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