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 }