ELeagueParser.java

package ejava.projects.eleague.xml;

import java.io.InputStream;
import java.util.HashMap;
import java.util.concurrent.Callable;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import com.sun.xml.bind.IDResolver;

/**
 * This class will read in Java objects from a specified XML file. These objects
 * can be used to create ingest data for projects.
 */
public class ELeagueParser {
	@SuppressWarnings("unused")
	private Logger log = LoggerFactory.getLogger(ELeagueParser.class);
	protected XMLInputFactory xmlif = XMLInputFactory.newInstance();
	protected Unmarshaller um;
	protected XMLStreamReader xmlr;
	public static final String SAMPLE_FILE = "xml/eSales-10.xml";

	/**
	 * Pass in the JAXB class that represents the root node of the document and
	 * an InputStream for the document to parse.
	 * 
	 * @param rootType
	 *            - the class of the root type
	 * @param is
	 *            - am input stream with document to parse
	 * @throws JAXBException
	 * @throws XMLStreamException
	 */
	public ELeagueParser(Class<?> rootType, InputStream is)
			throws JAXBException, XMLStreamException {
		JAXBContext jaxbContext = JAXBContext.newInstance(rootType);
		um = jaxbContext.createUnmarshaller();
		xmlif = XMLInputFactory.newInstance();
		xmlr = xmlif.createXMLStreamReader(is);

		// This (anonymous) class is a near replicate of sun's DefaultIDResolver
		// except that they added a clear() of the idmap within startDocument()
		// that prevents the unmarshaller from being called multiple times.
		IDResolver idResolver = new IDResolver() {
			private HashMap<String, Object> idmap = null;

			@Override
			public Callable<?> resolve(final String id, 
					@SuppressWarnings("rawtypes") Class targetType)
					throws SAXException {
				return new Callable<Object>() {
					public Object call() throws Exception {
						if (idmap == null)
							return null;
						return idmap.get(id);
					}
				};
			}

			@Override
			public void bind(String id, Object obj) throws SAXException {
				if (idmap == null)
					idmap = new HashMap<String, Object>();
				idmap.put(id, obj);
			};
		};
		um.setProperty(IDResolver.class.getName(), idResolver);
	}

	public void setSchema(InputStream schema) throws SAXException {
		SchemaFactory sf = SchemaFactory
				.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
		Schema schemaObject = sf.newSchema(new StreamSource(schema));
		um.setSchema(schemaObject);
	}

	private boolean contains(String elements[], String localName) {
		for (String element : elements) {
			if (element.equalsIgnoreCase(localName)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * This method will return either the object or null if we hit the end of
	 * stream before getting another instance. Note that only the local-name is
	 * being used. That won't work to great when two namespaces declare a common
	 * local-name. Should be easily fixable when needed.
	 * 
	 * @param elements an vararg of element names you wish to pull from document
	 * @return The next element that matches one of the element names in elements.
	 * @throws XMLStreamException
	 * @throws JAXBException
	 */
    @SuppressWarnings("rawtypes")
	public Object getObject(String... elements) throws XMLStreamException,
			JAXBException {
		xmlr.next();
		while (xmlr.hasNext()) {
			if (xmlr.isStartElement()
					&& contains(elements, xmlr.getName().getLocalPart())) {
				Object object = um.unmarshal(xmlr);
				return (object instanceof JAXBElement) ? ((JAXBElement) object)
						.getValue() : object;
			}
			xmlr.next();
		}
		return null;
	}

	public static InputStream getSampleData() {
		return Thread.currentThread().getContextClassLoader()
				.getResourceAsStream(SAMPLE_FILE);
	}
}