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.TypeConverter;
038    import org.apache.camel.converter.stream.StreamCache;
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: 748450 $
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        protected <T> boolean isJaxbType(Class<T> type) {
090            XmlRootElement element = type.getAnnotation(XmlRootElement.class);
091            return element != null;
092        }
093    
094        /**
095         * Lets try parse via JAXB
096         */
097        protected <T> T unmarshall(Class<T> type, Object value) throws JAXBException {
098            if (value == null) {
099                throw new IllegalArgumentException("Cannot convert from null value to JAXBSource");
100            }
101    
102            JAXBContext context = createContext(type);
103            // must create a new instance of unmarshaller as its not thred safe
104            Unmarshaller unmarshaller = context.createUnmarshaller();
105    
106            if (parentTypeConverter != null) {
107                InputStream inputStream = parentTypeConverter.convertTo(InputStream.class, value);
108                if (inputStream != null) {
109                    Object unmarshalled = unmarshal(unmarshaller, inputStream);
110                    return type.cast(unmarshalled);
111                }
112                Reader reader = parentTypeConverter.convertTo(Reader.class, value);
113                if (reader != null) {
114                    Object unmarshalled = unmarshal(unmarshaller, reader);
115                    return type.cast(unmarshalled);
116                }
117                Source source = parentTypeConverter.convertTo(Source.class, value);
118                if (source != null) {
119                    Object unmarshalled = unmarshal(unmarshaller, source);
120                    return type.cast(unmarshalled);
121                }
122            }
123    
124            if (value instanceof String) {
125                value = new StringReader((String) value);
126            }
127            if (value instanceof InputStream || value instanceof Reader) {
128                Object unmarshalled = unmarshal(unmarshaller, value);
129                return type.cast(unmarshalled);
130            }
131    
132            return null;
133        }
134    
135        protected <T> T marshall(Class<T> type, Object value) throws JAXBException {
136            if (parentTypeConverter != null) {
137                // lets convert the object to a JAXB source and try convert that to
138                // the required source
139                JAXBContext context = createContext(value.getClass());
140                JAXBSource source = new JAXBSource(context, value);
141                try {
142                    return parentTypeConverter.convertTo(type, source);
143                } catch (NoTypeConversionAvailableException e) {
144                    // lets try a stream
145                    StringWriter buffer = new StringWriter();
146                    // must create a new instance of marshaller as its not thred safe
147                    Marshaller marshaller = context.createMarshaller();
148                    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isPrettyPrint() ? Boolean.TRUE : Boolean.FALSE);
149                    marshaller.marshal(value, buffer);
150                    return parentTypeConverter.convertTo(type, buffer.toString());
151                }
152            }
153    
154            // lets try convert to the type from JAXB
155            return null;
156        }
157    
158        /**
159         * Unmarshals the given value with the unmarshaller
160         *
161         * @param unmarshaller  the unmarshaller
162         * @param value  the stream to unmarshal (will close it after use, also if exception is thrown)
163         * @return  the value
164         * @throws JAXBException is thrown if an exception occur while unmarshalling
165         */
166        protected Object unmarshal(Unmarshaller unmarshaller, Object value) throws JAXBException {
167            try {
168                if (value instanceof InputStream) {
169                    return unmarshaller.unmarshal((InputStream) value);
170                } else if (value instanceof Reader) {
171                    return unmarshaller.unmarshal((Reader) value);
172                } else if (value instanceof Source) {
173                    return unmarshaller.unmarshal((Source) value);
174                }
175            } finally {
176                if (value instanceof Closeable) {
177                    ObjectHelper.close((Closeable) value, "Unmarshalling", LOG);
178                }
179            }
180            return null;
181        }
182    
183        protected synchronized <T> JAXBContext createContext(Class<T> type) throws JAXBException {
184            JAXBContext context = contexts.get(type);
185            if (context == null) {
186                context = JAXBContext.newInstance(type);
187                contexts.put(type, context);
188            }
189            return context;
190        }
191    
192    }