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 */
017 package org.apache.camel.converter.jaxb;
018
019 import java.io.Closeable;
020 import java.io.InputStream;
021 import java.io.InputStreamReader;
022 import java.io.Reader;
023 import java.io.StringReader;
024 import java.io.StringWriter;
025 import java.io.UnsupportedEncodingException;
026 import java.io.Writer;
027 import java.util.HashMap;
028 import java.util.Map;
029
030 import javax.xml.bind.JAXBContext;
031 import javax.xml.bind.JAXBException;
032 import javax.xml.bind.Marshaller;
033 import javax.xml.bind.Unmarshaller;
034 import javax.xml.bind.annotation.XmlRootElement;
035 import javax.xml.stream.FactoryConfigurationError;
036 import javax.xml.stream.XMLOutputFactory;
037 import javax.xml.stream.XMLStreamException;
038 import javax.xml.stream.XMLStreamWriter;
039 import javax.xml.transform.Source;
040
041 import org.apache.camel.Exchange;
042 import org.apache.camel.NoTypeConversionAvailableException;
043 import org.apache.camel.Processor;
044 import org.apache.camel.StreamCache;
045 import org.apache.camel.TypeConverter;
046 import org.apache.camel.component.bean.BeanInvocation;
047 import org.apache.camel.converter.IOConverter;
048 import org.apache.camel.spi.TypeConverterAware;
049 import org.apache.camel.util.IOHelper;
050 import org.apache.camel.util.ObjectHelper;
051 import org.slf4j.Logger;
052 import org.slf4j.LoggerFactory;
053
054 /**
055 * @version
056 */
057 public class FallbackTypeConverter implements TypeConverter, TypeConverterAware {
058 private static final transient Logger LOG = LoggerFactory.getLogger(FallbackTypeConverter.class);
059 private Map<Class<?>, JAXBContext> contexts = new HashMap<Class<?>, JAXBContext>();
060 private TypeConverter parentTypeConverter;
061 private boolean prettyPrint = true;
062
063 public boolean isPrettyPrint() {
064 return prettyPrint;
065 }
066
067 public void setPrettyPrint(boolean prettyPrint) {
068 this.prettyPrint = prettyPrint;
069 }
070
071 public void setTypeConverter(TypeConverter parentTypeConverter) {
072 this.parentTypeConverter = parentTypeConverter;
073 }
074
075 public <T> T convertTo(Class<T> type, Object value) {
076 return convertTo(type, null, value);
077 }
078
079 private <T> boolean isNotStreamCacheType(Class<T> type) {
080 return !StreamCache.class.isAssignableFrom(type);
081 }
082
083 public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
084 if (BeanInvocation.class.isAssignableFrom(type) || Processor.class.isAssignableFrom(type)) {
085 // JAXB cannot convert to a BeanInvocation / Processor, so we need to indicate this
086 // to avoid Camel trying to do this when using beans with JAXB payloads
087 return null;
088 }
089
090 try {
091 if (isJaxbType(type)) {
092 return unmarshall(type, exchange, value);
093 }
094 if (value != null) {
095 if (isJaxbType(value.getClass()) && isNotStreamCacheType(type)) {
096 return marshall(type, exchange, value);
097 }
098 }
099 return null;
100 } catch (Exception e) {
101 throw ObjectHelper.wrapCamelExecutionException(exchange, e);
102 }
103
104 }
105
106 public <T> T mandatoryConvertTo(Class<T> type, Object value) throws NoTypeConversionAvailableException {
107 return mandatoryConvertTo(type, null, value);
108 }
109
110 public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException {
111 T answer = convertTo(type, exchange, value);
112 if (answer == null) {
113 throw new NoTypeConversionAvailableException(value, type);
114 }
115 return answer;
116 }
117
118 protected <T> boolean isJaxbType(Class<T> type) {
119 XmlRootElement element = type.getAnnotation(XmlRootElement.class);
120 return element != null;
121 }
122
123 /**
124 * Lets try parse via JAXB
125 */
126 protected <T> T unmarshall(Class<T> type, Exchange exchange, Object value) throws Exception {
127 LOG.trace("Unmarshal to {} with value {}", type, value);
128
129 if (value == null) {
130 throw new IllegalArgumentException("Cannot convert from null value to JAXBSource");
131 }
132
133 JAXBContext context = createContext(type);
134 // must create a new instance of unmarshaller as its not thread safe
135 Unmarshaller unmarshaller = context.createUnmarshaller();
136
137 if (parentTypeConverter != null) {
138 InputStream inputStream = parentTypeConverter.convertTo(InputStream.class, value);
139 if (inputStream != null) {
140 Object unmarshalled = unmarshal(unmarshaller, exchange, inputStream);
141 return type.cast(unmarshalled);
142 }
143 Reader reader = parentTypeConverter.convertTo(Reader.class, value);
144 if (reader != null) {
145 Object unmarshalled = unmarshal(unmarshaller, exchange, reader);
146 return type.cast(unmarshalled);
147 }
148 Source source = parentTypeConverter.convertTo(Source.class, value);
149 if (source != null) {
150 Object unmarshalled = unmarshal(unmarshaller, exchange, source);
151 return type.cast(unmarshalled);
152 }
153 }
154
155 if (value instanceof String) {
156 value = new StringReader((String) value);
157 }
158 if (value instanceof InputStream || value instanceof Reader) {
159 Object unmarshalled = unmarshal(unmarshaller, exchange, value);
160 return type.cast(unmarshalled);
161 }
162
163 return null;
164 }
165
166 protected <T> T marshall(Class<T> type, Exchange exchange, Object value) throws JAXBException, XMLStreamException, FactoryConfigurationError {
167 LOG.trace("Marshal from value {} to type {}", value, type);
168
169 T answer = null;
170 if (parentTypeConverter != null) {
171 // lets convert the object to a JAXB source and try convert that to
172 // the required source
173 JAXBContext context = createContext(value.getClass());
174 // must create a new instance of marshaller as its not thread safe
175 Marshaller marshaller = context.createMarshaller();
176 Writer buffer = new StringWriter();
177 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isPrettyPrint() ? Boolean.TRUE : Boolean.FALSE);
178 if (exchange != null && exchange.getProperty(Exchange.CHARSET_NAME, String.class) != null) {
179 marshaller.setProperty(Marshaller.JAXB_ENCODING, exchange.getProperty(Exchange.CHARSET_NAME, String.class));
180 }
181 if (needFiltering(exchange)) {
182 XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(buffer);
183 FilteringXmlStreamWriter filteringWriter = new FilteringXmlStreamWriter(writer);
184 marshaller.marshal(value, filteringWriter);
185 } else {
186 marshaller.marshal(value, buffer);
187 }
188 answer = parentTypeConverter.convertTo(type, buffer.toString());
189 }
190
191 return answer;
192 }
193
194 protected Object unmarshal(Unmarshaller unmarshaller, Exchange exchange, Object value) throws JAXBException, UnsupportedEncodingException {
195 try {
196 if (value instanceof InputStream) {
197 if (needFiltering(exchange)) {
198 return unmarshaller.unmarshal(new NonXmlFilterReader(new InputStreamReader((InputStream)value, IOConverter.getCharsetName(exchange))));
199 }
200 return unmarshaller.unmarshal((InputStream)value);
201 } else if (value instanceof Reader) {
202 Reader reader = (Reader)value;
203 if (needFiltering(exchange)) {
204 if (!(value instanceof NonXmlFilterReader)) {
205 reader = new NonXmlFilterReader((Reader)value);
206 }
207 }
208 return unmarshaller.unmarshal(reader);
209 } else if (value instanceof Source) {
210 return unmarshaller.unmarshal((Source)value);
211 }
212 } finally {
213 if (value instanceof Closeable) {
214 IOHelper.close((Closeable)value, "Unmarshalling", LOG);
215 }
216 }
217 return null;
218 }
219
220 protected boolean needFiltering(Exchange exchange) {
221 // exchange property takes precedence over data format property
222 return exchange != null && exchange.getProperty(Exchange.FILTER_NON_XML_CHARS, Boolean.FALSE, Boolean.class);
223 }
224
225 protected synchronized <T> JAXBContext createContext(Class<T> type) throws JAXBException {
226 JAXBContext context = contexts.get(type);
227 if (context == null) {
228 context = JAXBContext.newInstance(type);
229 contexts.put(type, context);
230 }
231 return context;
232 }
233
234 }