/*
* This file is part of the WfXML servlet.
* Copyright (C) 2001-2006 Danet GmbH (www.danet.de), BU BTS.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: Servlet.java 2431 2007-07-15 22:01:11Z mlipp $
*
* $Log$
* Revision 1.24 2007/04/03 14:47:54 schnelle
* Some code cleanup.
*
* Revision 1.23 2007/03/29 11:46:54 schnelle
* Reactivated ASAPException to propagate ASAP error messages in cases of an invalid key, a missing resource or an invalid factory.
*
* Revision 1.22 2007/03/27 21:59:42 mlipp
* Fixed lots of checkstyle warnings.
*
* Revision 1.21 2007/03/01 12:32:57 schnelle
* Enhanced Instance.SetProperties to process ContextData.
*
* Revision 1.20 2007/02/21 21:32:29 mlipp
* Using pooled JMS connections when in EJB now.
*
* Revision 1.19 2007/02/17 21:19:48 mlipp
* Workflow service caching redone.
*
* Revision 1.18 2007/02/06 08:35:34 schnelle
* Started automatic generation of wsdl description.
*
* Revision 1.17 2007/02/01 10:08:36 drmlipp
* Removed no longer used observer resource.
*
* Revision 1.16 2007/01/31 22:55:36 mlipp
* Some more refactoring and fixes of problems introduced by refactoring.
*
* Revision 1.15 2007/01/31 14:53:06 schnelle
* Small corrections wvaluating the resource reference.
*
* Revision 1.14 2007/01/31 12:24:06 drmlipp
* Design revisited.
*
* Revision 1.13 2007/01/30 11:56:14 drmlipp
* Merged Wf-XML branch.
*
* Revision 1.12.6.24 2007/01/29 15:04:19 schnelle
* Renaming of Observer to ObserverRegistry and URIDecoder to ResourceReference.
*
* Revision 1.12.6.23 2007/01/29 13:40:31 schnelle
* Storing of the sender base in the servlet context.
*
* Revision 1.12.6.22 2007/01/29 10:48:28 schnelle
* Using a key-value encoding of the parameters instead of the directory approach.
*
* Revision 1.12.6.21 2007/01/26 15:50:28 schnelle
* Added encoding for process id and package id.
*
* Revision 1.12.6.20 2007/01/24 14:22:38 schnelle
* Observer handler starts on servlet startup.
*
* Revision 1.12.6.19 2007/01/24 10:56:50 schnelle
* Prepared return of a result for aobservers.
*
* Revision 1.12.6.18 2007/01/19 12:34:56 schnelle
* Moved generation and decoding of the URI that is used as the receiver key to new class URIDecoder.
*
* Revision 1.12.6.17 2007/01/19 07:59:35 schnelle
* Corrected return value for factory list instances.
*
* Revision 1.12.6.16 2007/01/16 11:05:42 schnelle
* Refactoring: Moved subscription handling methods to own class.
*
* Revision 1.12.6.15 2007/01/11 10:23:52 schnelle
* Creation of StateChanged notifications.
*
* Revision 1.12.6.14 2007/01/10 13:41:27 schnelle
* Implemented subscribe.
*
* Revision 1.12.6.13 2007/01/09 12:06:14 schnelle
* Implemented methods to receive xsd and wsdl files.
*
* Revision 1.12.6.12 2006/12/20 13:32:24 schnelle
* Basic implementato of GetProperties for Instance and Activity.
*
* Revision 1.12.6.11 2006/12/18 14:41:02 schnelle
* Preparatation for individual schema definition for each getproperties request.
*
* Revision 1.12.6.10 2006/12/13 11:23:48 schnelle
* Implemented instance ListActivities.
*
* Revision 1.12.6.9 2006/12/12 13:24:38 schnelle
* Introduction of ASAPException to provide a detailed mesage.
*
* Revision 1.12.6.8 2006/12/12 09:34:35 schnelle
* Implemented ChangeState for Instance.
*
* Revision 1.12.6.7 2006/12/11 11:05:34 schnelle
* Added template methods for all requests.
*
* Revision 1.12.6.6 2006/12/01 12:49:54 schnelle
* Basic import of context data for process creation.
*
* Revision 1.12.6.5 2006/11/29 14:12:37 schnelle
* Take respect to namespaces of asap requests and responses.
*
* Revision 1.12.6.4 2006/11/28 15:31:51 schnelle
* Proper selection of the response generator.
*
* Revision 1.12.6.3 2006/11/28 12:20:09 schnelle
* Creation of a separate class to handle the issues for a specific resource.
*
* Revision 1.12.6.2 2006/11/27 15:41:55 schnelle
* Introducing some constants for request and response identification.
*
* Revision 1.12.6.1 2006/11/24 12:19:13 schnelle
* Separtion of response generation into ResponseGenerator class.
*
* Revision 1.12 2006/09/29 12:32:11 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.11 2005/11/17 21:30:25 mlipp
* Finished re-organization of jetspeed module creation.
*
* Revision 1.10 2005/06/18 19:43:12 mlipp
* Improved.
*
* Revision 1.9 2005/06/01 20:46:16 mlipp
* Started getProperties.
*
* Revision 1.8 2005/04/24 18:55:34 mlipp
* Fixed bug with scalar result.
*
* Revision 1.7 2005/04/11 20:22:18 mlipp
* Implemented enabled process definition list.
*
* Revision 1.6 2005/04/10 20:59:30 mlipp
* Improved exception handling.
*
* Revision 1.5 2005/04/10 19:53:54 mlipp
* Valid response and JaWE workaround.
*
* Revision 1.4 2005/04/06 21:08:05 mlipp
* Getting on...
*
* Revision 1.3 2005/04/06 20:03:26 mlipp
* Generating web.xml now.
*
* Revision 1.2 2005/01/24 20:25:56 mlipp
* Reverted saaj back to 1.1 to fit Axis version.
*
* Revision 1.1 2005/01/23 15:23:11 mlipp
* Getting started with WfXML.
*
*/
package de.danet.an.workflow.clients.wfxml;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.rmi.RemoteException;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.naming.NamingException;
import javax.naming.directory.InvalidSearchFilterException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.wsdl.Binding;
import javax.wsdl.Definition;
import javax.wsdl.Import;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.Types;
import javax.wsdl.WSDLException;
import javax.wsdl.extensions.schema.Schema;
import javax.wsdl.extensions.schema.SchemaImport;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.wsdl.xml.WSDLWriter;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.Name;
import javax.xml.soap.Node;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import com.ibm.wsdl.extensions.soap.SOAPAddressImpl;
import com.ibm.wsdl.factory.WSDLFactoryImpl;
import de.danet.an.util.XMLUtil;
import de.danet.an.workflow.api.EventSubscriber;
import de.danet.an.workflow.api.FactoryConfigurationError;
import de.danet.an.workflow.api.WorkflowService;
import de.danet.an.workflow.api.WorkflowServiceFactory;
import de.danet.an.workflow.omgcore.WfAuditEvent;
/**
* This class provides an interface between the workflow engine (i.e. its
* processes and any HTTP based client (e.g. Web browser application).
*
* <p>
* The servlet works as a mediator for services of the different roles,
* excluding the observer, as they are defined by the Wf-XML 2.0 standard.
* </p>
*
* @author Michael Lipp
* @author Dirk Schnelle
* @version $Revision: 2431 $
* @web.servlet name="WfXML Servlet" display-name="WfXML Servlet"
* description="WfXML interface servlet" load-on-startup="1"
* @web.servlet-mapping url-pattern="/*"
* @web.servlet-init-param name="SenderBase" value="@@@_WfXML_service_url_@@@"
* @web.servlet-init-param name="ApplicationPolicy"
* value="@@@_WfXML_servlet_application_policy_@@@"
* @web.servlet-init-param name="UserName"
* value="@@@_WfXML_servlet_username_@@@"
* @web.servlet-init-param name="Password"
* value="@@@_WfXML_servlet_password_@@@"
* @web.resource-ref name="jdbc/WfEngine" type="javax.sql.DataSource"
* auth="Container"
* @jboss.resource-ref res-ref-name="jdbc/WfEngine" jndi-name="java:/WfMOpenDS"
*/
public class Servlet extends HttpServlet {
/** Logger instance. */
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog(Servlet.class);
/** Attribute sender base in the servlet context. */
private static final String SENDER_BASE = "SenderBase";
/** Attribute observer registry in the servlet context. */
static final String OBSERVER_REGISTRY = "ObserverRegistry";
/** The servlet context. */
private ServletContext ctx = null;
/** Audit handler thread. */
private AuditHandlerThread auditHandlerThread = null;
/** Cache of the last workflow service lookup. */
private WorkflowService wfsCache = null;
/** Base URL that is used as a prefix in the sender key. */
private String senderBase;
/** The observer registry. */
private ObserverRegistry observerRegistry = null;
/** The event subscriber */
private EventSubscriber eventSubscriber = null;
/**
* Request workflow service.
*/
private synchronized WorkflowService getWorkflowService() {
synchronized (this) {
if (wfsCache == null) {
WorkflowServiceFactory factory
= WorkflowServiceFactory.newInstance();
wfsCache = factory.newWorkflowService();
}
return wfsCache;
}
}
/**
* Default initialization method.
*
* @param servletConfig
* a <code>ServletConfig</code> value
* @exception ServletException
* if an error occurs
*/
public void init(ServletConfig servletConfig) throws ServletException {
ctx = servletConfig.getServletContext();
try {
observerRegistry = new ObserverRegistry();
ctx.setAttribute(OBSERVER_REGISTRY, observerRegistry);
} catch (NamingException e) {
throw new ServletException (e);
}
// Read configuration values and overwrite default, if set
if (servletConfig != null) {
senderBase = servletConfig.getInitParameter(SENDER_BASE);
String applicationPolicy
= servletConfig.getInitParameter("ApplicationPolicy");
String userName
= servletConfig.getInitParameter("UserName");
String password
= servletConfig.getInitParameter("Password");
// The workflow engine may not yet be available, when the servlet
// is started. Repeat trying to subscribe until we are successful.
auditHandlerThread
= new AuditHandlerThread(applicationPolicy, userName, password);
auditHandlerThread.start();
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.GenericServlet#destroy()
*/
public void destroy() {
if (auditHandlerThread != null) {
auditHandlerThread.terminate();
try {
auditHandlerThread.join();
} catch (InterruptedException e) {
// deliberately ignored
}
auditHandlerThread = null;
}
if (eventSubscriber != null) {
wfsCache.release(eventSubscriber);
eventSubscriber = null;
}
if (wfsCache != null) {
wfsCache.release(wfsCache);
wfsCache = null;
}
super.destroy();
}
/**
* Retrieves the sender base.
*
* <p>
* The sender base is derived from the request. The this value may be
* overridden via the <code>SenderBase</code> parameter in the
* <code>web.xml</code>.
* </p>
* @param request the servlet request.
* @return the sender base.
*/
String getSenderBase(HttpServletRequest request) {
if (senderBase != null && senderBase.length() > 0) {
return senderBase;
}
return getRequestBasePath(request);
}
/**
* Create a new process and forward initial response (e.g. HTML start page).
* See user manual (chapter "tools") for a detailed description.
*
* @param request
* a <code>HttpServletRequest</code> value
* @param response
* a <code>HttpServletResponse</code> value
* @exception ServletException
* if an error occurs
* @exception IOException
* if an error occurs
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String requestUrl = request.getRequestURL().toString();
String query = request.getQueryString();
if ((query != null) && query.equalsIgnoreCase("wsdl")) {
getWsdl(request, response);
} else if (requestUrl.endsWith(".xsd")
|| requestUrl.endsWith(".wsdl")) {
getFile(request, response);
} else {
doPost(request, response);
}
}
/**
* Returns the contents of the requested file encoded as
* <code>text/plain</code>.
*
* @param request
* @param response
* @throws IOException
* Error accessing the file.
*/
private void getWsdl(HttpServletRequest request,
HttpServletResponse response) throws IOException {
WSDLFactory factory = new WSDLFactoryImpl();
WSDLReader reader = factory.newWSDLReader();
WSDLWriter writer = factory.newWSDLWriter();
String wsdllocation = ctx.getRealPath("/wfxml.wsdl");
File file = new File(wsdllocation);
try {
Definition definition = reader.readWSDL(file.toURI().getPath());
String requestBasePath = getRequestBasePath(request);
if (requestBasePath.endsWith("/")) {
requestBasePath
= requestBasePath.substring(0, requestBasePath.length() -1);
}
definition.setDocumentBaseURI(requestBasePath);
adjustReferencedElements(definition, requestBasePath);
addWsdlService(request, definition, "WfXmlServiceRegistry",
"WfMOpenServiceRegistry", "WfMOpenServiceRegistryService");
addWsdlService(request, definition, "WfXmlFactory",
"WfMOpenFactory", "WfMOpenFactoryService");
addWsdlService(request, definition, "WfXmlInstance",
"WfMOpenInstance", "WfMOpenInstanceService");
addWsdlService(request, definition, "WfXmlActivity",
"WfMOpenActivity", "WfMOpenActivityService");
writer.writeWSDL(definition, response.getOutputStream());
} catch (WSDLException e) {
throw new IOException(e.getMessage());
}
}
/**
* Adjust the import definitions to the path under which we were called.
* This is necessary, since the referenced resources will be retrieved
* from a package above the servlet directory, if the servlet
* is called with <code>http://.../wfxml?wsdl</code>.
* @param definition the WSDL definition.
* @param requestBasePath the base path.
*/
private void adjustReferencedElements(Definition definition,
String requestBasePath) {
List imports = definition
.getImports("http://www.oasis-open.org/asap/0.9/asap.wsdl");
Import asapImport = (Import) imports.get(0);
asapImport.setLocationURI(requestBasePath + "/asap.wsdl");
// TODO: The types are not properly written to the definition.
Types types = definition.getTypes();
List typeList = types.getExtensibilityElements();
Iterator iterator = typeList.iterator();
while (iterator.hasNext()) {
Object o = iterator.next();
if (o instanceof Schema) {
Schema schema = (Schema)o;
Map map = schema.getImports();
List schemaImports = (List)
map.get("http://www.wfmc.org/wfxml/2.0/wfxml20.xsd");
SchemaImport schemaImport = (SchemaImport) schemaImports.get(0);
Schema ref = schemaImport.getReferencedSchema();
schemaImport
.setSchemaLocationURI(requestBasePath + "/wfxml20.xsd");
}
}
}
/**
* Adds a service description to the WSDL definition.
* @param request
* @param definition
*/
private void addWsdlService(HttpServletRequest request,
Definition definition, String bindingName, String portName,
String serviceName) {
Service service = definition.createService();
Port port = definition.createPort();
QName name = new QName
("http://wfmopen.sourceforge.net/wfxml20.wsdl", bindingName, "");
Binding binding = definition.getBinding(name);
port.setName(portName);
port.setBinding(binding);
service.setQName
(new QName ("http://wfmopen.sourceforge.net/wfxml20.wsdl",
serviceName, "tns"));
service.addPort(port);
SOAPAddress address = new SOAPAddressImpl();
address.setLocationURI(getRequestBasePath(request));
port.addExtensibilityElement(address);
definition.addService(service);
}
/**
* Returns the contents of the requested file encoded as
* <code>text/plain</code>.
*
* @param request
* @param response
* @throws IOException
* Error accessing the file.
*/
private void getFile(HttpServletRequest request,
HttpServletResponse response) throws IOException {
ServletOutputStream out = response.getOutputStream();
// Get the file to view
String file = request.getPathTranslated();
// No file, nothing to view
if (file == null) {
out.println("No file to view");
return;
}
// Get and set the type of the file
response.setContentType("text/plain");
// Return the file
try {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
byte[] buf = new byte[4 * 1024]; // 4K buffer
int bytesRead;
while ((bytesRead = fis.read(buf)) != -1) {
out.write(buf, 0, bytesRead);
}
} finally {
if (fis != null) {
fis.close();
}
}
} catch (FileNotFoundException e) {
out.println("File not found");
}
}
/**
* Receive HTTP requests for a channel based access to a workflow process.
* See user manual (chapter "tools") for a detailed description.
*
* @param request
* a <code>HttpServletRequest</code> value
* @param response
* a <code>HttpServletResponse</code> value
* @exception ServletException
* if an error occurs
* @exception IOException
* if an error occurs
*/
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(true);
MessageFactory messageFactory = null;
try {
messageFactory = MessageFactory.newInstance();
} catch (SOAPException e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Unable to access SOAP subsystem: " + e.getMessage());
return;
}
SOAPMessage respMsg = null;
WorkflowService wfs = null;
// workaround JaWE bug
boolean agentIsJaWE = false;
Throwable fatalProblem = null;
try {
// get mime request headers for building request structure
MimeHeaders mimeHeaders = new MimeHeaders();
for (Enumeration en = request.getHeaderNames(); en
.hasMoreElements();) {
String headerName = (String) en.nextElement();
String headerVal = request.getHeader(headerName);
if (headerName.equalsIgnoreCase("user-agent")
&& headerVal.startsWith("JaWE")) {
agentIsJaWE = true;
}
StringTokenizer tk = new StringTokenizer(headerVal, ",");
while (tk.hasMoreTokens()) {
mimeHeaders.addHeader(headerName, tk.nextToken().trim());
}
}
// build request message from headers and stream
SOAPMessage reqMsg = messageFactory.createMessage(mimeHeaders,
request.getInputStream());
while (true) {
try {
respMsg = messageFactory.createMessage();
wfs = getWorkflowService();
if (wfs == null) {
SOAPFault fault = respMsg.getSOAPPart()
.getEnvelope().getBody().addFault();
fault.setFaultString
("Unable to access workflow service");
return;
}
AbstractResponseGenerator respgen = getResponseGenerator
(getSenderBase(request), reqMsg, wfs);
respgen.fillResponseHeader(reqMsg, respMsg);
respgen.evaluate(reqMsg, respMsg);
break;
} catch (RemoteException e) {
logger.debug("Problem while trying to create response "
+ "(retrying): " + e.getMessage(), e);
}
}
} catch (SOAPException e) {
fatalProblem = e;
} catch (FactoryConfigurationError e) {
fatalProblem = e;
} catch (ASAPException e) {
fatalProblem = e;
} catch (InvalidSearchFilterException e) {
fatalProblem = e;
} finally {
if (fatalProblem != null) {
if (logger.isDebugEnabled()) {
logger.debug("error executing the client request",
fatalProblem);
}
try {
respMsg = messageFactory.createMessage();
if (fatalProblem instanceof ASAPException) {
ASAPException cause = (ASAPException) fatalProblem;
FaultUtils.setFault
(respMsg, cause.getErrorCode(),
cause.getMessage());
} else {
FaultUtils.setFault(respMsg, fatalProblem);
}
} catch (SOAPException ee) {
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Unable to use SOAP subsystem: " + ee.getMessage());
return;
}
}
}
sendResponse(response, respMsg, agentIsJaWE);
}
/**
* Retrieves an {@link AbstractResponseGenerator} instance that is capable
* to handle the current request.
*
* <p>
* The response generator is determined through evaluation of the
* <code><as:RecieverKey></code> in the header of the request
* message
* </p>
*
* @param senderBase the base for constructing the sender key
* @param reqMsg
* the SOAP request.
* @param wfs
* reference to the workflow engine.
*
* @return Response generator to handle the current request.
* @throws SOAPException
* error evaluating the SOAP header.
* @throws FileNotFoundException
* error obtaining the sender base.
*/
private AbstractResponseGenerator getResponseGenerator
(String senderBase, SOAPMessage reqMsg, WorkflowService wfs)
throws SOAPException, ASAPException, InvalidSearchFilterException {
// Determine the base request URL as a basis for referenced
// resources.
String receiverKey = null;
receiverKey = getReceiverKey(reqMsg);
ResourceReference resRef
= new ResourceReference(senderBase, receiverKey);
ObserverRegistry observerRegistry
= (ObserverRegistry)ctx.getAttribute(OBSERVER_REGISTRY);
String resource = resRef.getResource();
if (resource.equals
(AbstractResponseGenerator.RESOURCE_SERVICE_REGISTRY)) {
return new ServiceRegistryResponseGenerator
(observerRegistry, wfs, resRef);
}
if (resource.equals(AbstractResponseGenerator.RESOURCE_FACTORY)) {
return new FactoryResponseGenerator(observerRegistry, wfs, resRef);
}
if (resource.equals(AbstractResponseGenerator.RESOURCE_INSTANCE)) {
return new InstanceResponseGenerator(observerRegistry, wfs, resRef);
}
if (resource.equals(AbstractResponseGenerator.RESOURCE_ACTIVITY)) {
return new ActivityResponseGenerator(observerRegistry, wfs, resRef);
}
// This should not happen, since we trap into an exception, trying
// to obtain the resource string.
throw new ASAPException(ASAPException.ASAP_INVALID_FACTORY,
receiverKey + " cannot be mapped to a known resource.");
}
/**
* Retrieves the base path that was used to call this servlet. Since this
* servlet may be called with the same URL as it is specified in the
* <code>SenderKey</code> of the ASAP header, we have to determine the
* base path without any extra path info to create the responses.
* @param request the request.
* @return base path.
*/
private String getRequestBasePath(HttpServletRequest request) {
String requestUrl = request.getRequestURL().toString();
String servletPath = request.getPathInfo();
// Some implementations may return "/" if there is no extra path info
// available, although they should return null in that case.
if ((servletPath == null) || (servletPath.length() <= 1)) {
return requestUrl;
}
return requestUrl.substring(0, requestUrl.length()
- servletPath.length());
}
/**
* Reads the <code>ReceiverKey</code> from the request header of the given
* message.
*
* @param message
* the message to inspect.
* @return value of the <code>ReceiverKey</code>.
* @throws SOAPException
* error evaluating the message.
*/
private String getReceiverKey(SOAPMessage message)
throws SOAPException, ASAPException {
SOAPHeaderElement headerElement = null;
SOAPHeader header = message.getSOAPHeader();
for (Iterator i = header.getChildElements(); i.hasNext();) {
Node node = (Node) i.next();
if (node instanceof SOAPHeaderElement) {
headerElement = (SOAPHeaderElement) node;
Name headerElementName = headerElement.getElementName();
String localName = headerElementName.getLocalName();
if (localName.equals(Consts.REQUEST_HEADER)) {
String headerUri = headerElementName.getURI();
if (headerUri.equals(Consts.ASAP_NS)
|| headerUri.equals(Consts.WFXML_NS)) {
break;
}
}
}
}
if (headerElement == null) {
throw new ASAPException(ASAPException.ASAP_ELEMENT_MISSING,
"ASAP request header not found.");
}
for (Iterator i = headerElement.getChildElements(); i.hasNext();) {
Node node = (Node) i.next();
if (node instanceof SOAPElement) {
SOAPElement element = (SOAPElement) node;
String name = element.getElementName().getLocalName();
if (name.equals(Consts.RECEIVER_KEY)) {
return XMLUtil.getFirstLevelTextContent(element);
}
}
}
throw new ASAPException(ASAPException.ASAP_ELEMENT_MISSING,
Consts.SENDER_KEY + " must be specified in the header");
}
/**
* Sends the response.
*
* @param response
* the servlet's response.
* @param respMsg
* the SOAP response message.
* @param agentIsJaWE
* <code>true</code> if this servlet was called by JaWE.
* @throws IOException
* Error accessing the output stream.
*/
private void sendResponse(HttpServletResponse response,
SOAPMessage respMsg, boolean agentIsJaWE) throws IOException {
response.setHeader("Content-Type", "text/xml");
OutputStream os = response.getOutputStream();
try {
if (!agentIsJaWE) {
respMsg.writeTo(os);
} else {
// workaround JaWE bug. JaWE cannot handle pretty
// printed XML
try {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
t.transform(new DOMSource(respMsg.getSOAPPart()
.getEnvelope()), new StreamResult(os));
} catch (TransformerConfigurationException ee) {
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Cannot get transformer: " + ee.getMessage());
return;
} catch (TransformerException ee) {
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Cannot transform: " + ee.getMessage());
return;
}
}
} catch (SOAPException ee) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Unable to write SOAP response: " + ee.getMessage());
return;
}
os.flush();
}
/**
* This class creates a workflow engine reference, and, when available
* receives events and forwards them to the WfXmlAuditHandler.
*
* @author Dirk Schnelle
* @author Michael Lipp
*/
private class AuditHandlerThread extends Thread {
private String applicationPolicy;
private String userName;
private String password;
private boolean running = true;
/**
* Constructs a new object.
*/
public AuditHandlerThread(String applicationPolicy, String userName,
String password) {
setDaemon(true);
this.applicationPolicy = applicationPolicy;
this.userName = userName;
this.password = password;
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
public void run() {
try {
LoginContext loginContext
= new AuditHandlerLoginContext
(applicationPolicy, userName, password);
loginContext.login ();
} catch (LoginException e) {
logger.error("login error", e);
return;
}
WorkflowService wfs = null;
while (running && wfs == null) {
try {
wfs = getWorkflowService();
} catch (FactoryConfigurationError fce) {
try {
sleep(500);
} catch (InterruptedException e) {
return;
}
}
}
try {
eventSubscriber = wfs.createEventSubscriber();
} catch (IOException e) {
logger.error
("Cannot create event subscriber: " + e.getMessage(), e);
return;
}
// Now handle events
WfXmlAuditHandler handler
= new WfXmlAuditHandler (wfs, observerRegistry);
while (running) {
try {
WfAuditEvent event = eventSubscriber.receive();
handler.receiveEvent(event);
} catch (Exception e) {
logger.warn
("Cannot handle event (ignored): " + e.getMessage());
}
}
}
/**
* Preliminarily terminate the running thread.
*/
public void terminate () {
running = false;
interrupt ();
}
}
/**
* Simple login context for authentication.
* @author Dirk Schnelle
*/
private static class AuditHandlerLoginContext extends LoginContext {
private static class CBH implements CallbackHandler {
private String userName = null;
private String password = null;
public CBH (String userName, String password) {
this.userName = userName;
this.password = password;
}
public void handle (Callback[] callbacks)
throws UnsupportedCallbackException, IOException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof TextOutputCallback) {
// display the message according to the specified type
TextOutputCallback toc
= (TextOutputCallback)callbacks[i];
switch (toc.getMessageType()) {
case TextOutputCallback.INFORMATION:
if (logger.isInfoEnabled()) {
logger.info(toc.getMessage());
}
break;
case TextOutputCallback.ERROR:
logger.error("ERROR: " + toc.getMessage());
break;
case TextOutputCallback.WARNING:
logger.warn("WARNING: " + toc.getMessage());
break;
default:
throw new IOException
("Unsupported message type: "
+ toc.getMessageType());
}
} else if (callbacks[i] instanceof NameCallback) {
// prompt the user for a username
NameCallback nc = (NameCallback)callbacks[i];
nc.setName(userName);
} else if (callbacks[i] instanceof PasswordCallback) {
// prompt the user for sensitive information
PasswordCallback pc = (PasswordCallback)callbacks[i];
pc.setPassword(password.toCharArray());
} else if (callbacks[i].getClass().getName().equals
("weblogic.security.auth.callback.URLCallback")) {
// deliberately ignored.
} else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback \""
+ callbacks[i].getClass().getName() + "\"");
}
}
}
}
public AuditHandlerLoginContext
(String applicationPolicy, String userName, String password)
throws LoginException {
super (applicationPolicy, new CBH(userName, password));
}
}
}