001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2006 Danet GmbH (www.danet.de), BU BTS.
004: * All rights reserved.
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * $Id: ResourceReference.java,v 1.8.2.1 2007/11/02 16:00:33 drmlipp Exp $
021: *
022: * $Log: ResourceReference.java,v $
023: * Revision 1.8.2.1 2007/11/02 16:00:33 drmlipp
024: * Merged bug fixes from HEAD.
025: *
026: * Revision 1.9 2007/09/26 20:24:22 mlipp
027: * Removed superfluous import.
028: *
029: * Revision 1.8 2007/03/29 11:46:54 schnelle
030: * Reactivated ASAPException to propagate ASAP error messages in cases of an invalid key, a missing resource or an invalid factory.
031: *
032: * Revision 1.7 2007/03/27 21:59:42 mlipp
033: * Fixed lots of checkstyle warnings.
034: *
035: * Revision 1.6 2007/03/01 12:32:57 schnelle
036: * Enhanced Instance.SetProperties to process ContextData.
037: *
038: * Revision 1.5 2007/02/01 13:44:43 schnelle
039: * Using namespace for factory schemas that do not contain '&'.
040: *
041: * Revision 1.4 2007/02/01 10:08:36 drmlipp
042: * Removed no longer used observer resource.
043: *
044: * Revision 1.3 2007/01/31 22:55:36 mlipp
045: * Some more refactoring and fixes of problems introduced by refactoring.
046: *
047: * Revision 1.2 2007/01/31 14:53:06 schnelle
048: * Small corrections wvaluating the resource reference.
049: *
050: * Revision 1.1 2007/01/31 12:24:05 drmlipp
051: * Design revisited.
052: *
053: * Revision 1.3 2007/01/30 13:11:37 schnelle
054: * Corrected check to decide the sending of a completed message.
055: *
056: * Revision 1.2 2007/01/30 11:56:14 drmlipp
057: * Merged Wf-XML branch.
058: *
059: * Revision 1.1.2.1 2007/01/29 15:04:23 schnelle
060: * Renaming of Observer to ObserverRegistry and URIDecoder to ResourceReference.
061: *
062: * Revision 1.1.2.7 2007/01/29 13:40:31 schnelle
063: * Storing of the sender base in the servlet context.
064: *
065: * Revision 1.1.2.6 2007/01/29 10:48:28 schnelle
066: * Using a key-value encoding of the parameters instead of the directory approach.
067: *
068: * Revision 1.1.2.5 2007/01/26 15:50:28 schnelle
069: * Added encoding for process id and package id.
070: *
071: * Revision 1.1.2.4 2007/01/24 14:22:38 schnelle
072: * Observer handler starts on servlet startup.
073: *
074: * Revision 1.1.2.3 2007/01/24 11:46:35 schnelle
075: * Moved wsdl files and xsd files intot resources subdirectory.
076: *
077: * Revision 1.1.2.2 2007/01/24 10:56:50 schnelle
078: * Prepared return of a result for aobservers.
079: *
080: * Revision 1.1.2.1 2007/01/19 12:34:56 schnelle
081: * Moved generation and decoding of the URI that is used as the receiver key to new class URIDecoder.
082: *
083: */
084: package de.danet.an.workflow.clients.wfxml;
085:
086: import java.io.UnsupportedEncodingException;
087: import java.net.URI;
088: import java.net.URISyntaxException;
089: import java.net.URLDecoder;
090: import java.net.URLEncoder;
091: import java.rmi.RemoteException;
092: import java.util.HashMap;
093: import java.util.Map;
094:
095: import de.danet.an.workflow.api.Activity;
096: import de.danet.an.workflow.api.Process;
097: import de.danet.an.workflow.api.ProcessDefinition;
098:
099: /**
100: * This class wraps the URI encoding and decoding of the
101: * <code>ReceiverKey</code> that is used in the ASAP header. Moreover is it
102: * used to retrieve the attributes that were given in a request to this
103: * service.
104: *
105: * <p>
106: * The <code>ReceiverKey</code> is a {@link java.net.URI} with the following
107: * structure
108: * <code>
109: * <schema:>//<authority>/<base path>[?<query>]
110: * </code>
111: * </p>
112: *
113: * <p>
114: * The parts up to <code>base path</code> describe the path to this service.
115: * For the servlet this is e.g. <code>http://localhost:8080/wfxml</code>.
116: * </p>
117: *
118: * <p>
119: * All parts that are needed to identify the specific instance are encoded in
120: * the <code>query</code> part of the URI as key-value pairs.
121: * The <code>instance</code> is given as such a query parameter and names the
122: * addressed resource instance of the request. If it is omitted the
123: * <code>Service Registry</code> is taken as the instance.
124: * A request to the
125: * <code>Factory</code> for the process <code>proc</code> that is described in
126: * package <code>pkg</code> is made using the following
127: * <code>ReceiverKey</code>, (assuming the base path above):<br>
128: * <code>
129: * http://localhost:8080/wfxml?Resource=Factory&PackageId=pkg&ProcessId=proc
130: * </code>
131: * </p>
132: *
133: * TODO: Add the unique identifier for the workflow engine.
134: *
135: * @author Dirk Schnelle
136: *
137: */
138: class ResourceReference {
139: /** Logger instance. */
140: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
141: .getLog(ResourceReference.class);
142:
143: /** The used encoding. */
144: private static final String UTF_8 = "UTF-8";
145:
146: /** Name of the attribute for the instance. */
147: private static final String ATTRIBUTE_RESOURCE = "Resource";
148:
149: /** Name of the attribute for the package id. */
150: private static final String ATTRIBUTE_PACKAGE_ID = "PackageId";
151:
152: /** Name of the attribute for the process id. */
153: private static final String ATTRIBUTE_PROCESS_ID = "ProcessId";
154:
155: /** Name of the attribute for the process key. */
156: private static final String ATTRIBUTE_PROCESS_KEY = "ProcessKey";
157:
158: /** Name of the attribute for the activity key. */
159: private static final String ATTRIBUTE_ACTIVITY_KEY = "ActivityKey";
160:
161: /** The base url of this WfXML server. */
162: private String baseUrl;
163:
164: /** Name of the resource. */
165: private String resource;
166:
167: /** Name of the package id. */
168: private String packageId;
169:
170: /** Name of the process id. */
171: private String processId;
172:
173: /** Name of the process key. */
174: private String processKey;
175:
176: /** Name of the activity key. */
177: private String activityKey;
178:
179: /**
180: * Create a new instance that references a process definition.
181: *
182: * @param baseUrl the sender base
183: * @param procDef the process definition
184: */
185: public ResourceReference(String baseUrl, ProcessDefinition procDef) {
186: if (baseUrl == null || procDef == null) {
187: throw new IllegalArgumentException();
188: }
189: this .baseUrl = baseUrl;
190: this .resource = AbstractResponseGenerator.RESOURCE_FACTORY;
191: this .packageId = procDef.packageId();
192: this .processId = procDef.processId();
193: }
194:
195: /**
196: * Create a new instance that references an instance.
197: *
198: * @param baseUrl
199: * @param packageId
200: * @param processId
201: * @param processKey
202: */
203: public ResourceReference(String baseUrl, String packageId,
204: String processId, String processKey) {
205: if (baseUrl == null || packageId == null || processId == null
206: || processKey == null) {
207: throw new IllegalArgumentException();
208: }
209: this .baseUrl = baseUrl;
210: this .resource = AbstractResponseGenerator.RESOURCE_INSTANCE;
211: this .packageId = packageId;
212: this .processId = processId;
213: this .processKey = processKey;
214: }
215:
216: /**
217: * Create a new instance that references an instance.
218: *
219: * @param baseUrl
220: * @param process
221: */
222: public ResourceReference(String baseUrl, Process process)
223: throws RemoteException {
224: if (baseUrl == null || process == null) {
225: throw new IllegalArgumentException();
226: }
227: this .baseUrl = baseUrl;
228: this .resource = AbstractResponseGenerator.RESOURCE_INSTANCE;
229: ProcessDefinition procDef = process.processDefinition();
230: this .packageId = procDef.packageId();
231: this .processId = procDef.processId();
232: this .processKey = process.key();
233: }
234:
235: /**
236: * Create a new instance that references an activity.
237: *
238: * @param baseUrl the base URL
239: * @param activity the activity
240: */
241: public ResourceReference(String baseUrl, Activity activity)
242: throws RemoteException {
243: if (baseUrl == null || activity == null) {
244: throw new IllegalArgumentException();
245: }
246: this .baseUrl = baseUrl;
247: this .resource = AbstractResponseGenerator.RESOURCE_ACTIVITY;
248: Process process = (Process) activity.container();
249: ProcessDefinition procDef = process.processDefinition();
250: this .packageId = procDef.packageId();
251: this .processId = procDef.processId();
252: this .processKey = process.key();
253: this .activityKey = activity.key();
254: }
255:
256: /**
257: * Constructs a new object.
258: * @param baseUrl base url of the main servlet.
259: * @param key <code>ReceiverKey</code> of a request.
260: * @exception ASAPException
261: * Error parsing the receiver key.
262: */
263: public ResourceReference(String baseUrl, String key)
264: throws ASAPException {
265: if (baseUrl == null) {
266: throw new IllegalArgumentException();
267: }
268: this .baseUrl = baseUrl;
269:
270: parseReceiverKey(key);
271: if (resource
272: .equals(AbstractResponseGenerator.RESOURCE_SERVICE_REGISTRY)) {
273: // nothing required
274: return;
275: }
276:
277: if (resource.equals(AbstractResponseGenerator.RESOURCE_FACTORY)) {
278: if (packageId == null || processId == null) {
279: throw new IllegalArgumentException("Invalid key: "
280: + key);
281: }
282: return;
283: }
284:
285: if (resource
286: .equals(AbstractResponseGenerator.RESOURCE_INSTANCE)) {
287: if (packageId == null || processId == null
288: || processKey == null) {
289: throw new IllegalArgumentException("Invalid key: "
290: + key);
291: }
292: return;
293: }
294:
295: if (resource
296: .equals(AbstractResponseGenerator.RESOURCE_ACTIVITY)) {
297: if (packageId == null || processId == null
298: || processKey == null || activityKey == null) {
299: throw new IllegalArgumentException("Invalid key: "
300: + key);
301: }
302: return;
303: }
304:
305: throw new ASAPException(ASAPException.ASAP_INVALID_FACTORY,
306: "Key '" + key + "' could not be mapped to a resource.");
307: }
308:
309: /**
310: * Parses the <code>ReceiverKey</code> and extracts all addresses resources.
311: * @exception ASAPException
312: * Error parsing the receiver key.
313: */
314: private void parseReceiverKey(String receiverKey)
315: throws ASAPException {
316: if (receiverKey == null) {
317: throw new IllegalArgumentException("Key may not be null");
318: }
319: URI uri;
320: try {
321: uri = new URI(receiverKey);
322: } catch (URISyntaxException e) {
323: throw new IllegalArgumentException(receiverKey
324: + " is not a valid URI: " + e.getMessage());
325: }
326: // The receiver key is always transferred in the SOAP envelope.
327: // Any character decoding will have happened already. And we
328: // don't give out keys or valued that contain "&" or "=".
329: String query = uri.getRawQuery();
330: String[] params = query.split("&");
331: Map parameters = new HashMap();
332: for (int i = 0; i < params.length; i++) {
333: String param = params[i];
334: String[] keyValuePair = param.split("=");
335: try {
336: String value = URLDecoder
337: .decode(keyValuePair[1], UTF_8);
338: parameters.put(keyValuePair[0], value);
339: } catch (ArrayIndexOutOfBoundsException e) {
340: throw new ASAPException(ASAPException.ASAP_INVALID_KEY,
341: "Invalid receiver key: '" + receiverKey + "'");
342: } catch (UnsupportedEncodingException e) {
343: throw new RuntimeException(
344: "Broken VM does not support UTF-8");
345: }
346: }
347:
348: // Pre-set the resource. It may be overridden later.
349: resource = AbstractResponseGenerator.RESOURCE_SERVICE_REGISTRY;
350:
351: resource = (String) parameters.get(ATTRIBUTE_RESOURCE);
352: packageId = (String) parameters.get(ATTRIBUTE_PACKAGE_ID);
353: processId = (String) parameters.get(ATTRIBUTE_PROCESS_ID);
354: processKey = (String) parameters.get(ATTRIBUTE_PROCESS_KEY);
355: activityKey = (String) parameters.get(ATTRIBUTE_ACTIVITY_KEY);
356: }
357:
358: /**
359: * Retrieves the name of the resource to which the receiver key points to.
360: * @return name of the resource.
361: */
362: public String getResource() {
363: return resource;
364: }
365:
366: /**
367: * Retrieves the base URL of this WfXML server.
368: * @return base URL.
369: */
370: public String getBaseUrl() {
371: return baseUrl;
372: }
373:
374: /**
375: * Retrieves the package id as it is specified in the
376: * <code>ReceiverKey</code>.
377: * @return package id.
378: */
379: public String getPackageId() {
380: if (packageId == null) {
381: throw new IllegalStateException(
382: "Resource has no packaged id.");
383: }
384: return packageId;
385: }
386:
387: /**
388: * Retrieves the process id as it is specified in the
389: * <code>ReceiverKey</code>.
390: * @return process id.
391: */
392: public String getProcessId() {
393: if (processId == null) {
394: throw new IllegalStateException(
395: "Resource has no process id.");
396: }
397: return processId;
398: }
399:
400: /**
401: * Retrieves the process key as it is specified in the
402: * <code>ReceiverKey</code>.
403: * @return process id.
404: */
405: public String getProcessKey() {
406: if (processKey == null) {
407: throw new IllegalStateException(
408: "Resource has no process key.");
409: }
410: return processKey;
411: }
412:
413: /**
414: * Retrieves the activity key as it is specified in the
415: * <code>ReceiverKey</code>.
416: * @return activity key.
417: */
418: public String getActivityKey() {
419: if (activityKey == null) {
420: throw new IllegalStateException(
421: "Resource has no activity key.");
422: }
423:
424: return activityKey;
425: }
426:
427: /**
428: * Retrieves the sender key.
429: * @return sender key for the observer.
430: */
431: public String getResourceKey() {
432: StringBuffer res = new StringBuffer(baseUrl);
433: if (res.indexOf("?") < 0) {
434: res.append("?");
435: } else {
436: res.append("&");
437: }
438: res.append(ATTRIBUTE_RESOURCE + "=" + resource);
439: appendParameter(res, ATTRIBUTE_PACKAGE_ID, packageId);
440: appendParameter(res, ATTRIBUTE_PROCESS_ID, processId);
441: appendParameter(res, ATTRIBUTE_PROCESS_KEY, processKey);
442: appendParameter(res, ATTRIBUTE_ACTIVITY_KEY, activityKey);
443: return res.toString();
444: }
445:
446: /**
447: * Adds the given parameter to the given url.
448: * @param url the base url.
449: * @param
450: * @return url with parameters if any.
451: */
452: private void appendParameter(StringBuffer url, String key,
453: String value) {
454: if (value == null) {
455: return;
456: }
457: url.append("&" + key + "=" + encode(value));
458: }
459:
460: /**
461: * Generates a namespace for the current resource.
462: *
463: * <p>
464: * Namespaces are defined for the factories and are used in the instances.
465: * </p>
466: * @return namespace.
467: */
468: public String getNamespace() {
469: if (resource
470: .equals(AbstractResponseGenerator.RESOURCE_SERVICE_REGISTRY)
471: || resource
472: .equals(AbstractResponseGenerator.RESOURCE_ACTIVITY)) {
473: throw new IllegalStateException(
474: "Namespaces can only be generated"
475: + " for factory and instance");
476: }
477:
478: StringBuffer str = new StringBuffer(baseUrl);
479:
480: // Since we are in the world of the factory, we use the values of
481: // the factory.
482: str.append('/');
483: str.append(AbstractResponseGenerator.RESOURCE_FACTORY);
484: str.append('/');
485: str.append(packageId);
486: str.append('/');
487: str.append(processId);
488:
489: return str.toString();
490: }
491:
492: /**
493: * Translates a string into <code>application/x-www-form-urlencoded</code>
494: * format using the <code>UTF-8</code> encoding scheme.
495: * @param str the string to encode.
496: * @return <code>UTF-8</code> encoded string.
497: */
498: private String encode(String str) {
499: try {
500: return URLEncoder.encode(str, UTF_8);
501: } catch (UnsupportedEncodingException e) {
502: throw new RuntimeException(
503: "Broken VM does not support UTF-8");
504: }
505: }
506: }
|