/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.component.stax; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import java.util.Map; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.XMLEvent; import org.apache.camel.Exchange; import org.apache.camel.InvalidPayloadException; import org.apache.camel.RuntimeCamelException; import org.apache.camel.support.ExpressionAdapter; import org.apache.camel.util.LRUSoftCache; import org.apache.camel.util.ObjectHelper; import static org.apache.camel.component.stax.StAXUtil.getTagName; /** * {@link org.apache.camel.Expression} to walk a {@link org.apache.camel.Message} body * using an {@link Iterator}, which uses StAX to walk in streaming mode. * The elements returned is a POJO which is bound using JAXB annotations. * <p/> * The message body must be able to convert to {@link XMLEventReader} type which is used as stream * to access the message body. And there must be a JAXB annotated class to use as binding. */ public class StAXJAXBIteratorExpression<T> extends ExpressionAdapter { private static final Map<Class<?>, JAXBContext> JAX_CONTEXTS = new LRUSoftCache<Class<?>, JAXBContext>(1000); private final Class<T> handled; private final String handledName; private final boolean isNamespaceAware; /** * Creates this expression. * * @param handled the class which has JAXB annotations to bind POJO. */ public StAXJAXBIteratorExpression(Class<T> handled) { this(handled, true); } /** * Creates this expression. * * @param handled the class which has JAXB annotations to bind POJO. * @param isNamespaceAware sets the namespace awareness of the xml reader */ public StAXJAXBIteratorExpression(Class<T> handled, boolean isNamespaceAware) { ObjectHelper.notNull(handled, "handled"); this.handled = handled; this.handledName = null; this.isNamespaceAware = isNamespaceAware; } /** * Creates this expression. * * @param handledName the FQN name of the class which has JAXB annotations to bind POJO. */ public StAXJAXBIteratorExpression(String handledName) { this(handledName, true); } /** * Creates this expression. * * @param handledName the FQN name of the class which has JAXB annotations to bind POJO. * @param isNamespaceAware sets the namespace awareness of the xml reader */ public StAXJAXBIteratorExpression(String handledName, boolean isNamespaceAware) { ObjectHelper.notNull(handledName, "handledName"); this.handled = null; this.handledName = handledName; this.isNamespaceAware = isNamespaceAware; } private static JAXBContext jaxbContext(Class<?> handled) throws JAXBException { if (JAX_CONTEXTS.containsKey(handled)) { return JAX_CONTEXTS.get(handled); } JAXBContext context; synchronized (JAX_CONTEXTS) { context = JAXBContext.newInstance(handled); JAX_CONTEXTS.put(handled, context); } return context; } @Override @SuppressWarnings("unchecked") public Object evaluate(Exchange exchange) { try { XMLEventReader reader; if (isNamespaceAware) { reader = exchange.getIn().getMandatoryBody(XMLEventReader.class); } else { InputStream inputStream = exchange.getIn().getMandatoryBody(InputStream.class); XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); xmlInputFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, false); reader = xmlInputFactory.createXMLEventReader(inputStream); } Class<T> clazz = handled; if (clazz == null && handledName != null) { clazz = (Class<T>) exchange.getContext().getClassResolver().resolveMandatoryClass(handledName); } return createIterator(reader, clazz); } catch (InvalidPayloadException e) { exchange.setException(e); return null; } catch (JAXBException e) { exchange.setException(e); return null; } catch (ClassNotFoundException e) { exchange.setException(e); return null; } catch (XMLStreamException e) { exchange.setException(e); return null; } } private Iterator<T> createIterator(XMLEventReader reader, Class<T> clazz) throws JAXBException { return new StAXJAXBIterator<T>(clazz, reader); } /** * Iterator to walk the XML reader */ static class StAXJAXBIterator<T> implements Iterator<T>, Closeable { private final XMLEventReader reader; private final Class<T> clazz; private final String name; private final Unmarshaller unmarshaller; private T element; StAXJAXBIterator(Class<T> clazz, XMLEventReader reader) throws JAXBException { this.clazz = clazz; this.reader = reader; name = getTagName(clazz); JAXBContext jaxb = jaxbContext(clazz); // unmarshaller is not thread safe so we need to create a new instance per iterator unmarshaller = jaxb.createUnmarshaller(); } @Override public boolean hasNext() { if (element == null) { element = getNextElement(); } return element != null; } @Override public T next() { if (element == null) { element = getNextElement(); } T answer = element; element = null; return answer; } @Override public void remove() { throw new UnsupportedOperationException(); } T getNextElement() { XMLEvent xmlEvent; boolean found = false; while (!found && reader.hasNext()) { try { xmlEvent = reader.peek(); if (xmlEvent != null && xmlEvent.isStartElement() && name.equals(xmlEvent.asStartElement().getName().getLocalPart())) { found = true; } else { reader.nextEvent(); } } catch (XMLStreamException e) { throw new RuntimeCamelException(e); } } if (!found) { return null; } try { return unmarshaller.unmarshal(reader, clazz).getValue(); } catch (JAXBException e) { throw new RuntimeCamelException(e); } } @Override public void close() throws IOException { try { reader.close(); } catch (XMLStreamException e) { throw new IOException(e); } } } }