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.Reader;
022    import java.io.StringReader;
023    import java.io.StringWriter;
024    import java.util.HashMap;
025    import java.util.Map;
026    import javax.xml.bind.JAXBContext;
027    import javax.xml.bind.JAXBException;
028    import javax.xml.bind.Marshaller;
029    import javax.xml.bind.Unmarshaller;
030    import javax.xml.bind.annotation.XmlRootElement;
031    import javax.xml.bind.util.JAXBSource;
032    import javax.xml.transform.Source;
033    
034    import org.apache.camel.Exchange;
035    import org.apache.camel.NoTypeConversionAvailableException;
036    import org.apache.camel.RuntimeCamelException;
037    import org.apache.camel.StreamCache;
038    import org.apache.camel.TypeConverter;
039    import org.apache.camel.spi.TypeConverterAware;
040    import org.apache.camel.util.ObjectHelper;
041    import org.apache.commons.logging.Log;
042    import org.apache.commons.logging.LogFactory;
043    
044    /**
045     * @version $Revision: 834590 $
046     */
047    public class FallbackTypeConverter implements TypeConverter, TypeConverterAware {
048        private static final transient Log LOG = LogFactory.getLog(FallbackTypeConverter.class);
049        private Map<Class<?>, JAXBContext> contexts = new HashMap<Class<?>, JAXBContext>();
050        private TypeConverter parentTypeConverter;
051        private boolean prettyPrint = true;
052    
053        public boolean isPrettyPrint() {
054            return prettyPrint;
055        }
056    
057        public void setPrettyPrint(boolean prettyPrint) {
058            this.prettyPrint = prettyPrint;
059        }
060    
061        public void setTypeConverter(TypeConverter parentTypeConverter) {
062            this.parentTypeConverter = parentTypeConverter;
063        }
064    
065        public <T> T convertTo(Class<T> type, Object value) {
066            try {
067                if (isJaxbType(type)) {
068                    return unmarshall(type, value);
069                }
070                if (value != null) {
071                    if (isJaxbType(value.getClass()) && isNotStreamCacheType(type)) {
072                        return marshall(type, value);
073                    }
074                }
075                return null;
076            } catch (JAXBException e) {
077                throw new RuntimeCamelException(e);
078            }
079        }
080    
081        private <T> boolean isNotStreamCacheType(Class<T> type) {
082            return !StreamCache.class.isAssignableFrom(type);
083        }
084    
085        public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
086            return convertTo(type, value);
087        }
088    
089        public <T> T mandatoryConvertTo(Class<T> type, Object value) throws NoTypeConversionAvailableException {
090            T answer = convertTo(type, value);
091            if (answer == null) {
092                throw new NoTypeConversionAvailableException(value, type);
093            }
094            return answer;
095        }
096    
097        public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException {
098            return mandatoryConvertTo(type, value);
099        }
100    
101        protected <T> boolean isJaxbType(Class<T> type) {
102            XmlRootElement element = type.getAnnotation(XmlRootElement.class);
103            return element != null;
104        }
105    
106        /**
107         * Lets try parse via JAXB
108         */
109        protected <T> T unmarshall(Class<T> type, Object value) throws JAXBException {
110            if (value == null) {
111                throw new IllegalArgumentException("Cannot convert from null value to JAXBSource");
112            }
113    
114            JAXBContext context = createContext(type);
115            // must create a new instance of unmarshaller as its not thred safe
116            Unmarshaller unmarshaller = context.createUnmarshaller();
117    
118            if (parentTypeConverter != null) {
119                InputStream inputStream = parentTypeConverter.convertTo(InputStream.class, value);
120                if (inputStream != null) {
121                    Object unmarshalled = unmarshal(unmarshaller, inputStream);
122                    return type.cast(unmarshalled);
123                }
124                Reader reader = parentTypeConverter.convertTo(Reader.class, value);
125                if (reader != null) {
126                    Object unmarshalled = unmarshal(unmarshaller, reader);
127                    return type.cast(unmarshalled);
128                }
129                Source source = parentTypeConverter.convertTo(Source.class, value);
130                if (source != null) {
131                    Object unmarshalled = unmarshal(unmarshaller, source);
132                    return type.cast(unmarshalled);
133                }
134            }
135    
136            if (value instanceof String) {
137                value = new StringReader((String) value);
138            }
139            if (value instanceof InputStream || value instanceof Reader) {
140                Object unmarshalled = unmarshal(unmarshaller, value);
141                return type.cast(unmarshalled);
142            }
143    
144            return null;
145        }
146    
147        protected <T> T marshall(Class<T> type, Object value) throws JAXBException {
148            T answer = null;
149            if (parentTypeConverter != null) {
150                // lets convert the object to a JAXB source and try convert that to
151                // the required source
152                JAXBContext context = createContext(value.getClass());
153                JAXBSource source = new JAXBSource(context, value);
154    
155                answer = parentTypeConverter.convertTo(type, source);
156                if (answer == null) {
157                    // lets try a stream
158                    StringWriter buffer = new StringWriter();
159                    // must create a new instance of marshaller as its not thred safe
160                    Marshaller marshaller = context.createMarshaller();
161                    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isPrettyPrint() ? Boolean.TRUE : Boolean.FALSE);
162                    marshaller.marshal(value, buffer);
163                    answer = parentTypeConverter.convertTo(type, buffer.toString());
164                }
165            }
166    
167            return answer;
168        }
169    
170        /**
171         * Unmarshals the given value with the unmarshaller
172         *
173         * @param unmarshaller  the unmarshaller
174         * @param value  the stream to unmarshal (will close it after use, also if exception is thrown)
175         * @return  the value
176         * @throws JAXBException is thrown if an exception occur while unmarshalling
177         */
178        protected Object unmarshal(Unmarshaller unmarshaller, Object value) throws JAXBException {
179            try {
180                if (value instanceof InputStream) {
181                    return unmarshaller.unmarshal((InputStream) value);
182                } else if (value instanceof Reader) {
183                    return unmarshaller.unmarshal((Reader) value);
184                } else if (value instanceof Source) {
185                    return unmarshaller.unmarshal((Source) value);
186                }
187            } finally {
188                if (value instanceof Closeable) {
189                    ObjectHelper.close((Closeable) value, "Unmarshalling", LOG);
190                }
191            }
192            return null;
193        }
194    
195        protected synchronized <T> JAXBContext createContext(Class<T> type) throws JAXBException {
196            JAXBContext context = contexts.get(type);
197            if (context == null) {
198                context = JAXBContext.newInstance(type);
199                contexts.put(type, context);
200            }
201            return context;
202        }
203    
204    }