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.List;
021import javax.xml.bind.annotation.XmlAccessType;
022import javax.xml.bind.annotation.XmlAccessorType;
023import javax.xml.bind.annotation.XmlAttribute;
024import javax.xml.bind.annotation.XmlElement;
025import javax.xml.bind.annotation.XmlRootElement;
026import javax.xml.bind.annotation.XmlTransient;
027
028import org.apache.camel.CamelContext;
029import org.apache.camel.model.DataFormatDefinition;
030import org.apache.camel.spi.DataFormat;
031import org.apache.camel.spi.Metadata;
032import org.apache.camel.spi.RouteContext;
033import org.apache.camel.util.ObjectHelper;
034
035/**
036 * YAML is a data format to marshal and unmarshal Java objects to and from YAML.
037 *
038 * @version
039 */
040@Metadata(firstVersion = "2.17.0", label = "dataformat,transformation,yaml", title = "YAML")
041@XmlRootElement(name = "yaml")
042@XmlAccessorType(XmlAccessType.FIELD)
043public class YAMLDataFormat extends DataFormatDefinition {
044    @XmlAttribute @Metadata(defaultValue = "SnakeYAML")
045    private YAMLLibrary library = YAMLLibrary.SnakeYAML;
046    @XmlTransient
047    private ClassLoader classLoader;
048    @XmlTransient
049    private Class<?> unmarshalType;
050    @XmlAttribute
051    private String unmarshalTypeName;
052    @XmlAttribute
053    private String constructor;
054    @XmlAttribute
055    private String representer;
056    @XmlAttribute
057    private String dumperOptions;
058    @XmlAttribute
059    private String resolver;
060    @XmlAttribute @Metadata(defaultValue = "true")
061    private Boolean useApplicationContextClassLoader = true;
062    @XmlAttribute @Metadata(defaultValue = "false")
063    private Boolean prettyFlow = false;
064    @XmlAttribute @Metadata(defaultValue = "false")
065    private Boolean allowAnyType = false;
066    @XmlElement(name = "typeFilter")
067    private List<YAMLTypeFilterDefinition> typeFilters;
068    @XmlAttribute
069    @Metadata(javaType = "java.lang.Integer", defaultValue = "50")
070    private int maxAliasesForCollections = 50;
071    @XmlAttribute
072    @Metadata(javaType = "java.lang.Boolean", defaultValue = "false")
073    private String allowRecursiveKeys;
074
075    public YAMLDataFormat() {
076        this(YAMLLibrary.SnakeYAML);
077    }
078
079    public YAMLDataFormat(YAMLLibrary library) {
080        super("yaml-" + library.name().toLowerCase());
081        this.library = library;
082    }
083
084    public YAMLDataFormat(YAMLLibrary library, Class<?> unmarshalType) {
085        super("yaml-" + library.name().toLowerCase());
086        this.library = library;
087        this.unmarshalType = unmarshalType;
088    }
089
090    public YAMLLibrary getLibrary() {
091        return library;
092    }
093
094    /**
095     * Which yaml library to use.
096     * <p/>
097     * By default it is SnakeYAML
098     */
099    public void setLibrary(YAMLLibrary library) {
100        this.library = library;
101        setDataFormatName("yaml-" + library.name().toLowerCase());
102    }
103
104    public Class<?> getUnmarshalType() {
105        return unmarshalType;
106    }
107
108    /**
109     * Class of the object to be created
110     */
111    public void setUnmarshalType(Class<?> type) {
112        this.unmarshalType = type;
113    }
114
115    public String getUnmarshalTypeName() {
116        return unmarshalTypeName;
117    }
118
119    /**
120     * Class name of the java type to use when unarmshalling
121     */
122    public void setUnmarshalTypeName(String unmarshalTypeName) {
123        this.unmarshalTypeName = unmarshalTypeName;
124    }
125
126    public ClassLoader getClassLoader() {
127        return classLoader;
128    }
129
130    /**
131     * Set a custom classloader
132     */
133    public void setClassLoader(ClassLoader classLoader) {
134        this.classLoader = classLoader;
135    }
136
137    public String getConstructor() {
138        return constructor;
139    }
140
141    /**
142     * BaseConstructor to construct incoming documents.
143     */
144    public void setConstructor(String constructor) {
145        this.constructor = constructor;
146    }
147
148    public String getRepresenter() {
149        return representer;
150    }
151
152    /**
153     * Representer to emit outgoing objects.
154     */
155    public void setRepresenter(String representer) {
156        this.representer = representer;
157    }
158
159    public String getDumperOptions() {
160        return dumperOptions;
161    }
162
163    /**
164     * DumperOptions to configure outgoing objects.
165     */
166    public void setDumperOptions(String dumperOptions) {
167        this.dumperOptions = dumperOptions;
168    }
169
170    public String getResolver() {
171        return resolver;
172    }
173
174    /**
175     * Resolver to detect implicit type
176     */
177    public void setResolver(String resolver) {
178        this.resolver = resolver;
179    }
180
181    public boolean isUseApplicationContextClassLoader() {
182        return useApplicationContextClassLoader;
183    }
184
185    /**
186     * Use ApplicationContextClassLoader as custom ClassLoader
187     */
188    public void setUseApplicationContextClassLoader(boolean useApplicationContextClassLoader) {
189        this.useApplicationContextClassLoader = useApplicationContextClassLoader;
190    }
191
192    public boolean isPrettyFlow() {
193        return prettyFlow;
194    }
195
196    /**
197     * Force the emitter to produce a pretty YAML document when using the flow
198     * style.
199     */
200    public void setPrettyFlow(boolean prettyFlow) {
201        this.prettyFlow = prettyFlow;
202    }
203
204    public boolean isAllowAnyType() {
205        return allowAnyType;
206    }
207
208    /**
209     * Allow any class to be un-marshaled
210     */
211    public void setAllowAnyType(boolean allowAnyType) {
212        this.allowAnyType = allowAnyType;
213    }
214
215    public List<YAMLTypeFilterDefinition> getTypeFilters() {
216        return typeFilters;
217    }
218
219    public int getMaxAliasesForCollections() {
220        return maxAliasesForCollections;
221    }
222
223    /**
224     * Set the maximum amount of aliases allowed for collections.
225     */
226    public void setMaxAliasesForCollections(int maxAliasesForCollections) {
227        this.maxAliasesForCollections = maxAliasesForCollections;
228    }
229
230    public String getAllowRecursiveKeys() {
231        return allowRecursiveKeys;
232    }
233
234    /**
235     * Set whether recursive keys are allowed.
236     */
237    public void setAllowRecursiveKeys(String allowRecursiveKeys) {
238        this.allowRecursiveKeys = allowRecursiveKeys;
239    }
240
241    /**
242     * Set the types SnakeYAML is allowed to un-marshall
243     */
244    public void setTypeFilters(List<YAMLTypeFilterDefinition> typeFilters) {
245        this.typeFilters = typeFilters;
246    }
247
248    @Override
249    protected DataFormat createDataFormat(RouteContext routeContext) {
250        if (library == YAMLLibrary.SnakeYAML) {
251            setProperty(routeContext.getCamelContext(), this, "dataFormatName", "yaml-snakeyaml");
252        }
253
254        return super.createDataFormat(routeContext);
255    }
256
257    @Override
258    protected void configureDataFormat(DataFormat dataFormat, CamelContext camelContext) {
259        if (library == YAMLLibrary.SnakeYAML) {
260            configureSnakeDataFormat(dataFormat, camelContext);
261        }
262    }
263
264    protected void configureSnakeDataFormat(DataFormat dataFormat, CamelContext camelContext) {
265        Class<?> yamlUnmarshalType =  unmarshalType;
266        if (yamlUnmarshalType == null && unmarshalTypeName != null) {
267            try {
268                yamlUnmarshalType = camelContext.getClassResolver().resolveMandatoryClass(unmarshalTypeName);
269            } catch (ClassNotFoundException e) {
270                throw ObjectHelper.wrapRuntimeCamelException(e);
271            }
272        }
273
274        setProperty(dataFormat, camelContext, "unmarshalType", yamlUnmarshalType);
275        setProperty(dataFormat, camelContext, "classLoader", classLoader);
276        setProperty(dataFormat, camelContext, "useApplicationContextClassLoader", useApplicationContextClassLoader);
277        setProperty(dataFormat, camelContext, "prettyFlow", prettyFlow);
278        setProperty(dataFormat, camelContext, "allowAnyType", allowAnyType);
279
280        if (typeFilters != null && !typeFilters.isEmpty()) {
281            List<String> typeFilterDefinitions = new ArrayList<>(typeFilters.size());
282            for (YAMLTypeFilterDefinition definition : typeFilters) {
283                String value = definition.getValue();
284
285                if (!value.startsWith("type") && !value.startsWith("regexp")) {
286                    YAMLTypeFilterType type = definition.getType();
287                    if (type == null) {
288                        type = YAMLTypeFilterType.type;
289                    }
290
291                    value = type.name() + ":" + value;
292                }
293
294                typeFilterDefinitions.add(value);
295            }
296
297            setProperty(dataFormat, camelContext, "typeFilterDefinitions", typeFilterDefinitions);
298        }
299
300        setPropertyRef(dataFormat, camelContext, "constructor", constructor);
301        setPropertyRef(dataFormat, camelContext, "representer", representer);
302        setPropertyRef(dataFormat, camelContext, "dumperOptions", dumperOptions);
303        setPropertyRef(dataFormat, camelContext, "resolver", resolver);
304    }
305
306    protected void setProperty(DataFormat dataFormat, CamelContext camelContext, String propertyName, Object propertyValue) {
307        if (ObjectHelper.isNotEmpty(propertyValue)) {
308            setProperty(camelContext, dataFormat, propertyName, propertyValue);
309        }
310    }
311
312    protected void setPropertyRef(DataFormat dataFormat, CamelContext camelContext, String propertyName, String propertyValue) {
313        if (ObjectHelper.isNotEmpty(propertyValue)) {
314            // must be a reference value
315            String ref = propertyValue.startsWith("#") ? propertyValue : "#" + propertyValue;
316            setProperty(camelContext, dataFormat, propertyName, ref);
317        }
318    }
319
320}