/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package fedora.server.validation;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.log4j.Logger;
import fedora.server.errors.ObjectValidityException;
import fedora.server.errors.ServerException;
import fedora.utilities.XmlTransformUtility;
/**
* Schematron validation for fedora objects encoded in schematron schema for
* Fedora. The schematron schema (metsExtRules1-0.xml) expresses a set of rules
* using XPATH that enable us to check for things that are either not expressed
* in the METS XML schema, or that cannot be expressed with XML Schema language.
* Generally we will look for things that are requirements of Fedora objects,
* which are not requirements for METS objects in general.
*
* @author Sandy Payette
* @version $Id$
*/
public class DOValidatorSchematron {
/** Logger for this class. */
private static final Logger LOG =
Logger.getLogger(DOValidatorSchematron.class.getName());
private StreamSource rulesSource;
private StreamSource preprocessorSource;
private final StreamSource validatingStyleSheet;
private static Map<String, ByteArrayOutputStream> generatedStyleSheets =
new HashMap<String, ByteArrayOutputStream>();
/**
* Constructs a DOValidatorSchematron instance with a Schematron
* preprocessor that is provided by the calling class. This will allow the
* DOValidator module to pass in the preprocessor that is configured with
* the Fedora repository.
*
* @param schemaPath
* the URL of the Schematron schema
* @param preprocessorPath
* the location of the Schematron preprocessor
* @param phase
* the phase in the fedora object lifecycle to which validation
* should pertain. (Currently options are "ingest" and "store"
* @throws ObjectValidityException
*/
public DOValidatorSchematron(String schemaPath,
String preprocessorPath,
String phase)
throws ObjectValidityException {
validatingStyleSheet = setUp(preprocessorPath, schemaPath, phase);
}
/**
* Run the Schematron validation on a Fedora object.
*
* @param objectAsFile
* the Fedora object as a File
* @throws ServerException
*/
public void validate(File objectAsFile) throws ServerException {
validate(new StreamSource(objectAsFile));
}
/**
* Run the Schematron validation on a Fedora object.
*
* @param objectAsStream
* the Fedora object as an Inputstream
* @throws ServerException
*/
public void validate(InputStream objectAsStream) throws ServerException {
validate(new StreamSource(objectAsStream));
}
/**
* Run the Schematron validation on a Fedora object.
*
* @param objectSource
* the Fedora object as an StreamSource
* @throws ServerException
*/
public void validate(StreamSource objectSource) throws ServerException {
DOValidatorSchematronResult result = null;
try {
// Create a transformer that uses the validating stylesheet.
// Run the Schematron validation of the Fedora object and
// output results in DOM format.
TransformerFactory tfactory = XmlTransformUtility.getTransformerFactory();
Transformer vtransformer =
tfactory.newTransformer(validatingStyleSheet);
DOMResult validationResult = new DOMResult();
vtransformer.transform(objectSource, validationResult);
result = new DOValidatorSchematronResult(validationResult);
} catch (Exception e) {
LOG.error("Schematron validation failed", e);
throw new ObjectValidityException(e.getMessage());
}
if (!result.isValid()) {
String msg = null;
try {
msg = result.getXMLResult();
} catch (Exception e) {
LOG
.warn("Error getting XML result of schematron validation failure",
e);
}
throw new ObjectValidityException(msg);
}
}
/**
* Run setup to prepare for Schematron validation. This entails dynamically
* creating the validating stylesheet using the preprocessor and the schema.
*
* @param preprocessorPath
* the location of the Schematron preprocessor
* @param fedoraschemaPath
* the URL of the Schematron schema
* @param phase
* the phase in the fedora object lifecycle to which validation
* should pertain. (Currently options are "ingest" and "store")
* @return StreamSource
* @throws ObjectValidityException
*/
private StreamSource setUp(String preprocessorPath,
String fedoraschemaPath,
String phase) throws ObjectValidityException {
String key = fedoraschemaPath + "#" + phase;
ByteArrayOutputStream out =
(ByteArrayOutputStream) generatedStyleSheets.get(key);
if (out == null) {
rulesSource = fileToStreamSource(fedoraschemaPath);
preprocessorSource = fileToStreamSource(preprocessorPath);
out =
createValidatingStyleSheet(rulesSource,
preprocessorSource,
phase);
generatedStyleSheets.put(key, out);
}
return new StreamSource(new ByteArrayInputStream(out.toByteArray()));
}
/**
* Create the validating stylesheet which will be used to perform the actual
* Schematron validation. The validating stylesheet is created dynamically
* using the preprocessor stylesheet and the Schematron schema for Fedora.
* The phase is key. The stylesheet is created for the appropriate phase as
* specified in the Schematron rules schema. Valid work flow phases are
* currently "ingest" and "store." Different schematron rules apply to
* different phases of the object lifecycle. Some rules are applied when an
* object is first being ingested into the repository. Other rules apply
* before the object is put into permanent storage.
*
* @param rulesSource
* the location of the rules
* @param preprocessorSource
* the location of the Schematron preprocessor
* @param phase
* the phase in the fedora object lifecycle to which validation
* should pertain. (Currently options are "ingest" and "store"
* @return A ByteArrayOutputStream containing the stylesheet
* @throws ObjectValidityException
*/
private ByteArrayOutputStream createValidatingStyleSheet(StreamSource rulesSource,
StreamSource preprocessorSource,
String phase)
throws ObjectValidityException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
// Create a transformer for that uses the Schematron preprocessor stylesheet.
// Transform the Schematron schema (rules) into a validating stylesheet.
TransformerFactory tfactory = XmlTransformUtility.getTransformerFactory();
Transformer ptransformer =
tfactory.newTransformer(preprocessorSource);
ptransformer.setParameter("phase", phase);
ptransformer.transform(rulesSource, new StreamResult(out));
} catch (TransformerException e) {
LOG.error("Schematron validation failed", e);
throw new ObjectValidityException(e.getMessage());
}
return out;
}
/** Code based on com.jclark.xsl.sax.Driver: * */
/**
* Generates a StreamSource from a file name.
*/
static public StreamSource fileToStreamSource(String str) {
return fileToStreamSource(new File(str));
}
static public StreamSource fileToStreamSource(File file) {
String path = file.getAbsolutePath();
String fSep = System.getProperty("file.separator");
if (fSep != null && fSep.length() == 1) {
path = path.replace(fSep.charAt(0), '/');
}
if (path.length() > 0 && path.charAt(0) != '/') {
path = '/' + path;
}
try {
String url = new URL("file", null, path).toString();
return new StreamSource(url);
} catch (java.net.MalformedURLException e) {
/*
* According to the spec this could only happen if the file protocol
* were not recognized.
*/
throw new Error("unexpected MalformedURLException");
}
}
}