001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.model.dataformat;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Map.Entry;
025
026import javax.xml.bind.annotation.XmlAccessType;
027import javax.xml.bind.annotation.XmlAccessorType;
028import javax.xml.bind.annotation.XmlAttribute;
029import javax.xml.bind.annotation.XmlElement;
030import javax.xml.bind.annotation.XmlRootElement;
031import javax.xml.bind.annotation.XmlTransient;
032import javax.xml.bind.annotation.XmlType;
033import javax.xml.bind.annotation.adapters.XmlAdapter;
034import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
035
036import org.apache.camel.CamelContext;
037import org.apache.camel.model.DataFormatDefinition;
038import org.apache.camel.spi.DataFormat;
039import org.apache.camel.spi.Metadata;
040import org.apache.camel.spi.RouteContext;
041import org.apache.camel.util.CamelContextHelper;
042import org.apache.camel.util.CollectionStringBuffer;
043import org.apache.camel.util.ObjectHelper;
044
045/**
046 * XSTream data format is used for unmarshal a XML payload to POJO or to marshal POJO back to XML payload.
047 *
048 * @version 
049 */
050@Metadata(firstVersion = "1.3.0", label = "dataformat,transformation,xml,json", title = "XStream")
051@XmlRootElement(name = "xstream")
052@XmlAccessorType(XmlAccessType.NONE)
053public class XStreamDataFormat extends DataFormatDefinition {
054    @XmlAttribute
055    private String permissions;
056    @XmlAttribute
057    private String encoding;
058    @XmlAttribute
059    private String driver;
060    @XmlAttribute
061    private String driverRef;
062    @XmlAttribute
063    private String mode;
064
065    @XmlJavaTypeAdapter(ConvertersAdapter.class)
066    @XmlElement(name = "converters")
067    private List<String> converters;
068    @XmlJavaTypeAdapter(AliasAdapter.class)
069    @XmlElement(name = "aliases")
070    private Map<String, String> aliases;
071    @XmlJavaTypeAdapter(OmitFieldsAdapter.class)
072    @XmlElement(name = "omitFields")
073    private Map<String, String[]> omitFields;
074    @XmlJavaTypeAdapter(ImplicitCollectionsAdapter.class)
075    @XmlElement(name = "implicitCollections")
076    private Map<String, String[]> implicitCollections;
077
078    public XStreamDataFormat() {
079        super("xstream");
080    }
081    
082    public XStreamDataFormat(String encoding) {
083        this();
084        setEncoding(encoding);
085    }
086    
087    public String getEncoding() {
088        return encoding;
089    }
090
091    /**
092     * Sets the encoding to use
093     */
094    public void setEncoding(String encoding) {
095        this.encoding = encoding;
096    }
097
098    public String getDriver() {
099        return driver;
100    }
101
102    /**
103     * To use a custom XStream driver.
104     * The instance must be of type com.thoughtworks.xstream.io.HierarchicalStreamDriver
105     */
106    public void setDriver(String driver) {
107        this.driver = driver;
108    }
109
110    public String getDriverRef() {
111        return driverRef;
112    }
113
114    /**
115     * To refer to a custom XStream driver to lookup in the registry.
116     * The instance must be of type com.thoughtworks.xstream.io.HierarchicalStreamDriver
117     */
118    public void setDriverRef(String driverRef) {
119        this.driverRef = driverRef;
120    }
121    
122    public String getMode() {
123        return mode;
124    }
125
126    /**
127     * Mode for dealing with duplicate references The possible values are:
128     * <ul>
129     *     <li>NO_REFERENCES</li>
130     *     <li>ID_REFERENCES</li>
131     *     <li>XPATH_RELATIVE_REFERENCES</li>
132     *     <li>XPATH_ABSOLUTE_REFERENCES</li>
133     *     <li>SINGLE_NODE_XPATH_RELATIVE_REFERENCES</li>
134     *     <li>SINGLE_NODE_XPATH_ABSOLUTE_REFERENCES</li>
135     * </ul>
136     */
137    public void setMode(String mode) {
138        this.mode = mode;
139    }
140
141    public List<String> getConverters() {
142        return converters;
143    }
144
145    /**
146     * List of class names for using custom XStream converters.
147     * The classes must be of type com.thoughtworks.xstream.converters.Converter
148     */
149    public void setConverters(List<String> converters) {
150        this.converters = converters;
151    }
152
153    public Map<String, String> getAliases() {
154        return aliases;
155    }
156
157    /**
158     * Alias a Class to a shorter name to be used in XML elements.
159     */
160    public void setAliases(Map<String, String> aliases) {
161        this.aliases = aliases;
162    }
163
164    public Map<String, String[]> getOmitFields() {
165        return omitFields;
166    }
167
168    /**
169     * Prevents a field from being serialized. To omit a field you must always provide the
170     * declaring type and not necessarily the type that is converted.
171     */
172    public void setOmitFields(Map<String, String[]> omitFields) {
173        this.omitFields = omitFields;
174    }
175
176    public Map<String, String[]> getImplicitCollections() {
177        return implicitCollections;
178    }
179
180    /**
181     * Adds a default implicit collection which is used for any unmapped XML tag.
182     */
183    public void setImplicitCollections(Map<String, String[]> implicitCollections) {
184        this.implicitCollections = implicitCollections;
185    }
186
187    public String getPermissions() {
188        return permissions;
189    }
190
191    /**
192     * Adds permissions that controls which Java packages and classes XStream is allowed to use during
193     * unmarshal from xml/json to Java beans.
194     * <p/>
195     * A permission must be configured either here or globally using a JVM system property. The permission
196     * can be specified in a syntax where a plus sign is allow, and minus sign is deny.
197     * <br/>
198     * Wildcards is supported by using <tt>.*</tt> as prefix. For example to allow <tt>com.foo</tt> and all subpackages
199     * then specfy <tt>+com.foo.*</tt>. Multiple permissions can be configured separated by comma, such as
200     * <tt>+com.foo.*,-com.foo.bar.MySecretBean</tt>.
201     * <br/>
202     * The following default permission is always included: <tt>"-*,java.lang.*,java.util.*"</tt> unless
203     * its overridden by specifying a JVM system property with they key <tt>org.apache.camel.xstream.permissions</tt>.
204     */
205    public void setPermissions(String permissions) {
206        this.permissions = permissions;
207    }
208
209    /**
210     * To add permission for the given pojo classes.
211     * @param type the pojo class(es) xstream should use as allowed permission
212     * @see #setPermissions(String)
213     */
214    public void setPermissions(Class<?>... type) {
215        CollectionStringBuffer csb = new CollectionStringBuffer(",");
216        for (Class<?> clazz : type) {
217            csb.append("+");
218            csb.append(clazz.getName());
219        }
220        setPermissions(csb.toString());
221    }
222
223    @Override
224    protected DataFormat createDataFormat(RouteContext routeContext) {
225        if ("json".equals(this.driver)) {
226            setProperty(routeContext.getCamelContext(), this, "dataFormatName", "json-xstream");
227        }
228        DataFormat answer = super.createDataFormat(routeContext);
229        // need to lookup the reference for the xstreamDriver
230        if (ObjectHelper.isNotEmpty(driverRef)) {
231            setProperty(routeContext.getCamelContext(), answer, "xstreamDriver", CamelContextHelper.mandatoryLookup(routeContext.getCamelContext(), driverRef));
232        }
233        return answer;
234    }
235
236    @Override
237    protected void configureDataFormat(DataFormat dataFormat, CamelContext camelContext) {
238        if (this.permissions != null) {
239            setProperty(camelContext, dataFormat, "permissions", this.permissions);
240        }
241        if (encoding != null) {
242            setProperty(camelContext, dataFormat, "encoding", encoding);
243        }
244        if (this.converters != null) {
245            setProperty(camelContext, dataFormat, "converters", this.converters);
246        }
247        if (this.aliases != null) {
248            setProperty(camelContext, dataFormat, "aliases", this.aliases);
249        }
250        if (this.omitFields != null) {
251            setProperty(camelContext, dataFormat, "omitFields", this.omitFields);
252        }
253        if (this.implicitCollections != null) {
254            setProperty(camelContext, dataFormat, "implicitCollections", this.implicitCollections);
255        }
256        if (this.mode != null) {
257            setProperty(camelContext, dataFormat, "mode", mode);
258        }
259    }
260    
261    
262
263    @XmlTransient
264    public static class ConvertersAdapter extends XmlAdapter<ConverterList, List<String>> {
265        @Override
266        public ConverterList marshal(List<String> v) throws Exception {
267            if (v == null) {
268                return null;
269            }
270
271            List<ConverterEntry> list = new ArrayList<>();
272            for (String str : v) {
273                ConverterEntry entry = new ConverterEntry();
274                entry.setClsName(str);
275                list.add(entry);
276            }
277            ConverterList converterList = new ConverterList();
278            converterList.setList(list);
279            return converterList;
280        }
281
282        @Override
283        public List<String> unmarshal(ConverterList v) throws Exception {
284            if (v == null) {
285                return null;
286            }
287
288            List<String> list = new ArrayList<>();
289            for (ConverterEntry entry : v.getList()) {
290                list.add(entry.getClsName());
291            }
292            return list;
293        }
294    }
295
296    @XmlAccessorType(XmlAccessType.NONE)
297    @XmlType(name = "converterList", namespace = "http://camel.apache.org/schema/spring")
298    public static class ConverterList {
299        @XmlElement(name = "converter", namespace = "http://camel.apache.org/schema/spring")
300        private List<ConverterEntry> list;
301
302        public List<ConverterEntry> getList() {
303            return list;
304        }
305
306        public void setList(List<ConverterEntry> list) {
307            this.list = list;
308        }
309    }
310
311    @XmlAccessorType(XmlAccessType.NONE)
312    @XmlType(name = "converterEntry", namespace = "http://camel.apache.org/schema/spring")
313    public static class ConverterEntry {
314        @XmlAttribute(name = "class")
315        private String clsName;
316
317        public String getClsName() {
318            return clsName;
319        }
320
321        public void setClsName(String clsName) {
322            this.clsName = clsName;
323        }
324    }
325
326    @XmlTransient
327    public static class ImplicitCollectionsAdapter 
328            extends XmlAdapter<ImplicitCollectionList, Map<String, String[]>> {
329
330        @Override
331        public ImplicitCollectionList marshal(Map<String, String[]> v) throws Exception {
332            if (v == null || v.isEmpty()) {
333                return null;
334            }
335
336            List<ImplicitCollectionEntry> list = new ArrayList<>();
337            for (Entry<String, String[]> e : v.entrySet()) {
338                ImplicitCollectionEntry entry = new ImplicitCollectionEntry(e.getKey(), e.getValue());
339                list.add(entry);
340            }
341
342            ImplicitCollectionList collectionList = new ImplicitCollectionList();
343            collectionList.setList(list);
344
345            return collectionList;
346        }
347
348        @Override
349        public Map<String, String[]> unmarshal(ImplicitCollectionList v) throws Exception {
350            if (v == null) {
351                return null;
352            }
353
354            Map<String, String[]> map = new HashMap<>();
355            for (ImplicitCollectionEntry entry : v.getList()) {
356                map.put(entry.getClsName(), entry.getFields());
357            }
358            return map;
359        }
360    }
361
362    @XmlAccessorType(XmlAccessType.NONE)
363    @XmlType(name = "implicitCollectionList", namespace = "http://camel.apache.org/schema/spring")
364    public static class ImplicitCollectionList {
365        @XmlElement(name = "class", namespace = "http://camel.apache.org/schema/spring")
366        private List<ImplicitCollectionEntry> list;
367
368        public List<ImplicitCollectionEntry> getList() {
369            return list;
370        }
371
372        public void setList(List<ImplicitCollectionEntry> list) {
373            this.list = list;
374        }
375    }
376
377    @XmlAccessorType(XmlAccessType.NONE)
378    @XmlType(name = "implicitCollectionEntry", namespace = "http://camel.apache.org/schema/spring")
379    public static class ImplicitCollectionEntry {
380        @XmlAttribute(name = "name")
381        private String clsName;
382
383        @XmlElement(name = "field", namespace = "http://camel.apache.org/schema/spring")
384        private String[] fields;
385
386        public ImplicitCollectionEntry() {
387        }
388
389        public ImplicitCollectionEntry(String clsName, String[] fields) {
390            this.clsName = clsName;
391            this.fields = fields;
392        }
393
394        public String getClsName() {
395            return clsName;
396        }
397
398        public void setClsName(String clsName) {
399            this.clsName = clsName;
400        }
401
402        public String[] getFields() {
403            return fields;
404        }
405
406        public void setFields(String[] fields) {
407            this.fields = fields;
408        }
409
410        @Override
411        public String toString() {
412            return "Alias[ImplicitCollection=" + clsName + ", fields=" + Arrays.asList(this.fields) + "]";
413        }
414    }
415
416    @XmlTransient
417    public static class AliasAdapter extends XmlAdapter<AliasList, Map<String, String>> {
418
419        @Override
420        public AliasList marshal(Map<String, String> value) throws Exception {
421            if (value == null || value.isEmpty()) {
422                return null;
423            }
424
425            List<AliasEntry> ret = new ArrayList<>(value.size());
426            for (Map.Entry<String, String> entry : value.entrySet()) {
427                ret.add(new AliasEntry(entry.getKey(), entry.getValue()));
428            }
429            AliasList jaxbMap = new AliasList();
430            jaxbMap.setList(ret);
431            return jaxbMap;
432        }
433
434        @Override
435        public Map<String, String> unmarshal(AliasList value) throws Exception {
436            if (value == null || value.getList() == null || value.getList().isEmpty()) {
437                return null;
438            }
439
440            Map<String, String> answer = new HashMap<>();
441            for (AliasEntry alias : value.getList()) {
442                answer.put(alias.getName(), alias.getClsName());
443            }
444            return answer;
445        }
446    }
447
448    @XmlAccessorType(XmlAccessType.NONE)
449    @XmlType(name = "aliasList", namespace = "http://camel.apache.org/schema/spring")
450    public static class AliasList {
451        @XmlElement(name = "alias", namespace = "http://camel.apache.org/schema/spring")
452        private List<AliasEntry> list;
453
454        public List<AliasEntry> getList() {
455            return list;
456        }
457
458        public void setList(List<AliasEntry> list) {
459            this.list = list;
460        }
461    }
462
463    @XmlAccessorType(XmlAccessType.NONE)
464    @XmlType(name = "aliasEntry", namespace = "http://camel.apache.org/schema/spring")
465    public static class AliasEntry {
466
467        @XmlAttribute
468        private String name;
469
470        @XmlAttribute(name = "class")
471        private String clsName;
472
473        public AliasEntry() {
474        }
475
476        public AliasEntry(String key, String clsName) {
477            this.name = key;
478            this.clsName = clsName;
479        }
480
481        public String getName() {
482            return name;
483        }
484
485        public void setName(String name) {
486            this.name = name;
487        }
488
489        public String getClsName() {
490            return clsName;
491        }
492
493        public void setClsName(String clsName) {
494            this.clsName = clsName;
495        }
496
497        @Override
498        public String toString() {
499            return "Alias[name=" + name + ", class=" + clsName + "]";
500        }
501    }
502
503    @XmlTransient
504    public static class OmitFieldsAdapter
505            extends XmlAdapter<OmitFieldList, Map<String, String[]>> {
506
507        @Override
508        public OmitFieldList marshal(Map<String, String[]> v) throws Exception {
509            if (v == null || v.isEmpty()) {
510                return null;
511            }
512
513            List<OmitFieldEntry> list = new ArrayList<>();
514            for (Entry<String, String[]> e : v.entrySet()) {
515                OmitFieldEntry entry = new OmitFieldEntry(e.getKey(), e.getValue());
516                list.add(entry);
517            }
518
519            OmitFieldList collectionList = new OmitFieldList();
520            collectionList.setList(list);
521
522            return collectionList;
523        }
524
525        @Override
526        public Map<String, String[]> unmarshal(OmitFieldList v) throws Exception {
527            if (v == null || v.getList() == null || v.getList().isEmpty()) {
528                return null;
529            }
530
531            Map<String, String[]> map = new HashMap<>();
532            for (OmitFieldEntry entry : v.getList()) {
533                map.put(entry.getClsName(), entry.getFields());
534            }
535            return map;
536        }
537    }
538
539    @XmlAccessorType(XmlAccessType.NONE)
540    @XmlType(name = "omitFieldList", namespace = "http://camel.apache.org/schema/spring")
541    public static class OmitFieldList {
542        @XmlElement(name = "omitField", namespace = "http://camel.apache.org/schema/spring")
543        private List<OmitFieldEntry> list;
544
545        public List<OmitFieldEntry> getList() {
546            return list;
547        }
548
549        public void setList(List<OmitFieldEntry> list) {
550            this.list = list;
551        }
552    }
553
554    @XmlAccessorType(XmlAccessType.NONE)
555    @XmlType(name = "omitFieldEntry", namespace = "http://camel.apache.org/schema/spring")
556    public static class OmitFieldEntry {
557
558        @XmlAttribute(name = "class")
559        private String clsName;
560
561        @XmlElement(name = "field", namespace = "http://camel.apache.org/schema/spring")
562        private String[] fields;
563
564        public OmitFieldEntry() {
565        }
566
567        public OmitFieldEntry(String clsName, String[] fields) {
568            this.clsName = clsName;
569            this.fields = fields;
570        }
571
572        public String getClsName() {
573            return clsName;
574        }
575
576        public void setClsName(String clsName) {
577            this.clsName = clsName;
578        }
579
580        public String[] getFields() {
581            return fields;
582        }
583
584        public void setFields(String[] fields) {
585            this.fields = fields;
586        }
587
588        @Override
589        public String toString() {
590            return "OmitField[" + clsName + ", fields=" + Arrays.asList(this.fields) + "]";
591        }
592    }
593}