blob: 7e030bb5f3068841d43ebadca0a894e37ab995e8 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.snakeyaml;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.ref.WeakReference;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
import org.apache.camel.spi.DataFormatName;
import org.apache.camel.support.ServiceSupport;
import org.apache.camel.util.IOHelper;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.BaseConstructor;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.resolver.Resolver;
/**
* A <a href="http://camel.apache.org/data-format.html">data format</a> ({@link DataFormat})
* using <a href="http://www.snakeyaml.org">SnakeYAML</a> to marshal to and from YAML.
*/
public class SnakeYAMLDataFormat extends ServiceSupport implements DataFormat, DataFormatName {
private ThreadLocal<WeakReference<Yaml>> yamlCache;
private BaseConstructor constructor;
private Representer representer;
private DumperOptions dumperOptions;
private Resolver resolver;
private ClassLoader classLoader;
private Class<?> unmarshalType;
private List<TypeDescription> typeDescriptions;
private Map<Class<?>, Tag> classTags;
private boolean useApplicationContextClassLoader;
private boolean prettyFlow;
public SnakeYAMLDataFormat() {
this(Object.class);
}
public SnakeYAMLDataFormat(Class<?> type) {
this.unmarshalType = type;
this.yamlCache = new ThreadLocal<>();
this.useApplicationContextClassLoader = true;
this.prettyFlow = false;
}
@Override
public String getDataFormatName() {
return "yaml-snakeyaml";
}
@Override
public void marshal(final Exchange exchange, final Object graph, final OutputStream stream) throws Exception {
try (final OutputStreamWriter osw = new OutputStreamWriter(stream, IOHelper.getCharsetName(exchange))) {
getYaml(exchange.getContext()).dump(graph, osw);
}
}
@Override
public Object unmarshal(final Exchange exchange, final InputStream stream) throws Exception {
try (final InputStreamReader isr = new InputStreamReader(stream, IOHelper.getCharsetName(exchange))) {
return getYaml(exchange.getContext()).loadAs(isr, unmarshalType);
}
}
protected Yaml getYaml(CamelContext context) {
Yaml yaml = null;
WeakReference<Yaml> ref = yamlCache.get();
if (ref != null) {
yaml = ref.get();
}
if (yaml == null) {
BaseConstructor yamlConstructor = this.constructor;
Representer yamlRepresenter = this.representer;
DumperOptions yamlDumperOptions = this.dumperOptions;
Resolver yamlResolver = this.resolver;
ClassLoader yamlClassLoader = this.classLoader;
if (yamlClassLoader == null && useApplicationContextClassLoader) {
yamlClassLoader = context.getApplicationContextClassLoader();
}
if (yamlConstructor == null) {
yamlConstructor = yamlClassLoader == null
? new Constructor()
: new CustomClassLoaderConstructor(yamlClassLoader);
if (typeDescriptions != null) {
for (TypeDescription typeDescription : typeDescriptions) {
((Constructor)yamlConstructor).addTypeDescription(typeDescription);
}
}
}
if (yamlRepresenter == null) {
yamlRepresenter = new Representer();
if (classTags != null) {
for (Map.Entry<Class<?>, Tag> entry : classTags.entrySet()) {
yamlRepresenter.addClassTag(entry.getKey(), entry.getValue());
}
}
}
if (yamlDumperOptions == null) {
yamlDumperOptions = new DumperOptions();
yamlDumperOptions.setPrettyFlow(prettyFlow);
}
if (yamlResolver == null) {
yamlResolver = new Resolver();
}
yaml = new Yaml(yamlConstructor, yamlRepresenter, yamlDumperOptions, yamlResolver);
yamlCache.set(new WeakReference<>(yaml));
}
return yaml;
}
@Override
protected void doStart() throws Exception {
// noop
}
@Override
protected void doStop() throws Exception {
// noop
}
public BaseConstructor getConstructor() {
return constructor;
}
/**
* BaseConstructor to construct incoming documents.
*/
public void setConstructor(BaseConstructor constructor) {
this.constructor = constructor;
}
public Representer getRepresenter() {
return representer;
}
/**
* Representer to emit outgoing objects.
*/
public void setRepresenter(Representer representer) {
this.representer = representer;
}
public DumperOptions getDumperOptions() {
return dumperOptions;
}
/**
* DumperOptions to configure outgoing objects.
*/
public void setDumperOptions(DumperOptions dumperOptions) {
this.dumperOptions = dumperOptions;
}
public Resolver getResolver() {
return resolver;
}
/**
* Resolver to detect implicit type
*/
public void setResolver(Resolver resolver) {
this.resolver = resolver;
}
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* Set a custom classloader
*/
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public Class<?> getUnmarshalType() {
return this.unmarshalType;
}
/**
* Class of the object to be created
*/
public void setUnmarshalType(Class<?> unmarshalType) {
this.unmarshalType = unmarshalType;
}
public List<TypeDescription> getTypeDescriptions() {
return typeDescriptions;
}
/**
* Make YAML aware how to parse a custom Class.
*/
public void setTypeDescriptions(List<TypeDescription> typeDescriptions) {
this.typeDescriptions = typeDescriptions;
}
public void addTypeDescriptions(TypeDescription... typeDescriptions) {
if (this.typeDescriptions == null) {
this.typeDescriptions = new LinkedList<>();
}
for (TypeDescription typeDescription : typeDescriptions) {
this.typeDescriptions.add(typeDescription);
}
}
public void addTypeDescription(Class<?> type, Tag tag) {
if (this.typeDescriptions == null) {
this.typeDescriptions = new LinkedList<>();
}
this.typeDescriptions.add(new TypeDescription(type, tag));
}
public Map<Class<?>, Tag> getClassTags() {
return classTags;
}
/**
* Define a tag for the <code>Class</code> to serialize.
*/
public void setClassTags(Map<Class<?>, Tag> classTags) {
this.classTags = classTags;
}
public void addClassTags(Class<?> type, Tag tag) {
if (this.classTags == null) {
this.classTags = new LinkedHashMap<>();
}
this.classTags.put(type, tag);
}
public boolean isUseApplicationContextClassLoader() {
return useApplicationContextClassLoader;
}
/**
* Use ApplicationContextClassLoader as custom ClassLoader
*/
public void setUseApplicationContextClassLoader(boolean useApplicationContextClassLoader) {
this.useApplicationContextClassLoader = useApplicationContextClassLoader;
}
public boolean isPrettyFlow() {
return prettyFlow;
}
/**
* Force the emitter to produce a pretty YAML document when using the flow
* style.
*/
public void setPrettyFlow(boolean prettyFlow) {
this.prettyFlow = prettyFlow;
}
/**
* Convenience method to set class tag for bot <code>Constructor</code> and
* <code>Representer</code>
*/
public void addTag(Class<?> type, Tag tag) {
addClassTags(type, tag);
addTypeDescription(type, tag);
}
}