/**
* Copyright 2013 MIR@MU Project
*
* 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 cz.muni.fi.mir.mathmlcanonicalization;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Properties;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLResolver;
import javax.xml.validation.SchemaFactory;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.XMLReaders;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
/**
* Global settings shared among all instances.
*
* @author mato
*/
public class Settings {
private static final Logger log = Logger.getLogger(Settings.class.getName());
// thread locals which allow creation of factories only once per thread
private static final ThreadLocal<XMLInputFactory> xmlInputFactory = new ThreadLocal<XMLInputFactory>() {
protected XMLInputFactory initialValue() {
return createXmlInputFactory();
}
};
private static final ThreadLocal<XMLInputFactory> defaultXmlInputFactory = new ThreadLocal<XMLInputFactory>() {
protected XMLInputFactory initialValue() {
return XMLInputFactory.newInstance();
}
};
private static final ThreadLocal<XMLOutputFactory> xmlOutputFactory = new ThreadLocal<XMLOutputFactory>() {
protected XMLOutputFactory initialValue() {
return XMLOutputFactory.newInstance();
}
};
private static final ThreadLocal<DocumentBuilderFactory> documentBuilderFactory = new ThreadLocal<DocumentBuilderFactory>() {
protected DocumentBuilderFactory initialValue() {
return DocumentBuilderFactory.newInstance();
}
};
private static final ThreadLocal<SchemaFactory> xmlSchemaFactory = new ThreadLocal<SchemaFactory>() {
protected SchemaFactory initialValue() {
return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
}
};
/**
* Path to the property file with canonicalizer settings.
*/
private static final String PROPERTIES_FILENAME = "settings.properties";
/**
* Name of the property containing path to the MathML DTD
*/
private static final String XHTMLPlusMATHMLPlusSVGDTD = "dtdXHTMLPlusMathMLPlusSVG";
private static final Properties PROPERTIES = readConfiguration();
/**
* Gets given global property from {@link
* cz.muni.fi.mir.mathmlcanonicalization.Settings#PROPERTIES_FILENAME}
*
* @param key property name
* @return property value (never null)
* @throws IllegalArgumentException when property not set
*/
public static String getProperty(String key) {
if (key == null) {
throw new NullPointerException("key");
}
final String property = PROPERTIES.getProperty(key);
if (property == null) {
throw new IllegalArgumentException("Property '" + key + "' not set");
}
return property;
}
/**
* Finds out if the global property is set
*
* @param key property name
* @return true if property is set, false otherwise
*/
public static boolean isProperty(String key) {
if (key == null) {
throw new NullPointerException("key");
}
return PROPERTIES.getProperty(key) != null;
}
/**
* Sets given global property
*
* @param key property name
* @param value property value
*/
public static void setProperty(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
PROPERTIES.put(key, value);
}
/**
* Sets properties desired for MathML normalization purpose
* NB: this method creates factory only once per thread
* @return initialized XMLInputFactory instance
*/
public static XMLInputFactory setupXMLInputFactory() {
return xmlInputFactory.get();
}
/**
* Returns XMLInputFactory instance with default configuration.
* NB: setupXMLInputFactory returns different factory customized for MathML
* NB: this method creates factory only once per thread
*/
public static XMLInputFactory defaultXmlInputFactory() {
return defaultXmlInputFactory.get();
}
/**
* Returns XMLOutputFactory instance with default configuration.
* NB: this method creates factory only once per thread
*/
public static XMLOutputFactory xmlOutputFactory() {
return xmlOutputFactory.get();
}
/**
* Returns DocumentBuilderFactory instance with default configuration.
* NB: this method creates factory only once per thread
*/
public static DocumentBuilderFactory documentBuilderFactory() {
return documentBuilderFactory.get();
}
/**
* Returns SchemaFactory instance dedicated to XML W3C Schema.
* NB: this method creates factory only once per thread
*/
public static SchemaFactory xmlSchemaFactory() {
return xmlSchemaFactory.get();
}
private static XMLInputFactory createXmlInputFactory() throws FactoryConfigurationError {
final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
inputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
inputFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, true);
inputFactory.setProperty(XMLInputFactory.RESOLVER, new XMLResolver() {
@Override
public Object resolveEntity(String publicID, String systemID, String baseURI, String namespace) {
if (systemID.endsWith("dtd")) {
return getStreamFromProperty(XHTMLPlusMATHMLPlusSVGDTD);
}
return null;
}
});
return inputFactory;
}
/**
* Sets properties desired for MathML normalization purpose
*
* @return initialized SAXBuilder instance
*/
public static SAXBuilder setupSAXBuilder() {
final SAXBuilder builder = new SAXBuilder();
builder.setXMLReaderFactory(XMLReaders.NONVALIDATING);
builder.setFeature("http://xml.org/sax/features/validation", false);
builder.setFeature("http://xml.org/sax/features/external-general-entities", true);
builder.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", true);
builder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", true);
builder.setEntityResolver(new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId) {
if (publicId.equalsIgnoreCase("-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN")
|| publicId.equalsIgnoreCase("-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN")
|| systemId.endsWith("xhtml-math11-f.dtd")) {
return new InputSource( getStreamFromProperty(XHTMLPlusMATHMLPlusSVGDTD) );
}
return null;
}
});
return builder;
}
/**
* Returns URL of classpath resource defined by specified property
*/
public static URL getResourceFromProperty(String property) {
String resource = getProperty(property);
URL result = Settings.class.getResource(resource);
if (result == null) {
throw new ConfigError("Classpath resource '" + resource + "' defined by property '" + property
+ " does not exist");
}
return result;
}
/**
* Returns stream of classpath resource defined by specified property
*/
public static InputStream getStreamFromProperty(String property) {
try {
return getResourceFromProperty(property).openStream();
} catch (IOException e) {
throw new ConfigError("Classpath resource resource defined by property '" + property
+ " could not be read", e);
}
}
private Settings() {
assert false;
}
private static Properties readConfiguration() throws ConfigError {
Properties result = new Properties();
final InputStream resourceAsStream = Settings.class.getResourceAsStream(PROPERTIES_FILENAME);
if (resourceAsStream == null) {
throw new ConfigError("cannot find property file " + PROPERTIES_FILENAME);
}
try {
result.load(resourceAsStream);
} catch (IOException e) {
throw new ConfigError("Error while reading configuration");
}
log.finer("canonicalizer properties loaded succesfully");
return result;
}
}