ESalesParser.java
package ejava.projects.esales.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 ESalesParser {
@SuppressWarnings("unused")
private static final Logger log = LoggerFactory.getLogger(ESalesParser.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 ESalesParser(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;
@SuppressWarnings("rawtypes")
@Override
public Callable<?> resolve(final String id, Class targetType) throws SAXException {
return new Callable() {
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 element
* @return
* @throws XMLStreamException
* @throws JAXBException
*/
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);
}
}