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.model.DataFormatDefinition;
037import org.apache.camel.spi.Metadata;
038import org.apache.camel.util.CollectionStringBuffer;
039
040/**
041 * XStream data format is used for unmarshal a XML payload to POJO or to marshal
042 * POJO back to XML payload.
043 */
044@Metadata(firstVersion = "1.3.0", label = "dataformat,transformation,xml,json", title = "XStream")
045@XmlRootElement(name = "xstream")
046@XmlAccessorType(XmlAccessType.NONE)
047public class XStreamDataFormat extends DataFormatDefinition {
048    @XmlAttribute
049    private String permissions;
050    @XmlAttribute
051    private String encoding;
052    @XmlAttribute
053    private String driver;
054    @XmlAttribute
055    private String driverRef;
056    @XmlAttribute
057    private String mode;
058
059    @XmlJavaTypeAdapter(ConvertersAdapter.class)
060    @XmlElement(name = "converters")
061    private List<String> converters;
062    @XmlJavaTypeAdapter(AliasAdapter.class)
063    @XmlElement(name = "aliases")
064    private Map<String, String> aliases;
065    @XmlJavaTypeAdapter(OmitFieldsAdapter.class)
066    @XmlElement(name = "omitFields")
067    private Map<String, String[]> omitFields;
068    @XmlJavaTypeAdapter(ImplicitCollectionsAdapter.class)
069    @XmlElement(name = "implicitCollections")
070    private Map<String, String[]> implicitCollections;
071
072    public XStreamDataFormat() {
073        super("xstream");
074    }
075
076    public XStreamDataFormat(String encoding) {
077        this();
078        setEncoding(encoding);
079    }
080
081    @Override
082    public String getDataFormatName() {
083        return "json".equals(driver) ? "json-xstream" : "xstream";
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. The instance must be of type
103     * 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. The
115     * instance must be of type
116     * 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. The classes must
147     * 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
170     * provide the declaring type and not necessarily the type that is
171     * converted.
172     */
173    public void setOmitFields(Map<String, String[]> omitFields) {
174        this.omitFields = omitFields;
175    }
176
177    public Map<String, String[]> getImplicitCollections() {
178        return implicitCollections;
179    }
180
181    /**
182     * Adds a default implicit collection which is used for any unmapped XML
183     * tag.
184     */
185    public void setImplicitCollections(Map<String, String[]> implicitCollections) {
186        this.implicitCollections = implicitCollections;
187    }
188
189    public String getPermissions() {
190        return permissions;
191    }
192
193    /**
194     * Adds permissions that controls which Java packages and classes XStream is
195     * allowed to use during unmarshal from xml/json to Java beans.
196     * <p/>
197     * A permission must be configured either here or globally using a JVM
198     * system property. The permission can be specified in a syntax where a plus
199     * sign is allow, and minus sign is deny. <br/>
200     * Wildcards is supported by using <tt>.*</tt> as prefix. For example to
201     * allow <tt>com.foo</tt> and all subpackages then specify
202     * <tt>+com.foo.*</tt>. Multiple permissions can be configured separated by
203     * comma, such as <tt>+com.foo.*,-com.foo.bar.MySecretBean</tt>. <br/>
204     * The following default permission is always included:
205     * <tt>"-*,java.lang.*,java.util.*"</tt> unless its overridden by specifying
206     * a JVM system property with they key
207     * <tt>org.apache.camel.xstream.permissions</tt>.
208     */
209    public void setPermissions(String permissions) {
210        this.permissions = permissions;
211    }
212
213    /**
214     * To add permission for the given pojo classes.
215     * 
216     * @param type the pojo class(es) xstream should use as allowed permission
217     * @see #setPermissions(String)
218     */
219    public void setPermissions(Class<?>... type) {
220        CollectionStringBuffer csb = new CollectionStringBuffer(",");
221        for (Class<?> clazz : type) {
222            csb.append("+");
223            csb.append(clazz.getName());
224        }
225        setPermissions(csb.toString());
226    }
227
228    @XmlTransient
229    public static class ConvertersAdapter extends XmlAdapter<ConverterList, List<String>> {
230        @Override
231        public ConverterList marshal(List<String> v) throws Exception {
232            if (v == null) {
233                return null;
234            }
235
236            List<ConverterEntry> list = new ArrayList<>();
237            for (String str : v) {
238                ConverterEntry entry = new ConverterEntry();
239                entry.setClsName(str);
240                list.add(entry);
241            }
242            ConverterList converterList = new ConverterList();
243            converterList.setList(list);
244            return converterList;
245        }
246
247        @Override
248        public List<String> unmarshal(ConverterList v) throws Exception {
249            if (v == null) {
250                return null;
251            }
252
253            List<String> list = new ArrayList<>();
254            for (ConverterEntry entry : v.getList()) {
255                list.add(entry.getClsName());
256            }
257            return list;
258        }
259    }
260
261    @XmlAccessorType(XmlAccessType.NONE)
262    @XmlType(name = "converterList", namespace = "http://camel.apache.org/schema/spring")
263    public static class ConverterList {
264        @XmlElement(name = "converter", namespace = "http://camel.apache.org/schema/spring")
265        private List<ConverterEntry> list;
266
267        public List<ConverterEntry> getList() {
268            return list;
269        }
270
271        public void setList(List<ConverterEntry> list) {
272            this.list = list;
273        }
274    }
275
276    @XmlAccessorType(XmlAccessType.NONE)
277    @XmlType(name = "converterEntry", namespace = "http://camel.apache.org/schema/spring")
278    public static class ConverterEntry {
279        @XmlAttribute(name = "class")
280        private String clsName;
281
282        public String getClsName() {
283            return clsName;
284        }
285
286        public void setClsName(String clsName) {
287            this.clsName = clsName;
288        }
289    }
290
291    @XmlTransient
292    public static class ImplicitCollectionsAdapter extends XmlAdapter<ImplicitCollectionList, Map<String, String[]>> {
293
294        @Override
295        public ImplicitCollectionList marshal(Map<String, String[]> v) throws Exception {
296            if (v == null || v.isEmpty()) {
297                return null;
298            }
299
300            List<ImplicitCollectionEntry> list = new ArrayList<>();
301            for (Entry<String, String[]> e : v.entrySet()) {
302                ImplicitCollectionEntry entry = new ImplicitCollectionEntry(e.getKey(), e.getValue());
303                list.add(entry);
304            }
305
306            ImplicitCollectionList collectionList = new ImplicitCollectionList();
307            collectionList.setList(list);
308
309            return collectionList;
310        }
311
312        @Override
313        public Map<String, String[]> unmarshal(ImplicitCollectionList v) throws Exception {
314            if (v == null) {
315                return null;
316            }
317
318            Map<String, String[]> map = new HashMap<>();
319            for (ImplicitCollectionEntry entry : v.getList()) {
320                map.put(entry.getClsName(), entry.getFields());
321            }
322            return map;
323        }
324    }
325
326    @XmlAccessorType(XmlAccessType.NONE)
327    @XmlType(name = "implicitCollectionList", namespace = "http://camel.apache.org/schema/spring")
328    public static class ImplicitCollectionList {
329        @XmlElement(name = "class", namespace = "http://camel.apache.org/schema/spring")
330        private List<ImplicitCollectionEntry> list;
331
332        public List<ImplicitCollectionEntry> getList() {
333            return list;
334        }
335
336        public void setList(List<ImplicitCollectionEntry> list) {
337            this.list = list;
338        }
339    }
340
341    @XmlAccessorType(XmlAccessType.NONE)
342    @XmlType(name = "implicitCollectionEntry", namespace = "http://camel.apache.org/schema/spring")
343    public static class ImplicitCollectionEntry {
344        @XmlAttribute(name = "name")
345        private String clsName;
346
347        @XmlElement(name = "field", namespace = "http://camel.apache.org/schema/spring")
348        private String[] fields;
349
350        public ImplicitCollectionEntry() {
351        }
352
353        public ImplicitCollectionEntry(String clsName, String[] fields) {
354            this.clsName = clsName;
355            this.fields = fields;
356        }
357
358        public String getClsName() {
359            return clsName;
360        }
361
362        public void setClsName(String clsName) {
363            this.clsName = clsName;
364        }
365
366        public String[] getFields() {
367            return fields;
368        }
369
370        public void setFields(String[] fields) {
371            this.fields = fields;
372        }
373
374        @Override
375        public String toString() {
376            return "Alias[ImplicitCollection=" + clsName + ", fields=" + Arrays.asList(this.fields) + "]";
377        }
378    }
379
380    @XmlTransient
381    public static class AliasAdapter extends XmlAdapter<AliasList, Map<String, String>> {
382
383        @Override
384        public AliasList marshal(Map<String, String> value) throws Exception {
385            if (value == null || value.isEmpty()) {
386                return null;
387            }
388
389            List<AliasEntry> ret = new ArrayList<>(value.size());
390            for (Map.Entry<String, String> entry : value.entrySet()) {
391                ret.add(new AliasEntry(entry.getKey(), entry.getValue()));
392            }
393            AliasList jaxbMap = new AliasList();
394            jaxbMap.setList(ret);
395            return jaxbMap;
396        }
397
398        @Override
399        public Map<String, String> unmarshal(AliasList value) throws Exception {
400            if (value == null || value.getList() == null || value.getList().isEmpty()) {
401                return null;
402            }
403
404            Map<String, String> answer = new HashMap<>();
405            for (AliasEntry alias : value.getList()) {
406                answer.put(alias.getName(), alias.getClsName());
407            }
408            return answer;
409        }
410    }
411
412    @XmlAccessorType(XmlAccessType.NONE)
413    @XmlType(name = "aliasList", namespace = "http://camel.apache.org/schema/spring")
414    public static class AliasList {
415        @XmlElement(name = "alias", namespace = "http://camel.apache.org/schema/spring")
416        private List<AliasEntry> list;
417
418        public List<AliasEntry> getList() {
419            return list;
420        }
421
422        public void setList(List<AliasEntry> list) {
423            this.list = list;
424        }
425    }
426
427    @XmlAccessorType(XmlAccessType.NONE)
428    @XmlType(name = "aliasEntry", namespace = "http://camel.apache.org/schema/spring")
429    public static class AliasEntry {
430
431        @XmlAttribute
432        private String name;
433
434        @XmlAttribute(name = "class")
435        private String clsName;
436
437        public AliasEntry() {
438        }
439
440        public AliasEntry(String key, String clsName) {
441            this.name = key;
442            this.clsName = clsName;
443        }
444
445        public String getName() {
446            return name;
447        }
448
449        public void setName(String name) {
450            this.name = name;
451        }
452
453        public String getClsName() {
454            return clsName;
455        }
456
457        public void setClsName(String clsName) {
458            this.clsName = clsName;
459        }
460
461        @Override
462        public String toString() {
463            return "Alias[name=" + name + ", class=" + clsName + "]";
464        }
465    }
466
467    @XmlTransient
468    public static class OmitFieldsAdapter extends XmlAdapter<OmitFieldList, Map<String, String[]>> {
469
470        @Override
471        public OmitFieldList marshal(Map<String, String[]> v) throws Exception {
472            if (v == null || v.isEmpty()) {
473                return null;
474            }
475
476            List<OmitFieldEntry> list = new ArrayList<>();
477            for (Entry<String, String[]> e : v.entrySet()) {
478                OmitFieldEntry entry = new OmitFieldEntry(e.getKey(), e.getValue());
479                list.add(entry);
480            }
481
482            OmitFieldList collectionList = new OmitFieldList();
483            collectionList.setList(list);
484
485            return collectionList;
486        }
487
488        @Override
489        public Map<String, String[]> unmarshal(OmitFieldList v) throws Exception {
490            if (v == null || v.getList() == null || v.getList().isEmpty()) {
491                return null;
492            }
493
494            Map<String, String[]> map = new HashMap<>();
495            for (OmitFieldEntry entry : v.getList()) {
496                map.put(entry.getClsName(), entry.getFields());
497            }
498            return map;
499        }
500    }
501
502    @XmlAccessorType(XmlAccessType.NONE)
503    @XmlType(name = "omitFieldList", namespace = "http://camel.apache.org/schema/spring")
504    public static class OmitFieldList {
505        @XmlElement(name = "omitField", namespace = "http://camel.apache.org/schema/spring")
506        private List<OmitFieldEntry> list;
507
508        public List<OmitFieldEntry> getList() {
509            return list;
510        }
511
512        public void setList(List<OmitFieldEntry> list) {
513            this.list = list;
514        }
515    }
516
517    @XmlAccessorType(XmlAccessType.NONE)
518    @XmlType(name = "omitFieldEntry", namespace = "http://camel.apache.org/schema/spring")
519    public static class OmitFieldEntry {
520
521        @XmlAttribute(name = "class")
522        private String clsName;
523
524        @XmlElement(name = "field", namespace = "http://camel.apache.org/schema/spring")
525        private String[] fields;
526
527        public OmitFieldEntry() {
528        }
529
530        public OmitFieldEntry(String clsName, String[] fields) {
531            this.clsName = clsName;
532            this.fields = fields;
533        }
534
535        public String getClsName() {
536            return clsName;
537        }
538
539        public void setClsName(String clsName) {
540            this.clsName = clsName;
541        }
542
543        public String[] getFields() {
544            return fields;
545        }
546
547        public void setFields(String[] fields) {
548            this.fields = fields;
549        }
550
551        @Override
552        public String toString() {
553            return "OmitField[" + clsName + ", fields=" + Arrays.asList(this.fields) + "]";
554        }
555    }
556}