/* * Created on 27/apr/2010 * * Copyright 2010 by Andrea Vacondio (andrea.vacondio@gmail.com). * * This file is part of the Sejda source code * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.sejda.core.context; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.sejda.common.XMLUtils.nullSafeGetBooleanAttribute; import static org.sejda.common.XMLUtils.nullSafeGetStringAttribute; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.SchemaFactory; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.commons.io.IOUtils; import org.sejda.core.Sejda; import org.sejda.core.notification.strategy.AsyncNotificationStrategy; import org.sejda.core.notification.strategy.NotificationStrategy; import org.sejda.core.notification.strategy.SyncNotificationStrategy; import org.sejda.model.exception.ConfigurationException; import org.sejda.model.parameter.base.TaskParameters; import org.sejda.model.task.Task; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * Retrieves the configuration from the input xml stream * * @author Andrea Vacondio * */ final class XmlConfigurationStrategy implements ConfigurationStrategy { private static final String ROOT_NODE = "/sejda"; private static final String VALIDATION_ATTRIBUTENAME = "validation"; private static final String IGNORE_XML_CONFIG_VALIDATION_ATTRIBUTENAME = "ignore_xml_config"; private static final String NOTIFICATION_XPATH = "/notification"; private static final String NOTIFICATION_ASYNC_ATTRIBUTENAME = "async"; private static final String TASKS_XPATH = "/tasks/task"; private static final String TASK_PARAM_ATTRIBUTENAME = "parameters"; private static final String TASK_VALUE_ATTRIBUTENAME = "task"; private static final String DEFAULT_SEJDA_CONFIG = "sejda.xsd"; private XPathFactory xpathFactory = XPathFactory.newInstance(); private Class<? extends NotificationStrategy> notificationStrategy; @SuppressWarnings("rawtypes") private Map<Class<? extends TaskParameters>, Class<? extends Task>> tasks; private boolean validation = false; private boolean ignoreXmlConfig = true; /** * Creates an instance initialized with the given input stream. The stream is not closed. * * @param input * stream to the input xml configuration file * @throws ConfigurationException * in case of error parsing the input stream */ private XmlConfigurationStrategy(InputStream input) throws ConfigurationException { initializeFromInputStream(input); } private void initializeFromInputStream(InputStream input) throws ConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { initializeSchemaValidation(factory); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(input); notificationStrategy = getNotificationStrategy(document); tasks = getTasksMap(document); validation = getValidation(document); ignoreXmlConfig = getIgnoreXmlConfig(document); } catch (IOException | SAXException e) { throw new ConfigurationException(e); } catch (ParserConfigurationException | XPathExpressionException e) { throw new ConfigurationException("Unable to create DocumentBuilder.", e); } } private void initializeSchemaValidation(DocumentBuilderFactory factory) throws SAXException { if (Boolean.getBoolean(Sejda.PERFORM_SCHEMA_VALIDATION_PROPERTY_NAME)) { SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); factory.setSchema(schemaFactory.newSchema(new Source[] { new StreamSource( Thread.currentThread().getContextClassLoader().getResourceAsStream(DEFAULT_SEJDA_CONFIG)) })); factory.setNamespaceAware(true); } } @Override public Class<? extends NotificationStrategy> getNotificationStrategy() { return notificationStrategy; } @Override @SuppressWarnings("rawtypes") public Map<Class<? extends TaskParameters>, Class<? extends Task>> getTasksMap() { return tasks; } @Override public boolean isValidation() { return validation; } @Override public boolean isIgnoreXmlConfiguration() { return ignoreXmlConfig; } @SuppressWarnings("rawtypes") private Map<Class<? extends TaskParameters>, Class<? extends Task>> getTasksMap(Document document) throws ConfigurationException, XPathExpressionException { Map<Class<? extends TaskParameters>, Class<? extends Task>> retMap = new HashMap<>(); NodeList nodes = (NodeList) xpathFactory.newXPath().evaluate(ROOT_NODE + TASKS_XPATH, document, XPathConstants.NODESET); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); Class<? extends TaskParameters> paramClass = getClassFromNode(node, TASK_PARAM_ATTRIBUTENAME, TaskParameters.class); Class<? extends Task> taksClass = getClassFromNode(node, TASK_VALUE_ATTRIBUTENAME, Task.class); retMap.put(paramClass, taksClass); } return retMap; } /** * Retrieves the value of the input xpath in the given node, creates a Class object and performs a check to ensure that the input assignableInterface is assignable by the * created Class object. * * @param <T> * * @param node * @param attributeName * @param assignableInterface * @return the retrieved class. * @throws ConfigurationException */ private <T> Class<? extends T> getClassFromNode(Node node, String attributeName, Class<T> assignableInterface) throws ConfigurationException { String attributeValue = nullSafeGetStringAttribute(node, attributeName); if (isNotBlank(attributeValue)) { Class<?> clazz; try { clazz = Class.forName(attributeValue.trim()); } catch (ClassNotFoundException e) { throw new ConfigurationException(String.format("Unable to find the configured %s", attributeValue), e); } if (assignableInterface.isAssignableFrom(clazz)) { return clazz.asSubclass(assignableInterface); } throw new ConfigurationException( String.format("The configured %s is not a subtype of %s", clazz, assignableInterface)); } throw new ConfigurationException(String.format("Missing %s configuration parameter.", attributeName)); } /** * Given a document, search for the notification strategy configuration and returns the configured strategy or the default one if nothing is configured. * * @param document * @return the class extending {@link NotificationStrategy} configured. * @throws XPathExpressionException */ private Class<? extends NotificationStrategy> getNotificationStrategy(Document document) throws XPathExpressionException { Node node = (Node) xpathFactory.newXPath().evaluate(ROOT_NODE + NOTIFICATION_XPATH, document, XPathConstants.NODE); if (nullSafeGetBooleanAttribute(node, NOTIFICATION_ASYNC_ATTRIBUTENAME)) { return AsyncNotificationStrategy.class; } return SyncNotificationStrategy.class; } private boolean getValidation(Document document) throws XPathExpressionException { Node node = (Node) xpathFactory.newXPath().evaluate(ROOT_NODE, document, XPathConstants.NODE); return nullSafeGetBooleanAttribute(node, VALIDATION_ATTRIBUTENAME); } private boolean getIgnoreXmlConfig(Document document) throws XPathExpressionException { Node node = (Node) xpathFactory.newXPath().evaluate(ROOT_NODE, document, XPathConstants.NODE); return nullSafeGetBooleanAttribute(node, IGNORE_XML_CONFIG_VALIDATION_ATTRIBUTENAME, true); } /** * static factory method. * * @param provider * provider for the configuration stream. * @return the new instance. * @throws ConfigurationException */ static XmlConfigurationStrategy newInstance(ConfigurationStreamProvider provider) throws ConfigurationException { InputStream stream = null; try { stream = provider.getConfigurationStream(); return new XmlConfigurationStrategy(stream); } finally { IOUtils.closeQuietly(stream); } } }