/*
* This file is part of the WfMOpen project.
* 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: ResourceReference.java 2576 2007-11-02 16:00:34Z drmlipp $
*
* $Log$
* Revision 1.9 2007/09/26 20:24:22 mlipp
* Removed superfluous import.
*
* Revision 1.8 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.7 2007/03/27 21:59:42 mlipp
* Fixed lots of checkstyle warnings.
*
* Revision 1.6 2007/03/01 12:32:57 schnelle
* Enhanced Instance.SetProperties to process ContextData.
*
* Revision 1.5 2007/02/01 13:44:43 schnelle
* Using namespace for factory schemas that do not contain '&'.
*
* Revision 1.4 2007/02/01 10:08:36 drmlipp
* Removed no longer used observer resource.
*
* Revision 1.3 2007/01/31 22:55:36 mlipp
* Some more refactoring and fixes of problems introduced by refactoring.
*
* Revision 1.2 2007/01/31 14:53:06 schnelle
* Small corrections wvaluating the resource reference.
*
* Revision 1.1 2007/01/31 12:24:05 drmlipp
* Design revisited.
*
* Revision 1.3 2007/01/30 13:11:37 schnelle
* Corrected check to decide the sending of a completed message.
*
* Revision 1.2 2007/01/30 11:56:14 drmlipp
* Merged Wf-XML branch.
*
* Revision 1.1.2.1 2007/01/29 15:04:23 schnelle
* Renaming of Observer to ObserverRegistry and URIDecoder to ResourceReference.
*
* Revision 1.1.2.7 2007/01/29 13:40:31 schnelle
* Storing of the sender base in the servlet context.
*
* Revision 1.1.2.6 2007/01/29 10:48:28 schnelle
* Using a key-value encoding of the parameters instead of the directory approach.
*
* Revision 1.1.2.5 2007/01/26 15:50:28 schnelle
* Added encoding for process id and package id.
*
* Revision 1.1.2.4 2007/01/24 14:22:38 schnelle
* Observer handler starts on servlet startup.
*
* Revision 1.1.2.3 2007/01/24 11:46:35 schnelle
* Moved wsdl files and xsd files intot resources subdirectory.
*
* Revision 1.1.2.2 2007/01/24 10:56:50 schnelle
* Prepared return of a result for aobservers.
*
* Revision 1.1.2.1 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.
*
*/
package de.danet.an.workflow.clients.wfxml;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.Process;
import de.danet.an.workflow.api.ProcessDefinition;
/**
* This class wraps the URI encoding and decoding of the
* <code>ReceiverKey</code> that is used in the ASAP header. Moreover is it
* used to retrieve the attributes that were given in a request to this
* service.
*
* <p>
* The <code>ReceiverKey</code> is a {@link java.net.URI} with the following
* structure
* <code>
* <schema:>//<authority>/<base path>[?<query>]
* </code>
* </p>
*
* <p>
* The parts up to <code>base path</code> describe the path to this service.
* For the servlet this is e.g. <code>http://localhost:8080/wfxml</code>.
* </p>
*
* <p>
* All parts that are needed to identify the specific instance are encoded in
* the <code>query</code> part of the URI as key-value pairs.
* The <code>instance</code> is given as such a query parameter and names the
* addressed resource instance of the request. If it is omitted the
* <code>Service Registry</code> is taken as the instance.
* A request to the
* <code>Factory</code> for the process <code>proc</code> that is described in
* package <code>pkg</code> is made using the following
* <code>ReceiverKey</code>, (assuming the base path above):<br>
* <code>
* http://localhost:8080/wfxml?Resource=Factory&PackageId=pkg&ProcessId=proc
* </code>
* </p>
*
* TODO: Add the unique identifier for the workflow engine.
*
* @author Dirk Schnelle
*
*/
class ResourceReference {
/** Logger instance. */
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog(ResourceReference.class);
/** The used encoding. */
private static final String UTF_8 = "UTF-8";
/** Name of the attribute for the instance. */
private static final String ATTRIBUTE_RESOURCE = "Resource";
/** Name of the attribute for the package id. */
private static final String ATTRIBUTE_PACKAGE_ID = "PackageId";
/** Name of the attribute for the process id. */
private static final String ATTRIBUTE_PROCESS_ID = "ProcessId";
/** Name of the attribute for the process key. */
private static final String ATTRIBUTE_PROCESS_KEY = "ProcessKey";
/** Name of the attribute for the activity key. */
private static final String ATTRIBUTE_ACTIVITY_KEY = "ActivityKey";
/** The base url of this WfXML server. */
private String baseUrl;
/** Name of the resource. */
private String resource;
/** Name of the package id. */
private String packageId;
/** Name of the process id. */
private String processId;
/** Name of the process key. */
private String processKey;
/** Name of the activity key. */
private String activityKey;
/**
* Create a new instance that references a process definition.
*
* @param baseUrl the sender base
* @param procDef the process definition
*/
public ResourceReference(String baseUrl, ProcessDefinition procDef) {
if (baseUrl == null || procDef == null) {
throw new IllegalArgumentException();
}
this.baseUrl = baseUrl;
this.resource = AbstractResponseGenerator.RESOURCE_FACTORY;
this.packageId = procDef.packageId();
this.processId = procDef.processId();
}
/**
* Create a new instance that references an instance.
*
* @param baseUrl
* @param packageId
* @param processId
* @param processKey
*/
public ResourceReference
(String baseUrl,
String packageId, String processId, String processKey) {
if (baseUrl == null || packageId == null || processId == null
|| processKey == null) {
throw new IllegalArgumentException();
}
this.baseUrl = baseUrl;
this.resource = AbstractResponseGenerator.RESOURCE_INSTANCE;
this.packageId = packageId;
this.processId = processId;
this.processKey = processKey;
}
/**
* Create a new instance that references an instance.
*
* @param baseUrl
* @param process
*/
public ResourceReference (String baseUrl, Process process)
throws RemoteException {
if (baseUrl == null || process == null) {
throw new IllegalArgumentException();
}
this.baseUrl = baseUrl;
this.resource = AbstractResponseGenerator.RESOURCE_INSTANCE;
ProcessDefinition procDef = process.processDefinition();
this.packageId = procDef.packageId();
this.processId = procDef.processId();
this.processKey = process.key();
}
/**
* Create a new instance that references an activity.
*
* @param baseUrl the base URL
* @param activity the activity
*/
public ResourceReference (String baseUrl, Activity activity)
throws RemoteException {
if (baseUrl == null || activity == null) {
throw new IllegalArgumentException();
}
this.baseUrl = baseUrl;
this.resource = AbstractResponseGenerator.RESOURCE_ACTIVITY;
Process process = (Process)activity.container();
ProcessDefinition procDef = process.processDefinition();
this.packageId = procDef.packageId();
this.processId = procDef.processId();
this.processKey = process.key();
this.activityKey = activity.key();
}
/**
* Constructs a new object.
* @param baseUrl base url of the main servlet.
* @param key <code>ReceiverKey</code> of a request.
* @exception ASAPException
* Error parsing the receiver key.
*/
public ResourceReference(String baseUrl, String key)
throws ASAPException {
if (baseUrl == null) {
throw new IllegalArgumentException();
}
this.baseUrl = baseUrl;
parseReceiverKey(key);
if (resource.equals
(AbstractResponseGenerator.RESOURCE_SERVICE_REGISTRY)) {
// nothing required
return;
}
if (resource.equals (AbstractResponseGenerator.RESOURCE_FACTORY)) {
if (packageId == null || processId == null) {
throw new IllegalArgumentException("Invalid key: " + key);
}
return;
}
if (resource.equals(AbstractResponseGenerator.RESOURCE_INSTANCE)) {
if (packageId == null || processId == null || processKey == null) {
throw new IllegalArgumentException("Invalid key: " + key);
}
return;
}
if (resource.equals(AbstractResponseGenerator.RESOURCE_ACTIVITY)) {
if (packageId == null || processId == null
|| processKey == null || activityKey == null) {
throw new IllegalArgumentException("Invalid key: " + key);
}
return;
}
throw new ASAPException(ASAPException.ASAP_INVALID_FACTORY,
"Key '" + key + "' could not be mapped to a resource.");
}
/**
* Parses the <code>ReceiverKey</code> and extracts all addresses resources.
* @exception ASAPException
* Error parsing the receiver key.
*/
private void parseReceiverKey(String receiverKey)
throws ASAPException {
if (receiverKey == null) {
throw new IllegalArgumentException("Key may not be null");
}
URI uri;
try {
uri = new URI(receiverKey);
} catch (URISyntaxException e) {
throw new IllegalArgumentException
(receiverKey + " is not a valid URI: " + e.getMessage());
}
// The receiver key is always transferred in the SOAP envelope.
// Any character decoding will have happened already. And we
// don't give out keys or valued that contain "&" or "=".
String query = uri.getRawQuery();
String[] params = query.split("&");
Map parameters = new HashMap ();
for (int i = 0; i < params.length; i++) {
String param = params[i];
String[] keyValuePair = param.split("=");
try {
String value = URLDecoder.decode(keyValuePair[1], UTF_8);
parameters.put(keyValuePair[0], value);
} catch (ArrayIndexOutOfBoundsException e) {
throw new ASAPException(ASAPException.ASAP_INVALID_KEY,
"Invalid receiver key: '" + receiverKey + "'");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Broken VM does not support UTF-8");
}
}
// Pre-set the resource. It may be overridden later.
resource = AbstractResponseGenerator.RESOURCE_SERVICE_REGISTRY;
resource = (String) parameters.get(ATTRIBUTE_RESOURCE);
packageId = (String) parameters.get(ATTRIBUTE_PACKAGE_ID);
processId = (String) parameters.get(ATTRIBUTE_PROCESS_ID);
processKey = (String) parameters.get(ATTRIBUTE_PROCESS_KEY);
activityKey = (String) parameters.get(ATTRIBUTE_ACTIVITY_KEY);
}
/**
* Retrieves the name of the resource to which the receiver key points to.
* @return name of the resource.
*/
public String getResource() {
return resource;
}
/**
* Retrieves the base URL of this WfXML server.
* @return base URL.
*/
public String getBaseUrl() {
return baseUrl;
}
/**
* Retrieves the package id as it is specified in the
* <code>ReceiverKey</code>.
* @return package id.
*/
public String getPackageId() {
if (packageId == null) {
throw new IllegalStateException("Resource has no packaged id.");
}
return packageId;
}
/**
* Retrieves the process id as it is specified in the
* <code>ReceiverKey</code>.
* @return process id.
*/
public String getProcessId() {
if (processId == null) {
throw new IllegalStateException("Resource has no process id.");
}
return processId;
}
/**
* Retrieves the process key as it is specified in the
* <code>ReceiverKey</code>.
* @return process id.
*/
public String getProcessKey() {
if (processKey == null) {
throw new IllegalStateException("Resource has no process key.");
}
return processKey;
}
/**
* Retrieves the activity key as it is specified in the
* <code>ReceiverKey</code>.
* @return activity key.
*/
public String getActivityKey() {
if (activityKey == null) {
throw new IllegalStateException("Resource has no activity key.");
}
return activityKey;
}
/**
* Retrieves the sender key.
* @return sender key for the observer.
*/
public String getResourceKey() {
StringBuffer res = new StringBuffer (baseUrl);
if (res.indexOf("?") < 0) {
res.append("?");
} else {
res.append("&");
}
res.append(ATTRIBUTE_RESOURCE + "=" + resource);
appendParameter(res, ATTRIBUTE_PACKAGE_ID, packageId);
appendParameter(res, ATTRIBUTE_PROCESS_ID, processId);
appendParameter(res, ATTRIBUTE_PROCESS_KEY, processKey);
appendParameter(res, ATTRIBUTE_ACTIVITY_KEY, activityKey);
return res.toString();
}
/**
* Adds the given parameter to the given url.
* @param url the base url.
* @param
* @return url with parameters if any.
*/
private void appendParameter(StringBuffer url, String key, String value) {
if (value == null) {
return;
}
url.append("&" + key + "=" + encode (value));
}
/**
* Generates a namespace for the current resource.
*
* <p>
* Namespaces are defined for the factories and are used in the instances.
* </p>
* @return namespace.
*/
public String getNamespace() {
if(resource.equals(AbstractResponseGenerator.RESOURCE_SERVICE_REGISTRY)
|| resource.equals(AbstractResponseGenerator.RESOURCE_ACTIVITY)) {
throw new IllegalStateException("Namespaces can only be generated"
+ " for factory and instance");
}
StringBuffer str = new StringBuffer (baseUrl);
// Since we are in the world of the factory, we use the values of
// the factory.
str.append('/');
str.append(AbstractResponseGenerator.RESOURCE_FACTORY);
str.append('/');
str.append(packageId);
str.append('/');
str.append(processId);
return str.toString();
}
/**
* Translates a string into <code>application/x-www-form-urlencoded</code>
* format using the <code>UTF-8</code> encoding scheme.
* @param str the string to encode.
* @return <code>UTF-8</code> encoded string.
*/
private String encode(String str) {
try {
return URLEncoder.encode(str, UTF_8);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Broken VM does not support UTF-8");
}
}
}