Java tutorial
/******************************************************************************* * Copyright 2012 Danny Kunz * * Licensed 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.omnaest.utils.xml; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.concurrent.locks.Lock; import javax.sql.rowset.spi.XmlReader; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventFactory; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLEventWriter; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.Namespace; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import javax.xml.stream.util.XMLEventConsumer; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.omnaest.utils.assertion.Assert; import org.omnaest.utils.events.exception.ExceptionHandler; import org.omnaest.utils.events.exception.basic.ExceptionHandlerIgnoring; import org.omnaest.utils.structure.collection.list.ListUtils; import org.omnaest.utils.structure.container.ByteArrayContainer; import org.omnaest.utils.structure.element.ElementHolder; import org.omnaest.utils.structure.element.accessor.Accessor; import org.omnaest.utils.structure.element.accessor.adapter.ThreadLocalToAccessorAdapter; import org.omnaest.utils.structure.element.cached.CachedElement; import org.omnaest.utils.structure.element.cached.CachedElement.ValueResolver; import org.omnaest.utils.structure.element.cached.ThreadLocalCachedElement; import org.omnaest.utils.structure.element.converter.ElementConverter; import org.omnaest.utils.structure.element.converter.ElementConverterIdentitiyCast; import org.omnaest.utils.structure.element.factory.Factory; import org.omnaest.utils.structure.iterator.IterableUtils; import org.omnaest.utils.structure.iterator.IteratorUtils; import org.omnaest.utils.structure.map.MapUtils; import org.omnaest.utils.xml.JAXBXMLHelper.JAXBContextBasedUnmarshaller; import org.omnaest.utils.xml.JAXBXMLHelper.UnmarshallingConfiguration; import org.omnaest.utils.xml.XMLIteratorFactory.XMLElementSelector.SelectionContext; import org.omnaest.utils.xml.XMLIteratorFactory.XMLEventTransformerForTagAndAttributeName.XMLTagAndAttributeNameTransformer; import org.omnaest.utils.xml.XMLIteratorFactory.XMLEventTransformerForTagAndAttributeName.XMLTagAndAttributeNameTransformerLowerCase; import org.omnaest.utils.xml.XMLIteratorFactory.XMLEventTransformerForTagAndAttributeName.XMLTagAndAttributeNameTransformerRemoveNamespace; import org.omnaest.utils.xml.XMLIteratorFactory.XMLEventTransformerForTagAndAttributeName.XMLTagAndAttributeNameTransformerUpperCase; import org.omnaest.utils.xml.context.XMLInstanceContextFactory; import org.omnaest.utils.xml.context.XMLInstanceContextFactoryJavaStaxDefaultImpl; import org.omnaest.utils.xml.exception.MissingXMLRootElementAnnotationException; /** * The {@link XMLIteratorFactory} is a wrapper around StAX and JAXB which allows to split a given xml {@link InputStream} content * into {@link Object}, {@link Map} or {@link String} content chunks. <br> * <br> * <h2>Example:</h2><br> * Code using the {@link XMLIteratorFactory} to create an {@link Iterator} instance for all book elements: * * <pre> * Iterator<Book> iterator = new XMLIteratorFactory( inputStream ).doLowerCaseXMLTagAndAttributeNames().newIterator( Book.class ); * </pre> * * <br> * XML snippet: * * <pre> * <Books> * <Book> * <Title>Simple title</Title> * <author>an author</author> * </Book> * <Book> * <Title>Second simple title</Title> * <Author>Second author</Author> * </Book> * </Books> * </pre> * * <br> * JAXB annotated class: * * <pre> * @XmlRootElement(name = "book") * @XmlType(name = "book") * @XmlAccessorType(XmlAccessType.FIELD) * protected static class Book * { * @XmlElement(name = "title") * private String title; * * @XmlElement(name = "author") * private String author; * } * </pre> * * <br> * There are several {@link Iterator} types offered:<br> * <ul> * <li> {@link String} based: {@link #newIterator(QName)}</li> * <li> {@link Map} based: {@link #newIteratorMapBased(QName)}</li> * <li> {@link Class} type based: {@link #newIterator(Class)}</li> * </ul> * Those types are faster in traversal of the original stream from top to bottom, whereby the slower ones can get some performance * improvement by using parallel processing. The {@link Iterator} instances are thread safe by default and the * {@link Iterator#next()} function can be called until an {@link NoSuchElementException} is thrown. <br> * In normal circumstances an {@link Iterator} is not usable in multithreaded environments, since {@link Iterator#hasNext()} and * {@link Iterator#next()} produce imminent gaps within the {@link Lock} of an element. This gap can be circumvented by calling * <ul> * <li>{@link #doCreateThreadsafeIterators(boolean)}</li> * </ul> * which will force {@link Iterator} instances to use {@link ThreadLocal}s internally. Otherwise do not use the * {@link Iterator#hasNext()} method, since any other {@link Thread} can clear the {@link Iterator} before the call to * {@link Iterator#next()} occurs. <br> * <br> * The {@link XMLIteratorFactory} allows to modify the underlying event stream using e.g.:<br> * <ul> * <li> {@link #doLowerCaseXMLTagAndAttributeNames()}</li> * <li> {@link #doUpperCaseXMLTagAndAttributeNames()}</li> * <li> {@link #doRemoveNamespacesForXMLTagAndAttributeNames()}</li> * <li> {@link #doAddXMLEventTransformer(XMLEventTransformer)}</li> * </ul> * <br> * <br> * If the {@link XMLIteratorFactory} should only operate on a subset of xml tags within a larger stream the concept of sopes is * available, which can be instrumented by calling {@link #doAddXMLTagScope(QName)}.<br> * If no scope's start tag is passed no reading of events will occur and the reading into a single {@link Iterator} will stop * immediately when an end tag of a scope is matched. * * @author Omnaest */ public class XMLIteratorFactory { /* ************************************************** Constants *************************************************** */ public static final String DEFAULT_ENCODING = "UTF-8"; private final Factory<Accessor<String>> SIMPLE_ACCESSOR_FACTORY = new Factory<Accessor<String>>() { @Override public Accessor<String> newInstance() { return new ElementHolder<String>(); } }; private final Factory<Accessor<String>> THREADLOCAL_BASED_ACCESSOR_FACTORY = new Factory<Accessor<String>>() { @Override public Accessor<String> newInstance() { return new ThreadLocalToAccessorAdapter<String>(); } }; public static final XMLInstanceContextFactory XML_INSTANCE_CONTEXT_FACTORY_JAVA_STAX_DEFAULT = new XMLInstanceContextFactoryJavaStaxDefaultImpl(); public static final JAXBTypeContentConverterFactory DEFAULT_JAXB_TYPE_CONTENT_CONVERTER_FACTORY = new JAXBTypeContentConverterFactory() { @Override public <E> ElementConverter<String, E> newElementConverter(Class<? extends E> type, ExceptionHandler exceptionHandler) { return new JAXBTypeContentConverter<E>(type, exceptionHandler); } }; /* ************************************** Variables / State (internal/hiding) ************************************* */ private final CachedElement<XMLEventReader> xmlEventReaderCache; private final TraversalContextControl traversalContextControl; private final List<XMLEventTransformer> xmlEventTransformerList; private final List<Scope> scopeList; private final List<TouchBarrier> touchBarrierList; private XMLInstanceContextFactory xmlInstanceContextFactory; private Factory<Accessor<String>> accessorFactory = null; private String encoding = XMLIteratorFactory.DEFAULT_ENCODING; private JAXBTypeContentConverterFactory jaxbTypeContentConverterFactory = DEFAULT_JAXB_TYPE_CONTENT_CONVERTER_FACTORY; /* ***************************** Beans / Services / References / Delegates (external) ***************************** */ private final ExceptionHandler exceptionHandler; /* ********************************************** Classes/Interfaces ********************************************** */ /** * @see #newElementConverter(Class, ExceptionHandler) * @author Omnaest */ public static interface JAXBTypeContentConverterFactory { /** * Returns an {@link ElementConverter} which converts from a given xml content {@link String} to an {@link Object} of the * given type * * @param type * {@link Class} * @param exceptionHandler * {@link ExceptionHandler} * @return new {@link ElementConverter} */ public <E> ElementConverter<String, E> newElementConverter(Class<? extends E> type, ExceptionHandler exceptionHandler); } /** * @author Omnaest * @param <E> */ public static class JAXBTypeContentConverter<E> implements ElementConverter<String, E> { /* ************************************** Variables / State (internal/hiding) ************************************* */ protected final ThreadLocalCachedElement<JAXBContextBasedUnmarshaller<E>> cachedElement; /* *************************************************** Methods **************************************************** */ /** * @see JAXBTypeContentConverter * @param type * @param exceptionHandler */ public JAXBTypeContentConverter(final Class<? extends E> type, final ExceptionHandler exceptionHandler) { this.cachedElement = new ThreadLocalCachedElement<JAXBXMLHelper.JAXBContextBasedUnmarshaller<E>>( new ValueResolver<JAXBContextBasedUnmarshaller<E>>() { @SuppressWarnings("unchecked") @Override public JAXBContextBasedUnmarshaller<E> resolveValue() { return JAXBXMLHelper.<E>newJAXBContextBasedUnmarshaller((Class<E>) type, new UnmarshallingConfiguration().setExceptionHandler(exceptionHandler)); } }); } @Override public E convert(String element) { return this.cachedElement.getValue().unmarshal(new ByteArrayContainer(element).getInputStream()); } } /** * @see XMLIteratorFactory * @author Omnaest */ protected static final class XMLIterator implements Iterator<String> { /* ********************************************** Variables ********************************************** */ private final Accessor<String> nextElementAccessor; private final NamespaceStack namespaceStack = new NamespaceStack(); /* ********************************************** Beans / Services / References / Delegation ********************************************** */ private final Accessor<String> accessor; private final ScopeControl scopeControl; private final ExceptionHandler exceptionHandler; private final XMLEventReader xmlEventReader; private final XMLEventFactory xmlEventFactory; private final String encoding; private final TraversalContextControl traversalContextControl; private final XMLElementSelector xmlElementSelector; private final XMLOutputFactory xmlOutputFactory; private final TouchBarrierControl touchBarrierControl; private final List<XMLEventTransformer> xmlEventTransformerList; /* ********************************************** Methods ********************************************** */ /** * @see XMLIterator * @param accessor * @param scopeControl * @param exceptionHandler * @param xmlEventReader * @param xmlEventFactory * @param encoding * @param traversalContextControl * @param xmlElementSelector * @param xmlOutputFactory * @param touchBarrierControl * @param xmlEventTransformerList */ protected XMLIterator(Accessor<String> accessor, ScopeControl scopeControl, ExceptionHandler exceptionHandler, XMLEventReader xmlEventReader, XMLEventFactory xmlEventFactory, String encoding, TraversalContextControl traversalContextControl, XMLElementSelector xmlElementSelector, XMLOutputFactory xmlOutputFactory, TouchBarrierControl touchBarrierControl, List<XMLEventTransformer> xmlEventTransformerList) { this.accessor = accessor; this.scopeControl = scopeControl; this.exceptionHandler = exceptionHandler; this.xmlEventReader = xmlEventReader; this.xmlEventFactory = xmlEventFactory; this.encoding = encoding; this.traversalContextControl = traversalContextControl; this.xmlElementSelector = xmlElementSelector; this.xmlOutputFactory = xmlOutputFactory; this.touchBarrierControl = touchBarrierControl; this.xmlEventTransformerList = xmlEventTransformerList; this.nextElementAccessor = this.accessor; } /* ********************************************** Methods ********************************************** */ @Override public synchronized boolean hasNext() { // this.resolveNextElementIfUnresolved(); // return this.nextElementAccessor.getElement() != null; } @Override public synchronized String next() { // String retval = null; // this.resolveNextElementIfUnresolved(); // retval = this.nextElementAccessor.getElement(); this.nextElementAccessor.setElement(null); // if (retval == null) { throw new NoSuchElementException(); } // return retval; } @Override public void remove() { throw new UnsupportedOperationException(); } public void resolveNextElementIfUnresolved() { // if (this.nextElementAccessor.getElement() == null) { this.nextElementAccessor.setElement(this.resolveNextElement()); } } @SuppressWarnings("unchecked") public String resolveNextElement() { // String retval = null; // try { // final ByteArrayContainer byteArrayContainerOut = new ByteArrayContainer(); final OutputStream outputStream = byteArrayContainerOut.getOutputStream(); final XMLEventWriter xmlEventWriter = this.xmlOutputFactory.createXMLEventWriter(outputStream, this.encoding); final XMLEventConsumer xmlEventConsumer = xmlEventWriter; // boolean read = false; boolean done = false; boolean touchedBarrier = false; boolean hasWrittenAtLeastOneElement = false; while (!done && !this.scopeControl.hasTraversedAnyScope() && this.xmlEventReader.hasNext() && (read || (!touchedBarrier && !(touchedBarrier = this.touchBarrierControl.isAnyBarrierTouched( this.transformXMLElement(this.xmlEventReader.peek()), this.traversalContextControl.getCurrentSelectionContext().getQNameHierarchy()))))) { // final XMLEvent currentEvent = this.transformXMLElement(this.xmlEventReader.nextEvent()); XMLEvent writableEvent = currentEvent; // if (currentEvent.isStartElement()) { // final StartElement startElement = currentEvent.asStartElement(); final QName name = startElement.getName(); this.traversalContextControl.addQName(name); // final SelectionContext selectionContext = this.traversalContextControl .getCurrentSelectionContext(); this.scopeControl.visitStartElement(selectionContext); // if (this.xmlElementSelector.selectElement(selectionContext) && (!this.scopeControl.hasScopes() || this.scopeControl.hasEnteredAnyScope())) { // read = true; } // if (read) { // if (!hasWrittenAtLeastOneElement) { xmlEventWriter.add(this.xmlEventFactory.createStartDocument()); } // this.namespaceStack.addStack(startElement.getNamespaces()); writableEvent = transformEventIncludingCurrentNamespaceIfNonDefault( this.xmlEventFactory, startElement, name); } } // if (read) { // xmlEventConsumer.add(writableEvent); hasWrittenAtLeastOneElement = true; } if (currentEvent.isEndElement()) { // if (read) { this.namespaceStack.removeStack(); } // final SelectionContext selectionContext = this.traversalContextControl .getCurrentSelectionContext(); if (this.xmlElementSelector.selectElement(selectionContext)) { // read = false; done = true; // xmlEventWriter.add(this.xmlEventFactory.createEndDocument()); } // this.traversalContextControl.reduceLastQName(); // this.scopeControl.visitEndElement(selectionContext); } } // if (hasWrittenAtLeastOneElement) { // xmlEventWriter.close(); outputStream.close(); // if (byteArrayContainerOut.isNotEmpty()) { retval = byteArrayContainerOut.toString(ByteArrayContainer.ENCODING_UTF8); } } // if (!this.xmlEventReader.hasNext()) { this.xmlEventReader.close(); } } catch (Exception e) { this.exceptionHandler.handleException(e); } // return retval; } @SuppressWarnings("unchecked") private StartElement transformEventIncludingCurrentNamespaceIfNonDefault( final XMLEventFactory xmlEventFactory, final StartElement startElement, final QName name) { // StartElement retval = startElement; // final String prefix = name.getPrefix(); final String namespaceUri = name.getNamespaceURI(); final String localName = name.getLocalPart(); if (StringUtils.isNotBlank(namespaceUri) && !StringUtils.equalsIgnoreCase(namespaceUri, "##default") && !this.namespaceStack.hasDeclaredNamespace(prefix, namespaceUri)) { // final Namespace namespace = xmlEventFactory.createNamespace(prefix, namespaceUri); final Iterator<?> namespaces = IteratorUtils.addToNewIterator(startElement.getNamespaces(), namespace); final Iterator<?> attributes = startElement.getAttributes(); retval = xmlEventFactory.createStartElement(prefix, namespaceUri, localName, attributes, namespaces); this.namespaceStack.addNamespaceToCurrentNamespaceStack(namespace); } // return retval; } /** * @param xmlEvent * @return */ private XMLEvent transformXMLElement(final XMLEvent xmlEvent) { // XMLEvent retval = xmlEvent; // for (XMLEventTransformer xmlEventTransformer : this.xmlEventTransformerList) { retval = xmlEventTransformer.transform(retval, this.xmlEventFactory); } // return retval; } } /** * @author Omnaest */ public static interface XMLElementSelector { /* ********************************************** Classes/Interfaces ********************************************** */ /** * @author Omnaest */ public static interface SelectionContext { /** * @see QName * @return */ public QName getQName(); /** * Returns all {@link QName}s of the currents element location. The current element is the last entry of the returned * {@link List} * * @return */ public List<QName> getQNameHierarchy(); } /* ********************************************** Methods ********************************************** */ /** * @param selectionContext * {@link SelectionContext} * @return */ public boolean selectElement(SelectionContext selectionContext); } /** * {@link XMLElementSelector} which matches a given {@link QName} * * @see XMLElementSelector * @author Omnaest */ public static class XMLElementSelectorQNameBased implements XMLElementSelector { /* ********************************************** Variables ********************************************** */ private final String selectingNamespace; private final String selectingTagName; /* ********************************************** Methods ********************************************** */ /** * @see XMLElementSelectorQNameBased * @param qName */ public XMLElementSelectorQNameBased(QName qName) { super(); this.selectingNamespace = qName != null ? qName.getNamespaceURI() : null; this.selectingTagName = qName != null ? qName.getLocalPart() : null; } @Override public boolean selectElement(SelectionContext selectionContext) { // boolean retval = false; // final QName currentQName = selectionContext.getQName(); // final boolean matchesNamespace = StringUtils.isBlank(this.selectingNamespace) || (currentQName != null && StringUtils.equals(this.selectingNamespace, currentQName.getNamespaceURI())); final boolean matchesTagName = currentQName != null && StringUtils.equals(this.selectingTagName, currentQName.getLocalPart()); retval = matchesNamespace && matchesTagName; // return retval; } } /** * @see XMLIteratorFactory * @see #transform(XMLEvent, XMLEventFactory) * @author Omnaest */ public static interface XMLEventTransformer { /** * This methods returns a transformed {@link XMLEvent} based on the given {@link XMLEvent}. This can be e.g. lower casing the * local name. The {@link XMLEventFactory} supports the creation of new {@link XMLEvent}s. * * @param xmlEvent * @param xmlEventFactory * @return transformed {@link XMLEvent} */ public XMLEvent transform(XMLEvent xmlEvent, XMLEventFactory xmlEventFactory); } /** * {@link XMLEventTransformer} which allows to transform the tag and attribute names.<br> * <br> * * @author Omnaest */ public static class XMLEventTransformerForTagAndAttributeName implements XMLEventTransformer { /* ********************************************** Variables ********************************************** */ private final XMLTagAndAttributeNameTransformer xmlTagAndAttributeNameTransformer; /* ********************************************** Classes/Interfaces ********************************************** */ /** * @author Omnaest */ public static interface XMLTagAndAttributeNameTransformer { /** * @param tagName * {@link QName} * @return {@link QName} */ public QName transformTagName(QName tagName); /** * @param attributeName * {@link QName} * @return {@link QName} */ public QName transformAttributeName(QName attributeName); } /** * @author Omnaest */ public static class XMLTagAndAttributeNameTransformerUpperCase implements XMLTagAndAttributeNameTransformer { @Override public QName transformTagName(QName tagName) { return new QName(tagName.getNamespaceURI(), StringUtils.upperCase(tagName.getLocalPart())); } @Override public QName transformAttributeName(QName attributeName) { return new QName(attributeName.getNamespaceURI(), StringUtils.upperCase(attributeName.getLocalPart())); } } /** * @author Omnaest */ public static class XMLTagAndAttributeNameTransformerLowerCase implements XMLTagAndAttributeNameTransformer { @Override public QName transformTagName(QName tagName) { return new QName(tagName.getNamespaceURI(), StringUtils.lowerCase(tagName.getLocalPart())); } @Override public QName transformAttributeName(QName attributeName) { return new QName(attributeName.getNamespaceURI(), StringUtils.lowerCase(attributeName.getLocalPart())); } } /** * {@link XMLTagAndAttributeNameTransformer} which removes any {@link Namespace} from xml tag and attributes * * @author Omnaest */ public static class XMLTagAndAttributeNameTransformerRemoveNamespace implements XMLTagAndAttributeNameTransformer { @Override public QName transformTagName(QName tagName) { // return new QName(null, tagName.getLocalPart()); } @Override public QName transformAttributeName(QName attributeName) { return new QName(null, attributeName.getLocalPart()); } } /* ********************************************** Methods ********************************************** */ /** * @see XMLEventTransformerForTagAndAttributeName * @param xmlTagAndAttributeNameTransformer */ public XMLEventTransformerForTagAndAttributeName( XMLTagAndAttributeNameTransformer xmlTagAndAttributeNameTransformer) { super(); this.xmlTagAndAttributeNameTransformer = xmlTagAndAttributeNameTransformer; } @Override public XMLEvent transform(XMLEvent xmlEvent, XMLEventFactory xmlEventFactory) { // XMLEvent retval = xmlEvent; // if (xmlEvent.isStartElement()) { // final StartElement startElement = xmlEvent.asStartElement(); final QName name = startElement.getName(); // final QName qname = this.xmlTagAndAttributeNameTransformer.transformTagName(name); final Iterator<?> attributes = startElement.getAttributes(); final Iterator<?> namespaces = startElement.getNamespaces(); // retval = xmlEventFactory.createStartElement(qname, attributes, namespaces); } else if (xmlEvent.isEndElement()) { // final EndElement endElement = xmlEvent.asEndElement(); final QName name = endElement.getName(); // final QName qname = this.xmlTagAndAttributeNameTransformer.transformTagName(name); final Iterator<?> namespaces = endElement.getNamespaces(); // retval = xmlEventFactory.createEndElement(qname, namespaces); } else if (xmlEvent.isAttribute()) { // final Attribute attribute = (Attribute) xmlEvent; final QName name = attribute.getName(); // final QName qname = this.xmlTagAndAttributeNameTransformer.transformAttributeName(name); final String value = attribute.getValue(); // retval = xmlEventFactory.createAttribute(qname, value); } // return retval; } } /** * Implementation of a {@link Namespace} stack which can be used to distinguish if a single element should explicitly declare a * {@link Namespace} or if a parental element did this already. * * @author Omnaest */ private static class NamespaceStack { /* ********************************************** Variables ********************************************** */ private final List<Map<String, String>> prefixToNamespaceUriMapList = new ArrayList<Map<String, String>>(); /* ********************************************** Methods ********************************************** */ /** * Adds a new stack layer. This should be called for each StartElement with all the explicitly declared {@link Namespace}s by * this element. * * @see #removeStack() * @see Namespace * @param namespaceIterator */ public void addStack(Iterator<Namespace> namespaceIterator) { // final Map<String, String> prefixToNamespaceUriMap = new HashMap<String, String>(); { // if (namespaceIterator != null) { for (Namespace namespace : IterableUtils.valueOf(namespaceIterator)) { if (namespace != null) { // final String prefix = namespace.getPrefix(); final String namespaceURI = namespace.getNamespaceURI(); prefixToNamespaceUriMap.put(prefix, namespaceURI); } } } } this.prefixToNamespaceUriMapList.add(0, prefixToNamespaceUriMap); } /** * @param namespace */ public void addNamespaceToCurrentNamespaceStack(Namespace namespace) { // final Map<String, String> prefixToNamespaceUriMap = ListUtils .firstElement(this.prefixToNamespaceUriMapList); if (prefixToNamespaceUriMap != null) { // final String prefix = namespace.getPrefix(); final String namespaceUri = namespace.getNamespaceURI(); prefixToNamespaceUriMap.put(prefix, namespaceUri); } } /** * This should be called for each {@link EndElement} * * @see #addStack(Iterator) */ public void removeStack() { ListUtils.removeFirst(this.prefixToNamespaceUriMapList); } /** * Returns true if the given {@link Namespace} is already declared * * @param prefix * @param namespaceUri * @return */ public boolean hasDeclaredNamespace(String prefix, String namespaceUri) { // boolean retval = false; // for (Map<String, String> prefixToNamespaceUriMap : this.prefixToNamespaceUriMapList) { // if (StringUtils.equalsIgnoreCase(namespaceUri, prefixToNamespaceUriMap.get(prefix))) { retval = true; break; } } // return retval; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("NamespaceStack [prefixToNamespaceUriMapList="); builder.append(this.prefixToNamespaceUriMapList); builder.append("]"); return builder.toString(); } } /** * Controls the traversal context over the {@link XMLStreamReader} * * @author Omnaest */ private static class TraversalContextControl { /* ********************************************** Variables ********************************************** */ private final List<QName> qNameList = new ArrayList<QName>(); /* ********************************************** Classes/Interfaces ********************************************** */ /** * @see SelectionContext * @author Omnaest */ private static class SelectionContextImpl implements SelectionContext { /* ********************************************** Variables ********************************************** */ private final List<QName> qNameList; /* ********************************************** Methods ********************************************** */ /** * @see SelectionContextImpl * @param qNameList */ public SelectionContextImpl(List<QName> qNameList) { super(); this.qNameList = new ArrayList<QName>(qNameList); } @Override public List<QName> getQNameHierarchy() { return this.qNameList; } @Override public QName getQName() { return ListUtils.lastElement(this.qNameList); } } /* ********************************************** Methods ********************************************** */ /** * Returns the current {@link SelectionContext} * * @return */ public SelectionContext getCurrentSelectionContext() { return new SelectionContextImpl(this.qNameList); } /** * Adds a new {@link QName} to the internal {@link List} * * @param qName */ public void addQName(QName qName) { this.qNameList.add(qName); } /** * Deletes the last given {@link QName} */ public void reduceLastQName() { ListUtils.removeLast(this.qNameList); } } /** * Internal representation of an {@link Scope} based on a given {@link QName} * * @author Omnaest */ private static class Scope { /* ********************************************** Variables ********************************************** */ private final XMLElementSelector elementSelector; private boolean isTraversed = false; private int enclosureCount = 0; /* ********************************************** Methods ********************************************** */ /** * @param selectionContext */ public void visitStartElement(SelectionContext selectionContext) { if (this.elementSelector.selectElement(selectionContext)) { this.enclosureCount++; } } /** * @see Scope * @param qName */ public Scope(QName qName) { super(); this.elementSelector = new XMLIteratorFactory.XMLElementSelectorQNameBased(qName); } /** * @param selectionContext */ public void visitEndElement(SelectionContext selectionContext) { if (this.elementSelector.selectElement(selectionContext)) { this.enclosureCount--; if (this.enclosureCount == 0) { this.isTraversed = true; } } } /** * @return the isTraversed */ public boolean isTraversed() { return this.isTraversed; } /** * Resets the traversed state to false */ public void resetTraversedState() { this.isTraversed = false; } /** * Returns true if the current {@link Scope} has actually been entered * * @return */ public boolean hasBeenEntered() { return this.enclosureCount > 0; } } /** * Control structure for a given {@link List} of {@link Scope}s. * * @author Omnaest */ private static class ScopeControl { /* ********************************************** Variables ********************************************** */ private final List<Scope> scopeList; /* ********************************************** Methods ********************************************** */ /** * Resets any given {@link Scope} initially and begins to manage it * * @see ScopeControl * @param scopeList */ public ScopeControl(List<Scope> scopeList) { super(); this.scopeList = scopeList; // if (scopeList != null) { for (Scope scope : scopeList) { scope.resetTraversedState(); } } } /** * Returns true if the {@link ScopeControl} manages at least {@link Scope} instance * * @return */ public boolean hasScopes() { return this.scopeList != null && !this.scopeList.isEmpty(); } /** * Returns true if any internal available {@link Scope} has been traversed * * @return */ public boolean hasTraversedAnyScope() { // boolean retval = false; // if (this.scopeList != null) { for (Scope scope : this.scopeList) { if (scope.isTraversed()) { retval = true; break; } } } // return retval; } /** * @param selectionContext * {@link SelectionContext} */ public void visitStartElement(SelectionContext selectionContext) { if (this.scopeList != null) { for (Scope scope : this.scopeList) { scope.visitStartElement(selectionContext); } } } /** * @param selectionContext * {@link SelectionContext} */ public void visitEndElement(SelectionContext selectionContext) { if (this.scopeList != null) { for (Scope scope : this.scopeList) { scope.visitEndElement(selectionContext); } } } /** * Returns true if at least one {@link Scope} has been entered currently * * @return */ public boolean hasEnteredAnyScope() { // boolean retval = false; // if (this.scopeList != null) { for (Scope scope : this.scopeList) { if (scope.hasBeenEntered()) { // retval = true; break; } } } // return retval; } } /** * Barrier which * * @author Omnaest */ private static class TouchBarrier { /* ********************************************** Variables / State ********************************************** */ private final XMLElementSelector xmlElementSelector; /* ********************************************** Methods ********************************************** */ @SuppressWarnings("unused") public TouchBarrier(XMLElementSelector xmlElementSelector) { super(); this.xmlElementSelector = xmlElementSelector; } public TouchBarrier(QName qName) { super(); this.xmlElementSelector = new XMLElementSelectorQNameBased(qName); } /** * Returns true if the current {@link TouchBarrier} matches a given {@link SelectionContext} * * @param selectionContext * @return */ protected boolean matches(SelectionContext selectionContext) { return this.xmlElementSelector.selectElement(selectionContext); } } /** * Controls structure for any given touch barrier {@link QName} * * @author Omnaest */ private static class TouchBarrierControl { /* ********************************************** Variables / State ********************************************** */ private final List<TouchBarrier> touchBarrierList; private final ExceptionHandler exceptionHandler; /* ********************************************** Methods ********************************************** */ /** * @see TouchBarrierControl * @param touchBarrierList * {@link List} of {@link TouchBarrier}s * @param exceptionHandler * {@link ExceptionHandler} */ public TouchBarrierControl(List<TouchBarrier> touchBarrierList, ExceptionHandler exceptionHandler) { super(); this.touchBarrierList = touchBarrierList; this.exceptionHandler = exceptionHandler; } /** * Checks if any of the internal {@link TouchBarrier}s are matching the next {@link XMLEvent} of any {@link XMLEventReader}. * To retrieve the next element use {@link XMLEventReader#peek()}, which does not remove the {@link XMLEvent} from the * {@link XmlReader}s stream. * * @param xmlEventPeek * {@link XMLEvent} * @param qNameHierarchy * @return true if the next element will touch any barrier */ public boolean isAnyBarrierTouched(XMLEvent xmlEventPeek, final List<QName> qNameHierarchy) { // boolean retval = false; // if (xmlEventPeek != null && xmlEventPeek.isStartElement() && this.touchBarrierList != null && !this.touchBarrierList.isEmpty()) { try { // final QName qName = xmlEventPeek.asStartElement().getName(); final SelectionContext selectionContext = new SelectionContext() { @Override public List<QName> getQNameHierarchy() { return qNameHierarchy; } @Override public QName getQName() { return qName; } }; for (TouchBarrier touchBarrier : this.touchBarrierList) { if (touchBarrier != null) { // final boolean matches = touchBarrier.matches(selectionContext); if (matches) { retval = true; break; } } } } catch (Exception e) { this.exceptionHandler.handleException(e); } } // return retval; } } /* ********************************************** Methods ********************************************** */ /** * Note: the {@link XMLIteratorFactory} does not close the underlying {@link InputStream} * * @see XMLIteratorFactory * @param inputStream * {@link InputStream} * @param exceptionHandler * {@link ExceptionHandler} */ public XMLIteratorFactory(final InputStream inputStream, final ExceptionHandler exceptionHandler) { // super(); // this.xmlEventTransformerList = new ArrayList<XMLEventTransformer>(); this.exceptionHandler = ObjectUtils.defaultIfNull(exceptionHandler, new ExceptionHandlerIgnoring()); this.xmlInstanceContextFactory = XMLIteratorFactory.XML_INSTANCE_CONTEXT_FACTORY_JAVA_STAX_DEFAULT; this.xmlEventReaderCache = this.newXmlEventReaderCache(inputStream, exceptionHandler); this.scopeList = new ArrayList<Scope>(); this.touchBarrierList = new ArrayList<TouchBarrier>(); this.traversalContextControl = new TraversalContextControl(); } private CachedElement<XMLEventReader> newXmlEventReaderCache(final InputStream inputStream, final ExceptionHandler exceptionHandler) { return new CachedElement<XMLEventReader>(new ValueResolver<XMLEventReader>() { @Override public XMLEventReader resolveValue() { // XMLEventReader retval = null; // try { // final XMLInputFactory xmlInputFactory = XMLIteratorFactory.this.xmlInstanceContextFactory .newXmlInputFactory(); Assert.isNotNull(xmlInputFactory, "xmlInputFactory must not be null"); // retval = xmlInputFactory.createXMLEventReader(inputStream); } catch (Exception e) { exceptionHandler.handleException(e); } // return retval; } }); } /** * Similar to {@link #XMLIteratorFactory(InputStream, ExceptionHandler)} using an {@link ExceptionHandlerIgnoring} * * @see XMLIteratorFactory * @param inputStream * {@link InputStream} */ public XMLIteratorFactory(InputStream inputStream) { // this(inputStream, new ExceptionHandlerIgnoring()); } /** * @see XMLIteratorFactory * @param xmlEventReaderCache * @param xmlInstanceContextFactory * @param xmlTransformerList * @param exceptionHandler * @param scopeList * @param touchBarrierList * @param jaxbTypeContentConverterFactory * @param traversalControl */ private XMLIteratorFactory(CachedElement<XMLEventReader> xmlEventReaderCache, XMLInstanceContextFactory xmlInstanceContextFactory, List<XMLEventTransformer> xmlTransformerList, ExceptionHandler exceptionHandler, List<Scope> scopeList, List<TouchBarrier> touchBarrierList, TraversalContextControl traversalContextControl, JAXBTypeContentConverterFactory jaxbTypeContentConverterFactory) { super(); this.xmlEventReaderCache = xmlEventReaderCache; this.xmlEventTransformerList = xmlTransformerList; this.exceptionHandler = exceptionHandler; this.scopeList = scopeList; this.touchBarrierList = touchBarrierList; this.traversalContextControl = traversalContextControl; this.xmlInstanceContextFactory = xmlInstanceContextFactory; this.jaxbTypeContentConverterFactory = jaxbTypeContentConverterFactory; } /** * This adds an {@link XMLEventTransformer} which does lower case the xml tag and attribute names * * @return new {@link XMLIteratorFactory} instance */ public XMLIteratorFactory doLowerCaseXMLTagAndAttributeNames() { // final XMLTagAndAttributeNameTransformer xmlTagAndAttributeNameTransformer = new XMLTagAndAttributeNameTransformerLowerCase(); final XMLEventTransformer xmlEventTransformer = new XMLEventTransformerForTagAndAttributeName( xmlTagAndAttributeNameTransformer); return this.doAddXMLEventTransformer(xmlEventTransformer); } /** * This adds an {@link XMLEventTransformer} which does remove all {@link Namespace} declarations on any xml tag and attribute * * @return new {@link XMLIteratorFactory} instance */ public XMLIteratorFactory doRemoveNamespacesForXMLTagAndAttributeNames() { // final XMLTagAndAttributeNameTransformer xmlTagAndAttributeNameTransformer = new XMLTagAndAttributeNameTransformerRemoveNamespace(); final XMLEventTransformer xmlEventTransformer = new XMLEventTransformerForTagAndAttributeName( xmlTagAndAttributeNameTransformer); return this.doAddXMLEventTransformer(xmlEventTransformer); } /** * This adds an {@link XMLEventTransformer} which does upper case the xml tag and attribute names * * @return new {@link XMLIteratorFactory} instance */ public XMLIteratorFactory doUpperCaseXMLTagAndAttributeNames() { // final XMLTagAndAttributeNameTransformer xmlTagAndAttributeNameTransformer = new XMLTagAndAttributeNameTransformerUpperCase(); final XMLEventTransformer xmlEventTransformer = new XMLEventTransformerForTagAndAttributeName( xmlTagAndAttributeNameTransformer); return this.doAddXMLEventTransformer(xmlEventTransformer); } /** * @param xmlEventTransformer * @return new {@link XMLIteratorFactory} instance if the given {@link XMLEventTransformer} is not null otherwise this instance */ public XMLIteratorFactory doAddXMLEventTransformer(XMLEventTransformer xmlEventTransformer) { // XMLIteratorFactory retval = this; // if (xmlEventTransformer != null) { retval = new XMLIteratorFactory(this.xmlEventReaderCache, this.xmlInstanceContextFactory, ListUtils.addToNewList(this.xmlEventTransformerList, xmlEventTransformer), this.exceptionHandler, this.scopeList, this.touchBarrierList, this.traversalContextControl, this.jaxbTypeContentConverterFactory); } // return retval; } /** * Returns a new {@link XMLIteratorFactory} instance with the configuration of this one but holding an additional xml tag scope * restriction. A scope restriction means that the internal stream is forwarded until it finds the beginning of a xml tag and * which is stopped when the end of the same xml tag is reached. <br> * <br> * Be aware of the fact that scopes can be nested. To begin reading elements only one of all the scopes have to be entered. To * stop an {@link Iterator} only one of the scopes has to be left. <br> * So it is quite possible that even if one scope is left an enclosing scope is still valid, which means the selection matching * is immediately active again until the enclosing scope is now left. <br> * <br> * After a scope has been passed it is possible to iterate further by creating a new {@link Iterator}. * * @param tagName * {@link QName} * @return */ public XMLIteratorFactory doAddXMLTagScope(QName tagName) { // XMLIteratorFactory retval = this; // if (tagName != null) { // final Scope scope = new Scope(tagName); retval = new XMLIteratorFactory(this.xmlEventReaderCache, this.xmlInstanceContextFactory, this.xmlEventTransformerList, this.exceptionHandler, ListUtils.addToNewList(this.scopeList, scope), this.touchBarrierList, this.traversalContextControl, this.jaxbTypeContentConverterFactory); } // return retval; } /** * Returns a new {@link XMLIteratorFactory} instance with the configuration of this one but holding an additional xml tag touch * barrier restriction. A touch barrier restriction means that the internal stream is validated in advance if the next start * element will match the given xml tag. If this is the case, the traversal is stopped and the next element keeps unread, so * that any further attempt to create a new {@link Iterator} of any kind will use the still remaining element of the touch * barrier. <br> * <br> * * @param tagName * {@link QName} * @return new {@link XMLIteratorFactory} instance */ public XMLIteratorFactory doAddXMLTagTouchBarrier(QName tagName) { // XMLIteratorFactory retval = this; // if (tagName != null) { // final TouchBarrier touchBarrier = new TouchBarrier(tagName); retval = new XMLIteratorFactory(this.xmlEventReaderCache, this.xmlInstanceContextFactory, this.xmlEventTransformerList, this.exceptionHandler, this.scopeList, ListUtils.addToNewList(this.touchBarrierList, touchBarrier), this.traversalContextControl, this.jaxbTypeContentConverterFactory); } // return retval; } /** * If given true as parameter the returned {@link Iterator} instances will use {@link ThreadLocal} states. This results in the * case that if one {@link Thread} resolves true for the {@link Iterator#hasNext()} function, the respective value will be * locked to this {@link Thread}. Another {@link Thread} would e.g. then get false for the {@link Iterator#hasNext()} function * even if the first {@link Thread} did not yet pulled the explicit value by invoking the {@link Iterator#next()} method.<br> * <br> * Even if this circumstance allows to share any created {@link Iterator} instance between threads without loosing the contract * of the {@link Iterator}, it must be ensured that any {@link Thread} which requests {@link Iterator#hasNext()} do actually * pull the value. Otherwise the internally backed value gets lost with the dereferencing of the {@link ThreadLocal}. * * @param threadsafe * @return this */ public XMLIteratorFactory doCreateThreadsafeIterators(boolean threadsafe) { // if (threadsafe) { this.accessorFactory = this.THREADLOCAL_BASED_ACCESSOR_FACTORY; } else { this.accessorFactory = this.SIMPLE_ACCESSOR_FACTORY; } // return this; } /** * New {@link Iterator} which returns xml content chunks for all xml tags matching the given {@link QName} <br> * <br> * Performance is fast with about <b>10000 elements per second</b> beeing processed * * @see #newIterator(QName, ElementConverter) * @param qName * {@link QName} * @return */ public Iterator<String> newIterator(final QName qName) { // return newIterator(qName, new ElementConverterIdentitiyCast<String, String>()); } /** * New {@link Iterator} which returns {@link Map} entities each based on a single content chunk which are produced for all xml * tags matching the given {@link QName} <br> * <br> * Performance is medium to slow with about <b>1000 elements per second</b> beeing processed. <br> * <br> * For details how xml content is transformed to a {@link Map} instance see {@link XMLNestedMapConverter} * * @see XMLNestedMapConverter * @see #newIterator(QName, ElementConverter) * @param qName * {@link QName} * @return */ public Iterator<Map<String, Object>> newIteratorMapBased(final QName qName) { // final ElementConverter<String, Map<String, Object>> elementConverter = new ElementConverter<String, Map<String, Object>>() { @SuppressWarnings("unchecked") @Override public Map<String, Object> convert(String element) { // final Map<String, Object> mapFromXML = new XMLNestedMapConverter() .setExceptionHandler(XMLIteratorFactory.this.exceptionHandler) .setXmlInstanceContextFactory(XMLIteratorFactory.this.xmlInstanceContextFactory) .newMapFromXML(element); final Entry<String, Object> firstEntry = MapUtils.firstEntry(mapFromXML); final Object value = firstEntry != null ? firstEntry.getValue() : null; return (Map<String, Object>) (value instanceof Map ? value : null); } }; return newIterator(qName, elementConverter); } /** * Similar to {@link #newIterator(QName)} but allows to specify an additional {@link ElementConverter} which post processes the * extracted xml chunks * * @param qName * {@link QName} * @param elementConverter * {@link ElementConverter} * @return */ public <E> Iterator<E> newIterator(final QName qName, ElementConverter<String, E> elementConverter) { // final XMLElementSelector xmlElementSelector = new XMLElementSelectorQNameBased(qName); return newIterator(xmlElementSelector, elementConverter); } /** * Selects xml parts based on {@link Class}es annotated with JAXB compliant annotations and uses JAXB to create instances of the * given type based on the data of the extracted xml chunks. <br> * <br> * Performance is slow with about <b>500 elements per second</b> beeing processed * * @param type * @return * @throws MissingXMLRootElementAnnotationException */ public <E> Iterator<E> newIterator(final Class<? extends E> type) { // final QName qName = JAXBXMLHelper.determineRootName(type); XMLElementSelector xmlElementSelector = new XMLElementSelectorQNameBased(qName); // return this.newIterator(xmlElementSelector, type); } /** * Similar to {@link #newIterator(Class)} but allows to specify a {@link XMLElementSelector} to select tags from the xml stream. * * @param xmlElementSelector * {@link XMLElementSelector} * @param type * @return */ public <E> Iterator<E> newIterator(final XMLElementSelector xmlElementSelector, final Class<? extends E> type) { // final ElementConverter<String, E> elementConverter = this.jaxbTypeContentConverterFactory .newElementConverter(type, this.exceptionHandler); return newIterator(xmlElementSelector, elementConverter); } /** * Similar to {@link #newIterator(QName, ElementConverter)} but allows to specify a more general {@link XMLElementSelector} * instead of a {@link QName} * * @param xmlElementSelector * {@link XMLElementSelector} * @param elementConverter * {@link ElementConverter} * @return */ public <E> Iterator<E> newIterator(final XMLElementSelector xmlElementSelector, final ElementConverter<String, E> elementConverter) { // final Iterator<String> iterator = newIterator(xmlElementSelector); return IteratorUtils.adapter(iterator, elementConverter); } /** * Similar to {@link #newIterator(QName)} but allows to specify a more general {@link XMLElementSelector} instead of a * {@link QName} * * @param xmlElementSelector * @return */ public Iterator<String> newIterator(final XMLElementSelector xmlElementSelector) { // Iterator<String> retval = null; // final XMLEventReader xmlEventReader = this.getXmlEventReader(); if (xmlEventReader != null && xmlElementSelector != null) { try { // final XMLOutputFactory xmlOutputFactory = this.xmlInstanceContextFactory.newXmlOutputFactory(); final XMLEventFactory xmlEventFactory = this.xmlInstanceContextFactory.newXmlEventFactory(); Assert.isNotNull(xmlOutputFactory, "xmlOutputFactory must not be null"); Assert.isNotNull(xmlEventFactory, "xmlEventFactory must not be null"); // final ScopeControl scopeControl = new ScopeControl(this.scopeList); final TouchBarrierControl touchBarrierControl = new TouchBarrierControl(this.touchBarrierList, this.exceptionHandler); final Accessor<String> accessor = this.newAccessor(); // retval = new XMLIterator(accessor, scopeControl, this.exceptionHandler, xmlEventReader, xmlEventFactory, this.encoding, this.traversalContextControl, xmlElementSelector, xmlOutputFactory, touchBarrierControl, this.xmlEventTransformerList); } catch (Exception e) { this.exceptionHandler.handleException(e); } } // return retval; } /** * Returns a new instance of an {@link Accessor} using the internal {@link #accessorFactory}. If the {@link #accessorFactory} is * null it will be set to the {@link #SIMPLE_ACCESSOR_FACTORY}. * * @return */ private Accessor<String> newAccessor() { // if (this.accessorFactory == null) { this.accessorFactory = this.SIMPLE_ACCESSOR_FACTORY; } // return this.accessorFactory.newInstance(); } /** * Sets the encoding. Default is {@value #DEFAULT_ENCODING} * * @param encoding * the encoding to set * @return this */ public XMLIteratorFactory setEncoding(String encoding) { this.encoding = encoding; return this; } /** * Closes the internal {@link XMLEventReader} which closes all iterators immediately */ public XMLIteratorFactory close() { // try { this.getXmlEventReader().close(); } catch (XMLStreamException e) { if (this.exceptionHandler != null) { this.exceptionHandler.handleException(e); } } // return this; } /** * Returns the {@link XMLEventReader} from the {@link #xmlEventReaderCache} * * @return */ private XMLEventReader getXmlEventReader() { return this.xmlEventReaderCache.getValue(); } /** * Allows to set an alternative {@link XMLInstanceContextFactory}, e.g. to replace the current java default stax implementation * by another one like Staxon or Jettison for JSON * * @see #XML_INSTANCE_CONTEXT_FACTORY_JAVA_STAX_DEFAULT * @param xmlInstanceContextFactory * {@link XMLInstanceContextFactory} * @return this */ public XMLIteratorFactory setXmlInstanceContextFactory(XMLInstanceContextFactory xmlInstanceContextFactory) { this.xmlInstanceContextFactory = xmlInstanceContextFactory; return this; } /** * Allows to set another {@link JAXBTypeContentConverterFactory} which is used to convert xml content to instances of JAXB based * types. See {@link #newIterator(Class)}. * * @see #DEFAULT_JAXB_TYPE_CONTENT_CONVERTER_FACTORY * @param jaxbTypeContentConverterFactory * {@link JAXBTypeContentConverterFactory} * @return this */ public XMLIteratorFactory setJAXBTypeContentConverterFactory( JAXBTypeContentConverterFactory jaxbTypeContentConverterFactory) { this.jaxbTypeContentConverterFactory = jaxbTypeContentConverterFactory; return this; } }