/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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 the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
package org.mulgara.descriptor;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.apache.log4j.*;
import org.w3c.dom.*;
import org.mulgara.itql.ItqlInterpreterBean;
import org.mulgara.query.Answer;
import org.mulgara.query.TuplesException;
import org.mulgara.query.rdf.LiteralImpl;
import org.mulgara.server.ServerInfo;
import org.mulgara.util.TempDir;
/**
* A Descriptor is therefore an XSL stylesheet that performs a specific, well
* defined task
*
* @created 2002-03-15
*
* @author Keith Ahern
*
* @version $Revision: 1.8 $
*
* @modified $Date: 2005/01/05 04:58:11 $
*
* @maintenanceAuthor $Author: newmana $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright © 2001-2003 <A href="http://www.PIsoftware.com/">Plugged In
* Software Pty Ltd</A>
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public class Descriptor {
/**
* Get line separator.
*/
private static final String eol = System.getProperty("line.separator");
/**
* Description of the Field
*/
public final static String DESCRIPTOR_NS_STRING =
"http://mulgara.org/descriptor#";
/**
* Description of the Field
*/
public final static String DESCRIPTOR_TARGET = "_target";
/**
* Description of the Field
*/
public final static String DESCRIPTOR_SELF = "_self";
/**
* Description of the Field
*/
public final static String DESCRIPTOR_SOURCE = "_source";
/**
* Description of the Field
*/
public final static String DESCRIPTOR_CONTEXT = "_context";
/**
* magic name/value to get to clear the descriptor cache in the factory
*/
public final static String CLEAR_DESCRIPTOR_CACHE = "_clearCache";
/**
* Description of the Field
*/
protected static TransformerFactory transformerFactory = null;
/**
* Description of the Field
*/
protected static DocumentBuilderFactory documentBuilderFactory = null;
/**
* Description of the Field
*/
protected static DocumentBuilder documentBuilder = null;
/**
* the filename to use for the stack trace output
*/
final static String DESCRIPTOR_STACKTRACE_OUTPUT =
"descriptor_stacktrace.log";
/**
* Description of the Field
*/
private static PrintWriter w = null;
/**
* Description of the Field
*/
private static final Logger log = Logger.getLogger(Descriptor.class);
/**
* Description of the Field
*/
private static Document stubDoc = null;
/**
* Description of the Field
*/
private static URI descriptorModelURI = null;
/**
* Description of the Field
*/
private static Map<URL,List<Param>> paramMap = null;
/**
* map if our mime types
*/
private static Map<URL,MimeType> mimeMap = null;
/**
* Description of the Field
*/
protected URL url = null;
/**
* Description of the Field
*/
protected Transformer transformer = null;
/**
* holds objects that can be useful from inside descriptors
*/
private DescriptorContext context = null;
/**
* default model for storing descriptors.
*/
public static final String DEFAULT_DESCRIPTOR_MODEL = "descriptors";
/**
* default protocol for accessing descriptors.
*/
public static final String DEFAULT_DESCRIPTOR_PROTOCOL = "http://";
/**
* default relative URL off a host for accessing descriptors.
*/
public static final String DEFAULT_DESCRIPTOR_REL_URL = "/descriptors";
/**
* Constructor for the Descriptor object. Descriptors should be retrieved from
* the factory.
*
* @param url Description of Parameter
* @throws DescriptorException EXCEPTION TO DO
*/
Descriptor(URL url) throws DescriptorException {
this(url, null);
}
/**
* Constructor for the Descriptor object. Descriptors should be retrieved from
* the factory.
*
* @param url Description of Parameter
* @param bean ItqlInterpreterBean to use
* @throws DescriptorException EXCEPTION TO DO
*/
Descriptor(URL url, ItqlInterpreterBean bean) throws DescriptorException {
if (url == null) {
throw new IllegalArgumentException(
"Tried to construct Descriptor with null URL!");
}
this.url = url;
if (log.isDebugEnabled()) {
log.debug("Descriptor initializing from Object with URL " + url);
}
// create our context object
context = new DescriptorContext();
// Use the supplied bean if its non null
if (bean != null) {
if (log.isDebugEnabled()) {
log.debug("using supplied Interpreter bean");
}
context.setInterpreterBean(bean);
}
else {
if (log.isDebugEnabled()) {
log.debug("creating own Interpreter bean");
}
context.setInterpreterBean(new org.mulgara.itql.ItqlInterpreterBean());
}
// configure ourselves
try {
if (documentBuilderFactory == null) {
documentBuilderFactory = DocumentBuilderFactory.newInstance();
}
if (documentBuilder == null) {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
}
// get our stylesheet from the URL
initiateTransformer(fetchStyleSheet(url));
if (descriptorModelURI == null) {
initiateDescriptorModel();
}
// get our metadata
getSettings();
}
catch (IOException ioe) {
throw new DescriptorException("Could not fetch stylesheet: " + url, ioe);
}
catch (TransformerConfigurationException tce) {
throw new DescriptorException("Transformer Exception: " + url, tce);
}
catch (ParserConfigurationException pe) {
throw new DescriptorException("Transformer failure: ", pe);
}
if (log.isDebugEnabled()) {
log.debug("Descriptor initialized from Object with URL " + url);
}
}
/**
* Get the descriptor Graph URI
*
* @return the Descriptor Graph URI as a string;
*/
public static String getModelURIString() {
return ServerInfo.getServerURI() + "#" + DEFAULT_DESCRIPTOR_MODEL;
}
/**
* Writes out the full stack trace details to file and concise summary
* messages to the screen. If writing to the file doesn't succeed, the stack
* trace is displayed on the screen.
*
* @param e the throwable exception
* @param exceptionType A displayable string of the exception type, e.g.
* configuration or fatal
*/
static void writeStackTrace(Throwable e, String exceptionType) {
PrintWriter pw = null;
FileWriter fw = null;
Throwable cause = null;
// write out the stack trace in case it's really needed
try {
fw = new FileWriter(DESCRIPTOR_STACKTRACE_OUTPUT);
pw = new PrintWriter(fw);
pw.println("Top level exception message is:");
pw.println(e.getMessage());
System.err.println("Top level exception message is:");
System.err.println("-->> " + e.getMessage() + " <<--");
pw.println(eol + "Stack trace is:");
e.printStackTrace(pw);
pw.println(eol + eol + "Although probably not important, the message " +
"and stack trace is included for all causes back up the chain..." +
eol);
cause = e.getCause();
while (cause != null) {
String cname = cause.getClass().getName();
System.err.println("which was caused by:");
System.err.println("-->> (" + cname + ") " + cause.getMessage() +
" <<--");
pw.println("Message is:" + eol + cause.getMessage());
pw.println("\nStack trace is:");
cause.printStackTrace(pw);
pw.println(eol);
cause = cause.getCause();
}
System.err.println(
"\nThe last message should indicate the base cause of your problem.");
}
catch (IOException ioe1) {
// can't write out the exception details
System.err.println("\nCan't write out the " + exceptionType +
" exception details to: " +
DESCRIPTOR_STACKTRACE_OUTPUT +
"!\nReason: " + ioe1.getMessage());
System.err.println("Printing " + exceptionType +
" exception details now..." + eol);
e.printStackTrace(System.err);
}
finally {
try {
if (pw != null) {
pw.flush();
pw.close();
}
if (fw != null) {
fw.close();
}
System.err.println("\nFull " + exceptionType +
" exception details successfully written to: " +
DESCRIPTOR_STACKTRACE_OUTPUT + "!" + eol);
}
catch (IOException ioe2) {
// some problems closing the file.
System.err.println("\nMay not have successfully written out the " +
exceptionType + " exception details to: " +
DESCRIPTOR_STACKTRACE_OUTPUT + "!\nReason: " +
ioe2.getMessage());
System.err.println("Printing " + exceptionType +
" exception details now..." + eol);
e.printStackTrace(System.err);
}
}
}
/**
* Reconstructs a full URL from a partial URL and a full source URL
*
* @param relativeURLString such as index.html or images/title.gif
* @param sourceURLString such as http://www.pisoftware.com/staff
* @return URL such as http://www.pisoftware.com/staff/index.html
* @throws DescriptorException EXCEPTION TO DO
*/
static URL resolveRelativeURL(String relativeURLString,
String sourceURLString) throws
DescriptorException {
if (log.isDebugEnabled()) {
log.debug("Attempting to make absolute URL from relative URL '" +
relativeURLString + "' and source URL '" + sourceURLString +
"'");
}
if ( (sourceURLString == null) || (sourceURLString.length() == 0)) {
throw new DescriptorException(
"Unable to create absolute URL from null/zero-length source URL (" +
sourceURLString + ") and relative URL (" + relativeURLString + ")");
}
URL descURL = null;
// lose the last bit of the source URL - after the last slash
int fslash = sourceURLString.lastIndexOf('/');
int bslash = sourceURLString.lastIndexOf('\\');
int slash = (fslash > bslash) ? fslash : bslash;
String baseURLString = sourceURLString.substring(0, slash + 1);
// include the slash
// create the descriptor URL
try {
descURL = new URL(baseURLString + relativeURLString);
}
catch (java.net.MalformedURLException me) {
throw new DescriptorException(
"Unable to create absolute URL from baseURL (" + baseURLString +
") and relative URL (" + relativeURLString + ")",
me);
}
return descURL;
}
/**
* Set the bean to use for example a local session bean or an RMI capable
* bean.
*
* @param bean see comment
*/
public void setInterpreterBean(ItqlInterpreterBean bean) {
if (log.isDebugEnabled()) {
log.debug("Setting new Interpreter bean");
}
context.setInterpreterBean(bean);
}
/**
* Returns the URL asssociated with this Descriptor
*
* @return The URL value
*/
public URL getURL() {
return url;
}
/**
* return mime type, defaults to HTML
*
* @return The MimeType value
* @throws DescriptorException EXCEPTION TO DO
*/
public MimeType getMimeType() throws DescriptorException {
// we will be in here, or exception would have been thrown earlier
return mimeMap.get(url);
}
/**
* returns unmodifiable list of Parameter objects
*
* @return The Params value
*/
public List<Param> getParams() {
return getParams(this.url);
}
/**
* returns unmodifiable list of Parameter objects
*
* @param url PARAMETER TO DO
* @return The Params value
*/
public List<Param> getParams(URL url) {
if (paramMap == null) {
synchronized (Descriptor.class) {
paramMap = new HashMap<URL,List<Param>>();
}
}
return Collections.unmodifiableList(paramMap.get(url));
}
/**
* Description of the Method
*
* @param inParams PARAMETER TO DO
* @return Description of the Returned Value
* @exception DescriptorException Description of Exception
*/
public Document processToDocument(Param[] inParams) throws
DescriptorException {
DOMResult result = new DOMResult();
process(result, inParams);
return (Document) result.getNode();
}
/**
* Converts to document fragment. NOTE xalan 2.4.1+ seems to have a problem
* returning document fragments from extensions, use a Node instead.
*
* @param inParams PARAMETER TO DO
* @return RETURNED VALUE TO DO
* @throws DescriptorException EXCEPTION TO DO
*/
public DocumentFragment processToDocumentFragment(Param[] inParams) throws
DescriptorException {
DocumentFragment df = null;
try {
DOMResult result = new DOMResult();
process(result, inParams);
// convert to document fragment
Document doc = (Document) result.getNode();
df = doc.createDocumentFragment();
df.appendChild(doc.getDocumentElement());
} catch (DescriptorException e) {
log.error(e.getCause());
throw e;
}
return df;
}
/**
* Description of the Method
*
* @param inParams PARAMETER TO DO
* @return Description of the Returned Value
* @exception DescriptorException Description of Exception
*/
public String processToString(Param[] inParams) throws DescriptorException {
StringWriter sw = new StringWriter();
Result result = new StreamResult(sw);
process(result, inParams);
return sw.toString();
}
/**
* Wonder method... Processes the source XML using the Descriptor stylesheet
* for this Descriptor.
*
* @param result Description of Parameter
* @param inParams PARAMETER TO DO
* @exception DescriptorException Description of Exception
*/
public void process(Result result, Param[] inParams) throws
DescriptorException {
// always clear parameters
transformer.clearParameters();
Object param = null;
// set passed parameters if any
if (inParams != null) {
for (int i = 0; i < inParams.length; i++) {
param = inParams[i].getValue();
if ( (param == null) ||
(param instanceof String && ( ( (String) param).length() == 0))) {
log.debug("Transformer ignoring param: " + inParams[i].getName() +
( (param == null) ? " null " : " empty string"));
}
else {
//transformer.setParameter(inParams[i].getName(), inParams[i].getValue());
transformer.setParameter(inParams[i].getName(), param);
log.debug("Transformer setting param: " + inParams[i].getName() +
" to '" + param + "'");
}
}
}
// always pass our own url as descriptor URL
transformer.setParameter(DESCRIPTOR_TARGET, url);
log.debug("Transformer always setting param: '" + DESCRIPTOR_TARGET + "'='" + url + "'");
// set the bean as a parameter to propagate to other beans
transformer.setParameter(DESCRIPTOR_CONTEXT, context);
log.debug("Transformer always setting param: '" + DESCRIPTOR_CONTEXT + "'='" + context + "'");
if (stubDoc == null) {
stubDoc = documentBuilder.newDocument();
stubDoc.appendChild(stubDoc.createElement("ROOT"));
}
try {
// perform the transformation
transformer.transform(new DOMSource(stubDoc), result);
log.debug("XLST transformed node result: " + result + " class: " + result.getClass().getName() );
}
catch (TransformerException te) {
log.error("Transformer failure: " + te.getCause());
throw new DescriptorException("Transformer failure: ", te);
}
}
/**
* Wonder method... Processes the source XML using the Descriptor stylesheet
* for this Descriptor.
*
* @param xml Description of Parameter
* @return Description of the Returned Value
* @exception DescriptorException Description of Exception
*/
public Result process(Source xml) throws DescriptorException {
try {
DOMResult result = new DOMResult(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument());
//USE String result if debugging, then you can dump the result to a log
//too StringWriter sw = new StringWriter();
//Result result = new StreamResult(sw);
//
transformer.transform(xml, result);
/*
if (log.isDebugEnabled()) {
log.debug("RESULT:" + eol + sw.toString());
}
*/
return result;
} catch (javax.xml.parsers.ParserConfigurationException pce) {
log.error("XML parser failure: " + pce.getCause());
throw new DescriptorException("XML parser failure: ", pce);
}
catch (TransformerException te) {
log.error("Transformer failure: " + te.getCause());
throw new DescriptorException("Transformer failure: ", te);
}
}
/**
* Resets the descriptor - should be called if the factory is resetting all
* descriptors with this URL
*
*/
protected void resetSettings() {
// remove ourselves from the param map
synchronized (paramMap) {
if (paramMap.containsKey(url)) {
paramMap.remove(url);
}
}
// remove ourselves from the mime map
synchronized (mimeMap) {
if (mimeMap.containsKey(url)) {
mimeMap.remove(url);
}
}
if (log.isDebugEnabled()) {
log.debug("Resetting Descriptor: " + url + " instance " + this);
}
}
/**
* Gets the settings such as parameters from a Mulgara server
*
* @throws DescriptorException EXCEPTION TO DO
*/
private void getSettings() throws DescriptorException {
// get parameter settings for this URL
if (paramMap == null) {
synchronized (Descriptor.class) {
paramMap = new HashMap<URL,List<Param>>();
}
}
// get mime settings for this URL
if (mimeMap == null) {
synchronized (Descriptor.class) {
mimeMap = new HashMap<URL,MimeType>();
}
}
synchronized (paramMap) {
if (paramMap.containsKey(url) && mimeMap.containsKey(url)) {
if (log.isDebugEnabled()) {
log.debug("Already have settings for: " + url);
}
// we have settings just return
return;
}
// var to be put in maps
List<Param> params = new ArrayList<Param>();
String mimeMajor = null;
String mimeMinor = null;
//String query = "select $param $paramRequired $mime-major $mime-minor from <" + descriptorModelURI + "> where " +
// "<" + url + "> <" + DESCRIPTOR_NS_STRING + "hasParam> $id and " +
// "$id <" + DESCRIPTOR_NS_STRING + "name> $param and
// "$id <" + DESCRIPTOR_NS_STRING + "required> $paramRequired and
// "<" + url + "> <" + DESCRIPTOR_NS_STRING + "#hasMimetype> $mimeNode and
// $mimeNode <" + DESCRIPTOR_NS_STRING + "#mime-major and"
// $mimeNode <" + DESCRIPTOR_NS_STRING + "#mime-minor"
StringBuffer b = new StringBuffer();
b.append("select $param $paramRequired $mimeMajor $mimeMinor from <");
b.append(descriptorModelURI);
b.append("> where <");
// param anon node
b.append(url);
b.append("> <");
b.append(DESCRIPTOR_NS_STRING);
b.append("hasParam> $id and ");
// param name
b.append("$id <");
b.append(DESCRIPTOR_NS_STRING);
b.append("name> $param and ");
// param required
b.append("$id <");
b.append(DESCRIPTOR_NS_STRING);
b.append("required> $paramRequired and ");
// mime type anon node
b.append("<");
b.append(url);
b.append("> <");
b.append(DESCRIPTOR_NS_STRING);
b.append("hasMimetype> $mimeNode and ");
// mime type anon node
// mime major
b.append("$mimeNode <");
b.append(DESCRIPTOR_NS_STRING);
b.append("mime-major> $mimeMajor and ");
// mime minor
b.append("$mimeNode <");
b.append(DESCRIPTOR_NS_STRING);
b.append("mime-minor> $mimeMinor ");
// close the query
b.append(";");
String query = b.toString();
List<Object> answers = context.getInterpreterBean().executeQueryToList(query);
Object obj = null;
try {
for (Iterator<Object> ai = answers.iterator(); ai.hasNext(); ) {
obj = ai.next();
Answer answer = (Answer)obj;
// get our result
if (!answer.isUnconstrained()) {
//reset cursor
answer.beforeFirst();
while (answer.next()) {
// do params
//params.add(((RDFLiteralImpl)resultSet.getObject("param")).getText());
params.add(new Param(
// param name
( (LiteralImpl) answer.getObject("param")).getLexicalForm(),
// param value
null,
// param required
//Boolean.valueOf( ((RDFLiteralImpl)resultSet.getObject("paramRequired")).getText() ).booleanValue()
( ( (LiteralImpl) answer.getObject("paramRequired")).
getLexicalForm()).equalsIgnoreCase(
"Yes")));
if (log.isDebugEnabled()) {
log.debug("retrieved parameter " +
( (LiteralImpl) answer.getObject("param")).getLexicalForm() +
" for descriptor " + url);
}
// do mime type
if (mimeMajor == null) {
mimeMajor = ( ( (LiteralImpl) answer.getObject("mimeMajor")).
getLexicalForm());
if (log.isDebugEnabled()) {
log.debug("Mime Major: " + mimeMajor);
}
}
if (mimeMinor == null) {
mimeMinor = ( ( (LiteralImpl) answer.getObject("mimeMinor")).
getLexicalForm());
if (log.isDebugEnabled()) {
log.debug("Mime Minor: " + mimeMinor);
}
}
}
}
if (answer != null) answer.close();
if (log.isDebugEnabled()) {
log.debug("Set Params for URL: " + url);
}
if (params.size() == 0) {
throw new DescriptorException(
"No params were found (not even _self) for " + url);
}
if (mimeMajor == null) {
throw new DescriptorException("Mime Major not set for " + url);
}
if (mimeMinor == null) {
throw new DescriptorException("Mime Minor not set for " + url);
}
// add to map so subsequent objects may work
paramMap.put(url, params);
mimeMap.put(url, new MimeType(mimeMajor, mimeMinor));
}
if (log.isDebugEnabled()) {
log.debug("Retrieved PARAMS!" + params);
}
}
catch (ClassCastException ce) {
ce.printStackTrace();
throw new DescriptorException("Mulgara Query error: " + obj,
(obj instanceof Exception) ?
(Exception) obj : ce);
}
catch (TuplesException qe) {
throw new DescriptorException("Mulgara Query error", qe);
}
}
}
/**
* Get our setting from the Mulgara store
*
* @throws DescriptorException EXCEPTION TO DO
*/
private void initiateDescriptorModel() throws DescriptorException {
String descriptorModelString = getModelURIString();
try {
descriptorModelURI = new URI(descriptorModelString);
if (log.isDebugEnabled()) {
log.debug("Using descriptor Graph URI is : " + descriptorModelURI);
}
}
catch (URISyntaxException use) {
throw new DescriptorException("Descriptor Graph URI: " +
descriptorModelString +
" is not a valid URI", use);
}
}
/**
* Fetchs the stylesheet from the URL for this Descriptor
*
* @param url Description of Parameter
* @return Description of the Returned Value
* @exception java.io.IOException Description of Exception
*/
private Source fetchStyleSheet(URL url) throws java.io.IOException {
InputStream stream = (InputStream) url.openStream();
if (log.isDebugEnabled()) {
log.debug("BYTES available: " + stream.available());
}
return new StreamSource(stream);
}
/**
* Description of the Method
*
* @param xslSource Description of Parameter
* @exception TransformerConfigurationException Description of Exception
* @throws DescriptorException EXCEPTION TO DO
*/
private void initiateTransformer(Source xslSource) throws
TransformerConfigurationException, DescriptorException {
if (transformerFactory == null) {
transformerFactory = TransformerFactory.newInstance();
//transformerFactory.setAttribute("http://xml.apache.org/xalan/features/optimize", java.lang.Boolean.FALSE);
}
//transformer = transformerFactory.newTransformer();
transformer = transformerFactory.newTransformer(xslSource);
//transformer.setErrorListener(new org.apache.xml.utils.ListingErrorHandler());
if (w == null) {
// construct the file
File errFile = new File(TempDir.getTempDir(), "descriptor-errors.log");
try {
w = new PrintWriter(new FileOutputStream(errFile), true);
w.write("Descriptor XALAN LOG " + new java.util.Date() + eol);
transformer.setErrorListener(new PIErrorHandler(w));
}
catch (IOException ioe) {
// could not create file, fall back to default handler
//transformer.setErrorListener(new org.apache.xml.utils.ListingErrorHandler());
// NO LONGER FATAL throw new DescriptorException("Unable to write to XALAN report file", ioe);
log.warn("Unable to write descriptor errors to file: " + errFile +
" using default error handler");
}
}
}
/**
* Inner class for holding Mime Types
*/
public class MimeType {
String mimeMajor = null;
String mimeMinor = null;
String mimeType = null;
/**
* CONSTRUCTOR MimeType TO DO
*
* @param mimeMajor PARAMETER TO DO
* @param mimeMinor PARAMETER TO DO
*/
public MimeType(String mimeMajor, String mimeMinor) {
if (mimeMajor == null) {
throw new IllegalArgumentException(
"Mime Major type is not allowed to be null!");
}
if (mimeMinor == null) {
throw new IllegalArgumentException(
"Mime Minor type is not allowed to be null!");
}
this.mimeMajor = mimeMajor;
this.mimeMinor = mimeMinor;
mimeType = mimeMajor + "/" + mimeMinor;
}
public String getMajorType() {
return mimeMajor;
}
public String getMinorType() {
return mimeMinor;
}
public String toString() {
return mimeType;
}
}
}