001/*
002 * The MIT License
003 *
004 * Copyright (c) 2015 Red Hat, Inc.
005 *
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 *
013 * The above copyright notice and this permission notice shall be included in
014 * all copies or substantial portions of the Software.
015 *
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
022 * THE SOFTWARE.
023 */
024package org.kohsuke.stapler.export;
025
026import java.io.IOException;
027import java.io.StringWriter;
028import java.lang.reflect.InvocationTargetException;
029import java.util.Arrays;
030import java.util.Collection;
031import java.util.List;
032
033import org.junit.Assert;
034import org.junit.Test;
035import static org.junit.Assert.assertEquals;
036
037public class ModelTest {
038    private ExportConfig config = new ExportConfig().withFlavor(Flavor.JSON).withClassAttribute(ClassAttributeBehaviour.ALWAYS.simple());
039    ModelBuilder builder = new ModelBuilder();
040
041    @Test // JENKINS-26775
042    public void syntheticMethodShouldNotBeExported() {
043        Model<Impl> model = builder.get(Impl.class);
044        assertEquals("Redundant properties discovered: " + model.getProperties(), 1, model.getProperties().size());
045    }
046
047    public static interface GenericInterface<T extends Number> {
048        Collection<T> get();
049    }
050
051    @ExportedBean
052    public static class Impl implements GenericInterface<Integer> {
053        @Exported
054        public List<Integer> get() {
055            return Arrays.asList(42);
056        }
057    }
058
059    //===========================================
060
061    @Test
062    public void merge() throws Exception {
063        StringWriter sw = new StringWriter();
064        builder.get(B.class).writeTo(b, Flavor.JSON.createDataWriter(b, sw, config));
065        // B.x should maskc C.x, so x should be 40
066        // but C.y should be printed as merged
067        assertEquals("{'_class':'B','y':20,'z':30,'x':40}", sw.toString().replace('"','\''));
068    }
069
070    /**
071     * y is a property from a merged object but that shouldn't be visible to {@link NamedPathPruner}.
072     */
073    @Test
074    public void merge_pathPrune() throws Exception {
075        StringWriter sw = new StringWriter();
076        builder.get(B.class).writeTo(b, new NamedPathPruner("z,y"), Flavor.JSON.createDataWriter(b, sw, config));
077        assertEquals("{'_class':'B','y':20,'z':30}", sw.toString().replace('"','\''));
078    }
079
080    B b = new B();
081    public static class B extends A {
082        @Exported
083        public int x = 40;
084    }
085
086    @ExportedBean
087    public static class A {
088        @Exported(merge=true)
089        public C c = new C();
090
091        @Exported
092        public int z = 30;
093    }
094
095    @ExportedBean
096    public static class C {
097        @Exported public int x = 10;
098        @Exported public int y = 20;
099    }
100
101    //===========================================
102
103    @Test
104    public void skipNull() throws Exception {
105        StringWriter sw = new StringWriter();
106        SomeNullProperty o = new SomeNullProperty();
107        builder.get(SomeNullProperty.class).writeTo(o, TreePruner.DEFAULT, Flavor.JSON.createDataWriter(o, sw, config));
108        assertEquals("{'_class':'SomeNullProperty','bbb':'bbb','ccc':null,'ddd':'ddd'}", sw.toString().replace('"','\''));
109    }
110
111    @ExportedBean
112    public static class SomeNullProperty {
113        @Exported(skipNull=true)
114        public String aaa = null;
115
116        @Exported(skipNull=true)
117        public String bbb = "bbb";
118
119        @Exported(skipNull=false)
120        public String ccc = null;
121
122        @Exported(skipNull=false)
123        public String ddd = "ddd";
124    }
125
126    /**
127     * Test ExportInterceptor
128     */
129
130    public static class ExportInterceptor1 extends ExportInterceptor{
131
132        @Override
133        public Object getValue(Property property, Object model, ExportConfig config) throws IOException {
134            try {
135                return property.getValue(model);
136            } catch (IllegalAccessException | InvocationTargetException | NotExportableException e) {
137                if(property.name.equals("shouldBeSkipped")){
138                    return SKIP;
139                }
140                throw new IOException(e);
141            }
142        }
143    }
144
145    public static class ExportInterceptor2 extends ExportInterceptor{
146
147        @Override
148        public Object getValue(Property property, Object model, ExportConfig config) throws IOException {
149            try {
150                return property.getValue(model);
151            } catch (IllegalAccessException | InvocationTargetException | NotExportableException e) {
152                if(!property.getType().isAssignableFrom(NotExportedBean.class)){
153                    throw new IOException("Failed to write "+property.name);
154                }
155                return SKIP; //skip failing property
156            }
157        }
158    }
159
160    @Test
161    public void testNotExportedBean() throws IOException {
162        ExportConfig config = new ExportConfig().withFlavor(Flavor.JSON).withExportInterceptor(new ExportInterceptor1()).withSkipIfFail(true);
163        StringWriter writer = new StringWriter();
164        ExportableBean b = new ExportableBean();
165        builder.get(ExportableBean.class).writeTo(b,Flavor.JSON.createDataWriter(b, writer, config));
166        Assert.assertEquals("{\"_class\":\""+ExportableBean.class.getName()+"\",\"name\":\"property1\",\"notExportedBean\":{},\"shouldBeNull\":null}",
167                writer.toString());
168    }
169
170    // should fail when serializing getShouldBeSkippedAsNull()
171    @Test(expected = IOException.class)
172    public void testNotExportedBeanFailing() throws IOException {
173        ExportConfig config = new ExportConfig().withFlavor(Flavor.JSON).withExportInterceptor(new ExportInterceptor2()).withSkipIfFail(true);
174        StringWriter writer = new StringWriter();
175        ExportableBean b = new ExportableBean();
176        builder.get(ExportableBean.class).writeTo(b,Flavor.JSON.createDataWriter(b, writer, config));
177    }
178
179    @ExportedBean
180    public static class ExportableBean{
181        @Exported
182        public String getName(){
183            return "property1";
184        }
185
186        // should be serialized as null
187        @Exported
188        public String getShouldBeNull(){
189            return null;
190        }
191
192        // should be skipped
193        @Exported
194        public String getShouldBeSkipped(){
195            throw new NullPointerException();
196        }
197
198        // null should be skipped
199        @Exported(skipNull = true)
200        public String getShouldBeSkippedAsNull(){
201            return null;
202        }
203        // should not get serialized in to JSON as empty map due to skipIfNull is true
204        @Exported
205        public NotExportedBean getNotExportedBean(){
206            return new NotExportedBean();
207        }
208
209        // should not get serialized in to JSON
210        @Exported(merge = true)
211        public NotExportedBean getNotExportedBeanMerged(){
212            return new NotExportedBean();
213        }
214    }
215
216    public static class NotExportedBean{
217        public String getName(){
218            return "property1";
219        }
220    }
221}