/****************************************************************************/
/* File: CalabashPkgExtension.java */
/* Author: F. Georges - H2O Consulting */
/* Date: 2011-09-06 */
/* Tags: */
/* Copyright (c) 2011 Florent Georges (see end of file.) */
/* ------------------------------------------------------------------------ */
package org.expath.pkg.calabash;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import net.sf.saxon.s9api.QName;
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 "calabash", configured with "calabash.xml".
*
* The extension descriptor "calabash.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>
* <step>
* <type>{http://example.org/ns/project}my-step-type</type>
* <class>org.example.extension.MyStep</class>
* </step>
* </package>
* </pre>
*
* The elements "jar" and "step" are optional, repeatable, and can appear in any
* order. The element "jar" links to the JAR files, in the content directory,
* to include the classpath. The element "step" register an extension step by
* using its fully qualified class name, and a step type name. The type name is
* in Clark notation (that is {@code "{namespace}local-name"}). The class must
* be a suitable class for an extension step for Calabash.
*
* @author Florent Georges
*/
public class CalabashPkgExtension
extends DescriptorExtension
{
public CalabashPkgExtension()
{
super("calabash", "calabash.xml");
}
@Override
protected void parseDescriptor(XMLStreamReader parser, Package pkg)
throws PackageException
{
myXSHelper.ensureNextElement(parser, "package");
CalabashPkgInfo info = new CalabashPkgInfo(pkg);
try {
parser.next();
while ( parser.getEventType() == XMLStreamConstants.START_ELEMENT ) {
if ( CALABASH_PKG_NS.equals(parser.getNamespaceURI()) ) {
handleElement(parser, info);
}
else {
// ignore elements not in the Calabash 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, CalabashPkgInfo info)
throws PackageException
, XMLStreamException
{
String local = parser.getLocalName();
if ( "jar".equals(local) ) {
String jar = myXSHelper.getElementValue(parser);
info.addJar(jar);
}
else if ( "step".equals(local) ) {
myXSHelper.ensureNextElement(parser, "type");
String type = myXSHelper.getElementValue(parser);
myXSHelper.ensureNextElement(parser, "class");
String clazz = myXSHelper.getElementValue(parser);
// position to </step>
parser.next();
// push the values in info
QName qname = QName.fromClarkName(type);
info.addStep(qname, clazz);
}
else {
throw new PackageException("Unknown Calabash component type: " + local);
}
}
@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
CalabashPkgInfo info = getInfo(pkg);
if ( info == null ) {
// not a Calabash extension
return;
}
// if there are no JAR, nothing to do
if ( ! info.hasJars() ) {
return;
}
// if there is some JAR, crete the classpath file
setupClasspath(pkg, info);
}
private CalabashPkgInfo getInfo(Package pkg)
throws PackageException
{
PackageInfo info = pkg.getInfo(getName());
if ( info == null ) {
return null;
}
if ( ! (info instanceof CalabashPkgInfo) ) {
throw new PackageException("Not a Calabash-specific package info: " + info.getClass());
}
return (CalabashPkgInfo) info;
}
private void setupClasspath(Package pkg, CalabashPkgInfo 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() ) {
Source jar_src = res.resolveComponent(jar);
String sysid = jar_src.getSystemId();
URI uri = URI.create(sysid);
File file = new File(uri);
out.write(file.getCanonicalPath());
out.write("\n");
}
out.close();
}
catch ( Storage.NotExistException ex ) {
throw new PackageException("The Calabash descriptor refers to an inexistent JAR", ex);
}
catch ( IOException ex ) {
throw new PackageException("Error writing the Calabash 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
* SaxonPkgExtension.
*/
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]/.calabash/
File calabash = classpath.getParentFile();
if ( calabash.exists() ) {
if ( ! calabash.isDirectory() ) {
throw new PackageException("Private dir is not a directory: " + calabash);
}
}
else if ( ! calabash.mkdir() ) {
throw new PackageException("Impossible to create directory: " + calabash);
}
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 = ".calabash/classpath.txt";
public static final String CALABASH_PKG_NS = "http://xmlcalabash.com/ns/expath-pkg";
private final XMLStreamHelper myXSHelper = new XMLStreamHelper(CALABASH_PKG_NS);
}
/* ------------------------------------------------------------------------ */
/* 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. */
/* ------------------------------------------------------------------------ */