package org.atomhopper.util.config.jaxb;
import org.atomhopper.util.config.AbstractConfigurationParser;
import org.atomhopper.util.config.ConfigurationParserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.IOException;
import java.net.URL;
public final class JAXBConfigurationParser<T> extends AbstractConfigurationParser<T> {
private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
private static final Logger LOG = LoggerFactory.getLogger(JAXBConfigurationParser.class);
private final JAXBContext jaxbContext;
private Schema validationSchema;
public JAXBConfigurationParser(Class<T> configClass, Class<?>... objectFactories) {
super(configClass);
validationSchema = null;
try {
jaxbContext = JAXBContext.newInstance(objectFactories);
} catch (JAXBException jaxbex) {
final String message = "Failed to create the JAXB context required for configuration marshalling";
LOG.error(message);
throw new ConfigurationParserException(message, jaxbex);
}
}
/**
* Reads in an arbitrary length array of urls and creates a combined schema
* from them.
*
* Note: Order matters! Base schemas should always be loaded first.
*
* @param schemaUrls
* @throws IOException
* @throws SAXException
*/
public void enableValidation(URL... schemaUrls) throws IOException, SAXException {
//This may be inefficient for a large number of schemas since I imagine
//each stream source holds onto a FD until its internal stream is done
//being read
final StreamSource[] streamSourceArray = new StreamSource[schemaUrls.length];
for (int i = 0; i < schemaUrls.length; i++) {
streamSourceArray[i] = new StreamSource(schemaUrls[i].openStream());
}
validationSchema = SCHEMA_FACTORY.newSchema(streamSourceArray);
}
private Unmarshaller createUnmarshaller() throws JAXBException {
final Unmarshaller freshUnmarhsaller = jaxbContext.createUnmarshaller();
if (validationSchema != null) {
freshUnmarhsaller.setSchema(validationSchema);
}
return freshUnmarhsaller;
}
@Override
protected T readConfiguration() {
T rootConfigElement;
try {
final Object unmarshaledObj = createUnmarshaller().unmarshal(getConfigurationResource().getInputStream());
if (getConfigurationClass().isInstance(unmarshaledObj)) {
rootConfigElement = (T) unmarshaledObj;
} else if (unmarshaledObj instanceof JAXBElement) {
rootConfigElement = ((JAXBElement<T>) unmarshaledObj).getValue();
} else {
throw new ConfigurationParserException("Failed to read config and no exception was thrown. Potential bug. Please report this.");
}
} catch (UnmarshalException mue) {
throw new ConfigurationParserException("Your configuration may be malformed. Please review it and make sure it validates correctly. Reason: "
+ mue.getMessage(), mue);
} catch (Exception ex) {
throw new ConfigurationParserException("Failed to read the configuration. Reason: "
+ "" + ex.getMessage()
+ " - pump cause for more details", ex);
}
return rootConfigElement;
}
}