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    }