
com.wandrell.pattern.parser.xml.ValidatedXMLFileParser Maven / Gradle / Ivy
/**
* The MIT License (MIT)
*
* Copyright (c) 2014 the original author or authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.wandrell.pattern.parser.xml;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.io.IOUtils;
import org.jdom2.Document;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.XMLReaderJDOMFactory;
import org.jdom2.input.sax.XMLReaderXSDFactory;
import org.jdom2.input.sax.XMLReaders;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import com.wandrell.pattern.conf.XMLValidationType;
import com.wandrell.pattern.parser.Parser;
/**
* Implementation of {@link Parser} for XML files, which can apply XSD or DTD
* validation files. Behind the scenes this is using the JDOM2 library SAX API
* classes.
*
* A {@code Reader} for the file is received by the parser, and then transformed
* into a {@link org.jdom2.Document Document} before being returned.
*
* It should be noted that while the most obvious use of validation is verifying
* the file, it can also be used to apply default values to it.
*
* This parser is meant for those cases where validation is needed. If you don't
* need it, think about using {@code XMLFileParser}, which may be faster.
*
* @author Bernardo Martínez Garrido
*/
public final class ValidatedXMLFileParser implements Parser {
/**
* Builder to transform the {@code Reader} into a {@code Document}.
*
* It is lazily instantiated.
*/
private SAXBuilder builder;
/**
* Flag indicating if the validation should be set on the builder or not.
*
* This changes to {@code true} when the validation data changes, and to
* {@code false} when the builder is created.
*/
private Boolean valChanged = true;
/**
* {@code Reader} for the validation file.
*
* It will be used just once, when initializing the validation factory. So
* it doesn't matter if it can be read multiple times or not.
*/
private Reader valData;
/**
* The type of validation being applied.
*/
private XMLValidationType valType;
/**
* Constructs a {@code ValidatedXMLFileParser} with no validation.
*/
public ValidatedXMLFileParser() {
super();
valType = XMLValidationType.NONE;
}
/**
* Constructs a {@code ValidatedXMLFileParser} with the specified
* validation.
*
* @param validationType
* the validation type to use
* @param validationFile
* reader for the validation file
*/
public ValidatedXMLFileParser(final XMLValidationType validationType,
final Reader validationFile) {
super();
checkNotNull(validationType,
"Received a null pointer as validation type");
checkNotNull(validationFile,
"Received a null pointer as validation file reader");
valType = validationType;
valData = validationFile;
}
/**
* Returns the validation type being used, or that no validation is being
* applied.
*
* @return the XML validation type being used
*/
public final XMLValidationType getValidationType() {
return valType;
}
/**
* Parses the XML file from the input into a JDOM2 {@code Document}.
*
* Validation will be applied during this process, which can cause failures
* and exceptions to be thrown.
*
* @param input
* {@code Reader} for the XML file
* @return a {@code Document} with the XML contents
* @throws JDOMException
* when an error stops the parsing
* @throws IOException
* when an IO exception stops the parsing
*/
@Override
public final Document parse(final Reader input) throws JDOMException,
IOException {
return getBuilder().build(input);
}
/**
* Sets the validation type and file to be used.
*
* @param type
* the validation type
* @param file
* reader for the validation file
*/
public final void setValidation(final XMLValidationType type,
final Reader file) {
checkNotNull(type, "Received a null pointer as validation type");
if (type != XMLValidationType.NONE) {
checkNotNull(file,
"Received a null pointer as validation file reader");
}
valType = type;
valData = file;
setValidationChanged(true);
}
/**
* Returns the {@code SAXBuilder} to be used when creating a
* {@code Document} from the parsed {@code Reader}.
*
* The builder will be created the first time it is required, or if the
* validation data has been changed after the last call.
*
* @return the {@code SAXBuilder} used
*/
private final SAXBuilder getBuilder() {
if (builder == null) {
builder = new SAXBuilder();
}
if (hasValidationChanged()) {
builder = new SAXBuilder();
// If validation is being applied a factory for it is set
switch (getValidationType()) {
case XSD:
builder.setXMLReaderFactory(getXSDValidationFactory());
break;
case DTD:
builder.setXMLReaderFactory(XMLReaders.DTDVALIDATING);
builder.setEntityResolver(getEntityResolver());
break;
default:
builder.setXMLReaderFactory(null);
break;
}
setValidationChanged(false);
}
return builder;
}
/**
* Returns the {@code EntityResolver} for the DTD validation process.
*
* @return the {@code EntityResolver} for the DTD validation process
*/
private final EntityResolver getEntityResolver() {
return new EntityResolver() {
/**
* Contents of the DTD validation file.
*
* This way it is only needed to use the reader once, no matter how
* many times the validation is applied.
*
* This will be initialized the first, and only, time the validation
* file is read.
*/
private String dtd;
@Override
public final InputSource resolveEntity(final String publicId,
final String systemId) throws IOException {
final InputSource source;
source = new InputSource(IOUtils.toInputStream(
getDTDFileContents(), "UTF-8"));
source.setPublicId(publicId);
source.setSystemId(systemId);
return source;
}
/**
* Returns the DTD validation file contents.
*
* The first time this file is read it's contents will be stored in
* the {@code dtd} variable.
*
* This way the file is read only once, avoiding problems caused by
* the file reader being closed.
*
* @return the contents of the validation file
* @throws IOException
* when an error occurs while reading the file
*/
private final String getDTDFileContents() throws IOException {
final StringBuilder readDTD;
BufferedReader reader;
String line;
if (dtd == null) {
reader = null;
try {
reader = IOUtils.toBufferedReader(getValidationData());
readDTD = new StringBuilder();
line = reader.readLine();
while (line != null) {
readDTD.append(line);
line = reader.readLine();
}
} finally {
if (reader != null) {
reader.close();
}
}
dtd = readDTD.toString();
}
return dtd;
}
};
}
/**
* Returns the reader for the validation file.
*
* It should be noted that this is a single use reader. So the validation
* data can, and should, be parsed once only.
*
* @return the stream for the validation file
*/
private final Reader getValidationData() {
return valData;
}
/**
* Returns the factory for the XSD validation process.
*
* @return the XSD validation factory
*/
private final XMLReaderJDOMFactory getXSDValidationFactory() {
final XMLReaderJDOMFactory factoryValidation;
final Source[] sources; // Sources for the validation files
try {
sources = new Source[1];
sources[0] = new StreamSource(getValidationData());
factoryValidation = new XMLReaderXSDFactory(sources);
} catch (final JDOMException e) {
throw new RuntimeException(e);
}
return factoryValidation;
}
/**
* Indicates if the validation information has changed.
*
* @return {@code true} if the validation data has changed, {@code false}
* otherwise
*/
private final Boolean hasValidationChanged() {
return valChanged;
}
/**
* Sets the validation changed status.
*
* If validation should be set again on the builder, this can be indicated
* giving a {@code true} value to this method.
*
* @param changed
* the validation changed status
*/
private final void setValidationChanged(final Boolean changed) {
valChanged = changed;
}
}