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 }