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.IOException;
020    import java.io.InputStream;
021    import java.io.InputStreamReader;
022    import java.io.OutputStream;
023    import java.io.UnsupportedEncodingException;
024    import javax.xml.bind.JAXBContext;
025    import javax.xml.bind.JAXBElement;
026    import javax.xml.bind.JAXBException;
027    import javax.xml.bind.Marshaller;
028    import javax.xml.bind.Unmarshaller;
029    import javax.xml.namespace.QName;
030    import javax.xml.stream.FactoryConfigurationError;
031    import javax.xml.stream.XMLOutputFactory;
032    import javax.xml.stream.XMLStreamException;
033    import javax.xml.stream.XMLStreamWriter;
034    import javax.xml.transform.Source;
035    import javax.xml.transform.stream.StreamSource;
036    
037    import org.apache.camel.CamelContext;
038    import org.apache.camel.CamelContextAware;
039    import org.apache.camel.Exchange;
040    import org.apache.camel.converter.IOConverter;
041    import org.apache.camel.impl.ServiceSupport;
042    import org.apache.camel.spi.DataFormat;
043    import org.apache.camel.util.IOHelper;
044    import org.apache.camel.util.ObjectHelper;
045    import org.slf4j.Logger;
046    import org.slf4j.LoggerFactory;
047    
048    /**
049     * A <a href="http://camel.apache.org/data-format.html">data format</a> ({@link DataFormat})
050     * using JAXB2 to marshal to and from XML
051     *
052     * @version 
053     */
054    public class JaxbDataFormat extends ServiceSupport implements DataFormat, CamelContextAware {
055    
056        private static final transient Logger LOG = LoggerFactory.getLogger(JaxbDataFormat.class);
057        private CamelContext camelContext;
058        private JAXBContext context;
059        private String contextPath;
060        private boolean prettyPrint = true;
061        private boolean ignoreJAXBElement = true;
062        private boolean filterNonXmlChars;
063        private String encoding;
064        private boolean fragment;
065        // partial support
066        private QName partNamespace;
067        private String partClass;
068        private Class partialClass;
069    
070        public JaxbDataFormat() {
071        }
072    
073        public JaxbDataFormat(JAXBContext context) {
074            this.context = context;
075        }
076    
077        public JaxbDataFormat(String contextPath) {
078            this.contextPath = contextPath;
079        }
080    
081        public void marshal(Exchange exchange, Object graph, OutputStream stream) throws IOException {
082            try {            
083                // must create a new instance of marshaller as its not thread safe
084                Marshaller marshaller = getContext().createMarshaller();
085                if (isPrettyPrint()) {
086                    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
087                } 
088                // exchange take precedence over encoding option
089                String charset = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
090                if (charset == null) {
091                    charset = encoding;
092                }
093                if (charset != null) {
094                    marshaller.setProperty(Marshaller.JAXB_ENCODING, charset);
095                }
096                if (isFragment()) {
097                    marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
098                }
099    
100                marshal(exchange, graph, stream, marshaller);
101    
102            } catch (JAXBException e) {
103                throw new IOException(e);
104            } catch (XMLStreamException e) {
105                throw new IOException(e);
106            }
107        }
108    
109        @SuppressWarnings("unchecked")
110        void marshal(Exchange exchange, Object graph, OutputStream stream, Marshaller marshaller)
111            throws XMLStreamException, JAXBException {
112    
113            Object e = graph;
114            if (partialClass != null && getPartNamespace() != null) {
115                e = new JAXBElement(getPartNamespace(), partialClass, graph);
116            }
117    
118            if (needFiltering(exchange)) {
119                marshaller.marshal(e, createFilteringWriter(stream));
120            } else {
121                marshaller.marshal(e, stream);
122            }
123        }
124    
125        private FilteringXmlStreamWriter createFilteringWriter(OutputStream stream)
126            throws XMLStreamException, FactoryConfigurationError {
127            XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(stream);
128            FilteringXmlStreamWriter filteringWriter = new FilteringXmlStreamWriter(writer);
129            return filteringWriter;
130        }
131    
132        @SuppressWarnings("unchecked")
133        public Object unmarshal(Exchange exchange, InputStream stream) throws IOException {
134            try {
135                // must create a new instance of unmarshaller as its not thread safe
136                Object answer;
137                Unmarshaller unmarshaller = getContext().createUnmarshaller();
138    
139                if (partialClass != null) {
140                    // partial unmarshalling
141                    Source source;
142                    if (needFiltering(exchange)) {
143                        source = new StreamSource(createNonXmlFilterReader(exchange, stream));
144                    } else {
145                        source = new StreamSource(stream);
146                    }
147                    answer = unmarshaller.unmarshal(source, partialClass);
148                } else {
149                    if (needFiltering(exchange)) {
150                        NonXmlFilterReader reader = createNonXmlFilterReader(exchange, stream);
151                        answer = unmarshaller.unmarshal(reader);
152                    } else  {
153                        answer = unmarshaller.unmarshal(stream);
154                    }
155                }
156    
157                if (answer instanceof JAXBElement && isIgnoreJAXBElement()) {
158                    answer = ((JAXBElement<?>)answer).getValue();
159                }
160                return answer;
161            } catch (JAXBException e) {
162                throw new IOException(e);
163            }
164        }
165    
166        private NonXmlFilterReader createNonXmlFilterReader(Exchange exchange, InputStream stream) throws UnsupportedEncodingException {
167            return new NonXmlFilterReader(new InputStreamReader(stream, IOConverter.getCharsetName(exchange)));
168        }
169    
170        protected boolean needFiltering(Exchange exchange) {
171            // exchange property takes precedence over data format property
172            return exchange == null ? filterNonXmlChars : exchange.getProperty(Exchange.FILTER_NON_XML_CHARS, filterNonXmlChars, Boolean.class);
173        }
174    
175        // Properties
176        // -------------------------------------------------------------------------
177        public boolean isIgnoreJAXBElement() {        
178            return ignoreJAXBElement;
179        }
180        
181        public void setIgnoreJAXBElement(boolean flag) {
182            ignoreJAXBElement = flag;
183        }
184        
185        public JAXBContext getContext() {
186            return context;
187        }
188    
189        public void setContext(JAXBContext context) {
190            this.context = context;
191        }
192    
193        public String getContextPath() {
194            return contextPath;
195        }
196    
197        public void setContextPath(String contextPath) {
198            this.contextPath = contextPath;
199        }
200    
201        public boolean isPrettyPrint() {
202            return prettyPrint;
203        }
204    
205        public void setPrettyPrint(boolean prettyPrint) {
206            this.prettyPrint = prettyPrint;
207        }
208        
209        public boolean isFragment() {
210            return fragment;
211        }
212        
213        public void setFragment(boolean fragment) {
214            this.fragment = fragment;
215        }
216    
217        public boolean isFilterNonXmlChars() {
218            return filterNonXmlChars;
219        }
220    
221        public void setFilterNonXmlChars(boolean filterNonXmlChars) {
222            this.filterNonXmlChars = filterNonXmlChars;
223        }
224    
225        public String getEncoding() {
226            return encoding;
227        }
228    
229        public void setEncoding(String encoding) {
230            this.encoding = encoding;
231        }
232    
233        public QName getPartNamespace() {
234            return partNamespace;
235        }
236    
237        public void setPartNamespace(QName partNamespace) {
238            this.partNamespace = partNamespace;
239        }
240    
241        public String getPartClass() {
242            return partClass;
243        }
244    
245        public void setPartClass(String partClass) {
246            this.partClass = partClass;
247        }
248    
249        public CamelContext getCamelContext() {
250            return camelContext;
251        }
252    
253        public void setCamelContext(CamelContext camelContext) {
254            this.camelContext = camelContext;
255        }
256    
257        @Override
258        protected void doStart() throws Exception {
259            ObjectHelper.notNull(camelContext, "CamelContext");
260    
261            if (context == null) {
262                // if context not injected, create one and resolve partial class up front so they are ready to be used
263                context = createContext();
264            }
265            if (partClass != null) {
266                partialClass = camelContext.getClassResolver().resolveMandatoryClass(partClass);
267            }
268        }
269    
270        @Override
271        protected void doStop() throws Exception {
272        }
273    
274        /**
275         * Strategy to create JAXB context
276         */
277        protected JAXBContext createContext() throws JAXBException {
278            if (contextPath != null) {
279                // prefer to use application class loader which is most likely to be able to
280                // load the the class which has been JAXB annotated
281                ClassLoader cl = camelContext.getApplicationContextClassLoader();
282                if (cl != null) {
283                    LOG.info("Creating JAXBContext with contextPath: " + contextPath + " and ApplicationContextClassLoader: " + cl);
284                    return JAXBContext.newInstance(contextPath, cl);
285                } else {
286                    LOG.info("Creating JAXBContext with contextPath: " + contextPath);
287                    return JAXBContext.newInstance(contextPath);
288                }
289            } else {
290                LOG.info("Creating JAXBContext");
291                return JAXBContext.newInstance();
292            }
293        }
294    
295    }