/****************************************************************************/
/* File: SaxonPkgExtension.java */
/* Author: F. Georges - H2O Consulting */
/* Date: 2010-09-19 */
/* Tags: */
/* Copyright (c) 2010-2015 Florent Georges (see end of file.) */
/* ------------------------------------------------------------------------ */
package org.expath.pkg.saxon;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.expath.pkg.repo.DescriptorExtension;
import org.expath.pkg.repo.FileSystemStorage.FileSystemResolver;
import org.expath.pkg.repo.Package;
import org.expath.pkg.repo.PackageException;
import org.expath.pkg.repo.PackageInfo;
import org.expath.pkg.repo.Repository;
import org.expath.pkg.repo.Storage;
import org.expath.pkg.repo.parser.XMLStreamHelper;
/**
* Represent the extension "saxon", configured with "saxon.xml".
*
* The extension descriptor "saxon.xml" must be at the root of the package. Its
* format is as following:
*
* <pre>
* <package xmlns="http://saxon.sf.net/ns/expath-pkg">
* <jar>dir/file.jar</jar>
* <function>org.example.extension.MyFunction</function>
* <xslt>
* <import-uri>http://example.org/ns/project/style.xsl</import-uri>
* <file>stylesheet.xsl</file>
* </xslt>
* <xquery>
* <namespace>http://example.org/ns/project/lib</namespace>
* <file>query-lib.xql</file
* </xquery>
* </package>
* </pre>
*
* The elements "jar", "function", "xslt" and "xquery" are optional, repeatable,
* and can appear in any order. The element "jar" links to the JAR files, in
* the content directory, to be included in the classpath. The element "function"
* register an extension function by using its fully qualified class name. The
* class must extends the Saxon class ExtensionFunctionDefinition. If it extends
* the EXPath class EXPathFunctionDefinition, the method setConfiguration() will
* be called after it has been instantiated. The elements "xslt" and "xquery"
* add XSLT stylesheets and XQuery libraries to the package component list. They
* are useful when the components are depending on Saxon, like using some Saxon
* extension instruction or attribute.
*
* @author Florent Georges
*/
public class SaxonPkgExtension
extends DescriptorExtension
{
public SaxonPkgExtension()
{
super("saxon", "saxon.xml");
}
@Override
protected void parseDescriptor(XMLStreamReader parser, Package pkg)
throws PackageException
{
myXSHelper.ensureNextElement(parser, "package");
SaxonPkgInfo info = new SaxonPkgInfo(pkg);
try {
parser.next();
while ( parser.getEventType() == XMLStreamConstants.START_ELEMENT ) {
if ( SAXON_PKG_NS.equals(parser.getNamespaceURI()) ) {
handleElement(parser, info);
}
else {
// ignore elements not in the Saxon Pkg namespace
// TODO: FIXME: Actually ignore (pass it.)
throw new PackageException("TODO: Ignore elements in other namespace");
}
parser.next();
}
// position to </package>
parser.next();
}
catch ( XMLStreamException ex ) {
throw new PackageException("Error reading the saxon descriptor", ex);
}
pkg.addInfo(getName(), info);
}
private void handleElement(XMLStreamReader parser, SaxonPkgInfo info)
throws PackageException
, XMLStreamException
{
String local = parser.getLocalName();
if ( "jar".equals(local) ) {
String jar = myXSHelper.getElementValue(parser);
info.addJar(jar);
}
else if ( "function".equals(local) ) {
String fun = myXSHelper.getElementValue(parser);
info.addFunction(fun);
}
else if ( "library".equals(local) ) {
String lib = myXSHelper.getElementValue(parser);
info.addLibrary(lib);
}
else if ( "xslt".equals(local) ) {
Mapping m = handleMapping(parser, "import-uri");
info.addXslt(m.href, m.file);
}
else if ( "xquery".equals(local) ) {
// TODO: Handle main modules as well (with "import-uri" instead of
// "namespace").
Mapping m = handleMapping(parser, "namespace");
info.addXQuery(m.href, m.file);
}
else if ( "xslt-wrapper".equals(local) ) {
Mapping m = handleMapping(parser, "import-uri");
info.addXsltWrapper(m.href, m.file);
}
else if ( "xquery-wrapper".equals(local) ) {
// TODO: Handle main modules as well (with "import-uri" instead of
// "namespace").
Mapping m = handleMapping(parser, "namespace");
info.addXQueryWrapper(m.href, m.file);
}
else {
throw new PackageException("Unknown Saxon component type: " + local);
}
}
private Mapping handleMapping(XMLStreamReader parser, String uri_name)
throws PackageException, XMLStreamException
{
myXSHelper.ensureNextElement(parser, uri_name);
String href = myXSHelper.getElementValue(parser);
myXSHelper.ensureNextElement(parser, "file");
String file = myXSHelper.getElementValue(parser);
// position to </...> (xslt, xquery, xslt-wrapper or xquery-wrapper)
parser.next();
return new Mapping(href, file);
}
@Override
public void install(Repository repo, Package pkg)
throws PackageException
{
// first init and parse the descriptor
init(repo, pkg);
// get the freshly created info object
SaxonPkgInfo info = getInfo(pkg);
if ( info == null ) {
// not a Saxon extension
return;
}
// if there are no JAR, nothing to do
if ( ! info.hasJars() ) {
return;
}
setupClasspath(pkg, info);
}
private SaxonPkgInfo getInfo(Package pkg)
throws PackageException
{
PackageInfo info = pkg.getInfo(getName());
if ( info == null ) {
return null;
}
if ( ! (info instanceof SaxonPkgInfo) ) {
throw new PackageException("Not a Saxon-specific package info: " + info.getClass());
}
return (SaxonPkgInfo) info;
}
private void setupClasspath(Package pkg, SaxonPkgInfo info)
throws PackageException
{
File classpath = createClasspathFile(pkg);
if ( classpath == null ) {
return;
}
Storage.PackageResolver res = pkg.getResolver();
try {
FileWriter out = new FileWriter(classpath);
for ( String jar : info.getJars() ) {
try {
// ignore the result, just to check if it exists
res.resolveComponent(jar);
}
catch ( Storage.NotExistException ex ) {
throw new PackageException("The Saxon descriptor refers to a JAR file that does not exist: " + jar, ex);
}
// add it to classpath.txt
out.write(jar);
out.write("\n");
}
out.close();
}
catch ( IOException ex ) {
throw new PackageException("Error writing the Saxon classpath file: " + classpath, ex);
}
}
/**
* Create and return the classpath, return null if already exists.
*
* Throw an exception if the storage is not on the file system, if the
* parent directory exists but is not in fact a directory, or if it is not
* possible to create the parent directory.
*
* TODO: FIXME: This code should be moved on the storage class, so we
* wouldn't have to do all those checks above. Duplicated in
* CalabashPkgExtension.
*/
private File createClasspathFile(Package pkg)
throws PackageException
{
try {
pkg.getResolver().resolveResource(CLASSPATH_FILE);
// if the file exists, return null
return null;
}
catch ( Storage.NotExistException ex ) {
// if the file does not exist, continue
}
// if the classpath does not exist, we must use a FileSystemResolver
FileSystemResolver res = getFileSystemResolver(pkg);
File classpath = res.resolveResourceAsFile(CLASSPATH_FILE);
// check [pkg_dir]/.saxon/
File saxon = classpath.getParentFile();
if ( saxon.exists() ) {
if ( ! saxon.isDirectory() ) {
throw new PackageException("Private dir is not a directory: " + saxon);
}
}
else if ( ! saxon.mkdir() ) {
throw new PackageException("Impossible to create directory: " + saxon);
}
return classpath;
}
private FileSystemResolver getFileSystemResolver(Package pkg)
throws PackageException
{
Storage.PackageResolver res = pkg.getResolver();
if ( res == null ) {
throw new PackageException("Resolver is null on package: " + pkg.getName());
}
if ( ! (res instanceof FileSystemResolver) ) {
throw new PackageException("Not a file system resolver: " + res.getClass());
}
return (FileSystemResolver) res;
}
public static final String CLASSPATH_FILE = ".saxon/classpath.txt";
public static final String SAXON_PKG_NS = "http://saxon.sf.net/ns/expath-pkg";
private final XMLStreamHelper myXSHelper = new XMLStreamHelper(SAXON_PKG_NS);
private static class Mapping
{
public Mapping(String h, String f) {
href = h;
file = f;
}
public String href;
public String file;
}
}
/* ------------------------------------------------------------------------ */
/* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT. */
/* */
/* The contents of this file are subject to the Mozilla Public License */
/* Version 1.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.mozilla.org/MPL/. */
/* */
/* Software distributed under the License is distributed on an "AS IS" */
/* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See */
/* the License for the specific language governing rights and limitations */
/* under the License. */
/* */
/* The Original Code is: all this file. */
/* */
/* The Initial Developer of the Original Code is Florent Georges. */
/* */
/* Contributor(s): none. */
/* ------------------------------------------------------------------------ */