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    
025    import javax.xml.bind.JAXBContext;
026    import javax.xml.bind.JAXBElement;
027    import javax.xml.bind.JAXBException;
028    import javax.xml.bind.Marshaller;
029    import javax.xml.bind.Unmarshaller;
030    import javax.xml.namespace.QName;
031    import javax.xml.stream.FactoryConfigurationError;
032    import javax.xml.stream.XMLOutputFactory;
033    import javax.xml.stream.XMLStreamException;
034    import javax.xml.stream.XMLStreamWriter;
035    import javax.xml.transform.Source;
036    import javax.xml.transform.stream.StreamSource;
037    
038    import org.apache.camel.Exchange;
039    import org.apache.camel.converter.IOConverter;
040    import org.apache.camel.spi.DataFormat;
041    import org.apache.camel.util.IOHelper;
042    
043    /**
044     * A <a href="http://camel.apache.org/data-format.html">data format</a> ({@link DataFormat})
045     * using JAXB2 to marshal to and from XML
046     *
047     * @version $Revision: 901192 $
048     */
049    public class JaxbDataFormat implements DataFormat {
050    
051        private JAXBContext context;
052        private String contextPath;
053        private boolean prettyPrint = true;
054        private boolean ignoreJAXBElement = true;
055        private boolean filterNonXmlChars;
056        private String encoding;
057        // partial support
058        private QName partNamespace;
059        private String partClass;
060        private Class partialClass;
061    
062        public JaxbDataFormat() {
063        }
064    
065        public JaxbDataFormat(JAXBContext context) {
066            this.context = context;
067        }
068    
069        public JaxbDataFormat(String contextPath) {
070            this.contextPath = contextPath;
071        }
072    
073        public void marshal(Exchange exchange, Object graph, OutputStream stream) throws IOException {
074            try {            
075                // must create a new instance of marshaller as its not thread safe
076                Marshaller marshaller = getContext().createMarshaller();
077                if (isPrettyPrint()) {
078                    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
079                } 
080                // exchange take precedence over encoding option
081                String charset = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
082                if (charset == null) {
083                    charset = encoding;
084                }
085                if (charset != null) {
086                    marshaller.setProperty(Marshaller.JAXB_ENCODING, charset);
087                }
088    
089                marshal(exchange, graph, stream, marshaller);
090    
091            } catch (JAXBException e) {
092                throw IOHelper.createIOException(e);
093            } catch (XMLStreamException e) {
094                throw IOHelper.createIOException(e);
095            }
096        }
097    
098        @SuppressWarnings("unchecked")
099        void marshal(Exchange exchange, Object graph, OutputStream stream, Marshaller marshaller)
100            throws XMLStreamException, JAXBException {
101    
102            Object e = graph;
103            if (getPartClass() != null && getPartNamespace() != null) {
104                e = new JAXBElement(getPartNamespace(), getPartialClass(exchange), graph);
105            }
106    
107            if (needFiltering(exchange)) {
108                marshaller.marshal(e, createFilteringWriter(stream));
109            } else {
110                marshaller.marshal(e, stream);
111            }
112        }
113    
114        private FilteringXmlStreamWriter createFilteringWriter(OutputStream stream)
115            throws XMLStreamException, FactoryConfigurationError {
116            XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(stream);
117            FilteringXmlStreamWriter filteringWriter = new FilteringXmlStreamWriter(writer);
118            return filteringWriter;
119        }
120    
121        @SuppressWarnings("unchecked")
122        public Object unmarshal(Exchange exchange, InputStream stream) throws IOException {
123            try {
124                // must create a new instance of unmarshaller as its not thread safe
125                Object answer;
126                Unmarshaller unmarshaller = getContext().createUnmarshaller();
127    
128                if (getPartClass() != null) {
129                    // partial unmarshalling
130                    Source source;
131                    if (needFiltering(exchange)) {
132                        source = new StreamSource(createNonXmlFilterReader(exchange, stream));
133                    } else {
134                        source = new StreamSource(stream);
135                    }
136                    answer = unmarshaller.unmarshal(source, getPartialClass(exchange));
137                } else {
138                    if (needFiltering(exchange)) {
139                        NonXmlFilterReader reader = createNonXmlFilterReader(exchange, stream);
140                        answer = unmarshaller.unmarshal(reader);
141                    } else  {
142                        answer = unmarshaller.unmarshal(stream);
143                    }
144                }
145    
146                if (answer instanceof JAXBElement && isIgnoreJAXBElement()) {
147                    answer = ((JAXBElement<?>)answer).getValue();
148                }
149                return answer;
150            } catch (JAXBException e) {
151                throw IOHelper.createIOException(e);
152            }
153        }
154    
155        private NonXmlFilterReader createNonXmlFilterReader(Exchange exchange, InputStream stream) throws UnsupportedEncodingException {
156            return new NonXmlFilterReader(new InputStreamReader(stream, IOConverter.getCharsetName(exchange)));
157        }
158    
159        protected boolean needFiltering(Exchange exchange) {
160            // exchange property takes precedence over data format property
161            return exchange == null ? filterNonXmlChars : exchange.getProperty(Exchange.FILTER_NON_XML_CHARS, filterNonXmlChars, Boolean.class);
162        }
163    
164        private synchronized Class getPartialClass(Exchange exchange) {
165            if (partialClass == null) {
166                partialClass = exchange.getContext().getClassResolver().resolveClass(getPartClass());
167            }
168            return partialClass;
169        }
170    
171        // Properties
172        // -------------------------------------------------------------------------
173        public boolean isIgnoreJAXBElement() {        
174            return ignoreJAXBElement;
175        }
176        
177        public void setIgnoreJAXBElement(boolean flag) {
178            ignoreJAXBElement = flag;
179        }
180        
181        public synchronized JAXBContext getContext() throws JAXBException {
182            if (context == null) {
183                context = createContext();
184            }
185            return context;
186        }
187    
188        public void setContext(JAXBContext context) {
189            this.context = context;
190        }
191    
192        public String getContextPath() {
193            return contextPath;
194        }
195    
196        public void setContextPath(String contextPath) {
197            this.contextPath = contextPath;
198        }
199    
200        public boolean isPrettyPrint() {
201            return prettyPrint;
202        }
203    
204        public void setPrettyPrint(boolean prettyPrint) {
205            this.prettyPrint = prettyPrint;
206        }
207    
208        public boolean isFilterNonXmlChars() {
209            return filterNonXmlChars;
210        }
211    
212        public void setFilterNonXmlChars(boolean filterNonXmlChars) {
213            this.filterNonXmlChars = filterNonXmlChars;
214        }
215    
216        public String getEncoding() {
217            return encoding;
218        }
219    
220        public void setEncoding(String encoding) {
221            this.encoding = encoding;
222        }
223    
224        public final QName getPartNamespace() {
225            return partNamespace;
226        }
227    
228        public final void setPartNamespace(QName partNamespace) {
229            this.partNamespace = partNamespace;
230        }
231    
232        public final String getPartClass() {
233            return partClass;
234        }
235    
236        public final void setPartClass(String partClass) {
237            this.partClass = partClass;
238        }
239    
240        protected JAXBContext createContext() throws JAXBException {
241            if (contextPath != null) {
242                return JAXBContext.newInstance(contextPath);
243            } else {
244                return JAXBContext.newInstance();
245            }
246        }
247    }