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