/*
* 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.tag;
//Mulgara utilities
// Java 2 enterprise packages
import java.io.IOException;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.log4j.Logger;
import org.apache.soap.SOAPException;
import org.apache.soap.rpc.Parameter;
import org.apache.soap.rpc.Response;
import org.mulgara.util.TagSoapClient;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Sends queries to a Mulgara server.
*
* @created 2001-11-07
*
* @author Tom Adams
* @author Tate Jones
*
* @version $Revision: 1.10 $
*
* @modified $Date: 2005/03/12 02:54:05 $ by $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 StatementTag extends BodyTagSupport {
private static final long serialVersionUID = -7419226348089456963L;
/**
* the name of the id attribute
*/
protected final static String ATTR_ID = "id";
/**
* the name of the server attribute
*/
protected final static String ATTR_SERVER = "server";
/**
* the prefix of the key to retrieve the map of query answers from
*/
protected final static String KEY_STATEMENT_PREFIX =
"mulgara.tag.statement.answers";
//
// Constants
//
/**
* the category to log to
*
*/
private final static Logger log =
Logger.getLogger(StatementTag.class.getName());
//
// Members
//
/**
* the Mulgara server containing metadata we're interested in, overrides the value
* set using the {@link InitTag} tag
*/
private URL server = null;
/**
* the (ordered) map of queries to send to a Mulgara server
*/
private LinkedHashMap<String,String> queries = null;
/**
* the URL of the SOAP endpoint of the Mulgara server
*/
private URL soapEndpoint = null;
/**
* the list of answers ({@link org.apache.soap.rpc.Response }s) to the queries
* in this tag, in the order of execution specified by the {@link ExecuteTag}s
* in the body
*/
private ArrayList<Response> answers = null;
/**
* mapping of query ids to answer list indices
*/
private HashMap<String,Integer> answerPositionMap = null;
// findSoapEndpoint()
/**
* Formats a {@link org.apache.soap.rpc.Response} into a {@link
* org.w3c.dom.Document}.
*
* @param answer the answer to a query
* @return the <code>answer</code> as an {@link org.w3c.dom.Document}
* @throws SOAPException if the SOAP message cannot be externalised
* @throws ParserConfigurationException if a Document builder cannot be
* created
* @throws SAXException if the contents of the SOAP message cannot be parsed
* @throws IOException if an I/O error occurs
*/
private static Document formatSoapResponse(Response answer) throws
SOAPException,
ParserConfigurationException,
SAXException,
IOException {
// parse the answer into a document
DocumentBuilder builder =
DocumentBuilderFactory.newInstance().newDocumentBuilder();
Parameter result = answer.getReturnValue();
Document doc =
builder.parse(new InputSource(
new StringReader(result.getValue().toString())));
// return the document
return doc;
}
// execute()
/**
* Sets the URL of the SOAP endpoint of the Mulgara server containing metadata
* we're interested in. <p>
*
* Note. This method will be called if this tag is invoked with the <code>server</code>
* attribute set, overriding the server name set using the <code>init</code>
* tag. </p>
*
* @param server the URL of the SOAP endpoint of the Mulgara server containing
* metadata we're interested in
* @throws JspTagException if <code>server</code> specified is not a valid URL
*/
public void setServer(String server) throws JspTagException {
try {
this.server = new URL(server);
}
catch (MalformedURLException mue) {
// log the error
log.error("Invalid SOAP endpoint URL in metadata tag");
// wrap it and re-throw!
throw new JspTagException("statement: Invalid URL specified as server " +
"attribute value");
}
// try-catch
}
// setServer()
/**
* Returns the URL of the SOAP endpoint of the Mulgara server containing metadata
* we're interested in. <p>
*
* Note. This method may return null if this tag has not been invoked with the
* <code>server</code> attribute set. If this is the case, the server URL may
* be obtained by retrieving the value of the attribute
* "mulgara.server.soapendpoint". </p>
*
* @return the URL of the SOAP endpoint of the Mulgara server containing metadata
* we're interested in, or <code>null</code> if the server hasn't been
* set
*/
public String getServer() {
if (this.server == null) {
return null;
}
else {
return this.server.toString();
}
// end if
}
//
// Methods overriding BodyTagSupport
//
/**
* Initialises the query map and the SOAP endpoint.
*
* @return a response code informing the servlet container how to proceed with
* JSP tag execution
* @throws JspException if no SOAP endpoint was specified either as an
* attribute to this tag, or in a preceeding init tag, or if the SOAP
* endpoint specified was invalid
* @throws JspTagException EXCEPTION TO DO
*/
public int doStartTag() throws JspTagException {
// log that we're starting execution of the start tag
log.debug("Starting statement tag execution");
// intialise the members
this.queries = new LinkedHashMap<String,String>();
this.answers = new ArrayList<Response>();
this.answerPositionMap = new HashMap<String,Integer>();
try {
// get the SOAP endpoint
URL soapEndpoint = this.findSoapEndpoint();
if (soapEndpoint == null) {
throw new JspTagException("statement: If invoked without \"server\" " +
"attribute, statement tag must be preceeded by init tag");
}
// end if
// log that we've found the SOAP endpoint
log.debug("Found Mulgara server SOAP enpoint - " + soapEndpoint);
// set the SOAP endpoint
this.soapEndpoint = soapEndpoint;
}
catch (MalformedURLException mue) {
// log the error
log.error("Invalid SOAP endpoint or document URL in statement tag");
// wrap it and re-throw!
throw new JspTagException("statement: Invalid URL specified as server " +
"attribute value");
}
// try-catch
// continue to evaluate the body
return EVAL_BODY_BUFFERED;
}
// doStartTag()
/**
* Returns the results of the nested queries.
*
* @return RETURNED VALUE TO DO
* @throws JspTagException if an the content cannot be rendered
*/
public int doAfterBody() throws JspTagException {
// log what we're doing
log.debug("Returning query results");
try {
// parse the Reponse answers into a list of Answers
if ( (this.answers != null) && (this.answers.size() > 0)) {
// find out whether to return the answers in the raw (as SOAP messages)
// or parsed into a nice list of Documents
if (this.getId() != null) {
// log that we're parsing results
log.debug("Returning results as list of Documents");
// create a list for the answers
ArrayList<Document> formattedAnswers = new ArrayList<Document>();
// parse the Response answers into a list of Answers
for (Iterator<Response> ansIterator = this.answers.iterator();
ansIterator.hasNext(); ) {
// get the answer as a document
Document document =
StatementTag.formatSoapResponse( ansIterator.next());
// add it to the
formattedAnswers.add(document);
}
// end for
// set a page context variable containing the query answers
this.pageContext.setAttribute(this.getId(), formattedAnswers);
// log that we've added the answers
log.debug("Added formatted answers to " + formattedAnswers.size() +
" queries to page context");
// set a page context variable containing the answer position mappings
this.pageContext.setAttribute(StatementTag.KEY_STATEMENT_PREFIX +
"." + this.getId(),
this.answerPositionMap);
// log that we've added the answer position map
log.debug("Added position mappings to " +
this.answerPositionMap.size() + " queries");
}
else {
// log that we're returning the results to the stream
log.debug("Returning raw results to stream");
// create a String buffer to hold the answers
StringBuffer output = new StringBuffer();
// print each answer to this buffer
for (Iterator<Response> ansIterator = this.answers.iterator();
ansIterator.hasNext(); ) {
pageContext.getOut().println(TagSoapClient.getContent(ansIterator.next()));
}
// end for
// log that we're about to print the query answers
log.debug("Printing " + this.answers.size() + " answers");
// write the answers to the output stream
this.getPreviousOut().println(output.toString());
}
// end if
}
else {
log.debug("No answers found -> returning nothing");
}
// end if
}
catch (SOAPException se) {
// log the error
log.error("Unable to externalise SOAP answer", se);
// wrap it and re-throw!
throw new JspTagException("statement: Unable to externalise SOAP " +
"answer to query");
}
catch (ParserConfigurationException pce) {
// log the error
log.error("Unable create parser to parse SOAP message", pce);
// wrap it and re-throw!
throw new JspTagException("statement: Unable to create XML parser for " +
"SOAP answer");
}
catch (SAXException saxe) {
// log the error
log.error("Unable to parse SOAP answer into a document", saxe);
// wrap it and re-throw!
throw new JspTagException("statement: Unable to parse SOAP " +
"answer into an XML document");
}
catch (IOException ioe) {
// log the error
log.error("Error handling SOAP asnwer", ioe);
// wrap it and re-throw!
throw new JspTagException("statement: Unable to print query answer " +
"to JSP output stream");
}
catch (Exception e) {
log.error("Unhandled exception in statement tag", e);
}
// try-catch
// no need to re-execute the body
return SKIP_BODY;
}
// doAfterBody()
/**
* Resets the tag to its default state.
*/
public void release() {
this.id = null;
this.server = null;
this.queries = null;
this.soapEndpoint = null;
this.answers = null;
this.answerPositionMap = null;
}
// release()
//
// Public API
//
/**
* Adds a query to the query map using an auto-generated id (key).
*
* @param query the query to add to the map
*/
public void addQuery(String query) {
// no id was specified, make one up
String id = "Query-" + Integer.toString(this.queries.size());
while (this.queries.containsKey(id)) {
// we should never get duplicate keys as we cannot remove queries, but
// just in case
id += ("-" + Integer.toString(this.queries.size()));
}
// end if
// log that we're about to add a new query
log.debug("Adding new query with id " + id);
// add the query
this.addQuery(id, query);
}
// addQuery()
/**
* Adds a query to the query map.
*
* @param queryId the id of the query (its key in the map)
* @param query the query to add to the map
*/
public void addQuery(String queryId, String query) {
log.debug("Adding new query (" + queryId + ") - " + query);
this.queries.put(queryId, query);
}
// addQuery()
/**
* Executes all the queries in this statement.
*
* @throws JspTagException if the batch of queries cannot be sent as a
* Response
*/
public void execute() throws JspTagException {
try {
// log that we're executing a query
log.debug("Executing all queries in batch mode");
// create a buffer to hold the queries
StringBuffer queryBatch = new StringBuffer();
// build up the batch query
for (Iterator<String> ids = this.queries.keySet().iterator(); ids.hasNext(); ) {
// add the query to the batch buffer
String query = this.queries.get(ids.next());
queryBatch.append(query + "\n");
}
// end for
// log that we're retrieving the answers to the batch query
log.debug("Finding answer to batch query");
// send the query batch
Response batchAnswer = this.sendQuery(queryBatch.toString());
// log that we're adding the answers
log.debug("Adding answers to batch query to answer list");
// add the answer to the list of answers
this.answers.add(batchAnswer);
// log that we've added the answers
log.debug("Added answers to batch query to answer list");
}
catch (SOAPException se) {
// log the error
log.error("Unable to send query batch as a SOAP message", se);
// wrap it and re-throw!
throw new JspTagException("statement: Unable to send query batch as a " +
"SOAP message");
}
// try-catch
}
// execute()
/**
* Executes the query with the given id.
*
* @param queryId the id (key) of the query to execute
* @throws JspTagException if a query with the id specified does not exist
*/
public void execute(String queryId) throws JspTagException {
try {
// make sure the query id is valid
if (!this.queries.containsKey(queryId)) {
throw new JspTagException("statement: No query exists with id " +
queryId);
}
// end if
// log that we're executing the query
log.debug("Executing query id - " + queryId);
// get the query to execute
String query = this.queries.get(queryId);
// send the query
Response answer = this.sendQuery(query);
// log that we're executing the query
log.debug("Adding answer to query " + queryId + " to answer list");
// add the SOAP answer to the answers list
this.answers.add(answer);
// save the position of the answer to this query
this.answerPositionMap.put(queryId,
new Integer(this.answers.size() - 1));
}
catch (SOAPException se) {
// log the error
log.error("Unable to send query into a SOAP message", se);
// wrap it and re-throw!
throw new JspTagException("statement: Unable to send query as a SOAP " +
"message");
}
// end if
}
// formatSoapResponse()
/**
* Sends a query to a Mulgara server.
*
* @param query the query to send
* @return the answer to the query
* @throws SOAPException if the query cannot be sent, or the response cannot
* be received
*/
private Response sendQuery(String query) throws SOAPException {
// log that we're about to send the query
log.debug("Executing query - \n" + query);
URL url = null;
try {
//Create a URL to the SOAP end - point
url = new URL(this.soapEndpoint.toString());
}
catch (MalformedURLException mue) {
// log the error
log.error("Invalid SOAP endpoint URL in metadata tag");
throw new SOAPException(org.apache.soap.Constants.FAULT_CODE_SERVER,
"statement: Invalid URL specified as server " +
"attribute value", mue);
}
// try-catch
return TagSoapClient.invoke(url, query);
}
// sendQuery()
/**
* Finds the URL of the SOAP endpoint of the Mulgara server containing metadata
* we're interested in. <p>
*
* This tag looks first for an attribute defined on this tag, and then in the
* <code>pageContext</code> for an attribute set using the <code>init</code>
* tag. </p>
*
* @return the URL of the SOAP endpoint of the Mulgara server containing metadata
* we're interested in, or null if the model URI has not been defined as
* an attribute to this tag or using the <code>init</code> tag
* @throws MalformedURLException if the SOAP endpoint is not a valid URL
*/
private URL findSoapEndpoint() throws MalformedURLException {
URL soapEndpoint = null;
// look for the endpoint as an attribute first, then in the page context
if (this.getServer() != null) {
// override the default server with the URL specified as an attribute
soapEndpoint = new URL(this.getServer());
}
else {
// get the endpoint set in the init tag (we don't know the scope...)
soapEndpoint = (URL)this.pageContext.findAttribute(InitTag.KEY_SERVER);
}
// end if
// return the endpoint
return soapEndpoint;
}
// setAnswerPositionMap()
}