001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
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: WorkflowServiceFactory.java,v 1.4 2007/03/22 13:41:50 schnelle Exp $
021: *
022: * $Log: WorkflowServiceFactory.java,v $
023: * Revision 1.4 2007/03/22 13:41:50 schnelle
024: * Typo.
025: *
026: * Revision 1.3 2006/09/29 12:32:07 drmlipp
027: * Consistently using WfMOpen as projct name now.
028: *
029: * Revision 1.2 2006/09/27 15:14:17 drmlipp
030: * Added option to configure workflow engine's initial context
031: * using default initial context.
032: *
033: * Revision 1.1.1.3 2004/08/18 15:17:36 drmlipp
034: * Update to 1.2
035: *
036: * Revision 1.8 2004/01/23 12:49:26 lipp
037: * Fixes to WorkflowService[Factory] implementation/documentation.
038: *
039: * Revision 1.7 2004/01/22 15:06:09 lipp
040: * Clarified serializability of workflow service.
041: *
042: * Revision 1.6 2003/11/21 14:54:40 lipp
043: * Support initial context override.
044: *
045: * Revision 1.5 2003/06/27 08:51:46 lipp
046: * Fixed copyright/license information.
047: *
048: * Revision 1.4 2002/12/19 16:23:46 lipp
049: * Resolved illegal dependency between apis and danet.an.util.
050: *
051: * Revision 1.3 2002/11/22 09:09:28 lipp
052: * Fixed comment.
053: *
054: * Revision 1.2 2002/10/15 13:22:32 huaiyang
055: * Remove system.out.println and printStackTrace.
056: *
057: * Revision 1.1 2002/09/18 13:00:26 lipp
058: * Renamed WorkflowEngine to WorkflowService and introduced
059: * WorkflowServiceFactory.
060: *
061: */
062: package de.danet.an.workflow.api;
063:
064: import java.io.BufferedReader;
065: import java.io.IOException;
066: import java.io.InputStreamReader;
067:
068: import java.util.ArrayList;
069: import java.util.Enumeration;
070: import java.util.HashMap;
071: import java.util.List;
072: import java.util.Map;
073: import java.util.Vector;
074:
075: import java.net.URL;
076:
077: import javax.naming.InitialContext;
078: import javax.naming.NamingException;
079:
080: /**
081: * This class provides a factory API that enables clients to obtain
082: * a workflow service facility.
083: *
084: * @author <a href="mailto:lipp@danet.de"></a>
085: * @version $Revision: 1.4 $
086: */
087: public abstract class WorkflowServiceFactory {
088:
089: /** The properties that have been set. */
090: private Map properties = new HashMap();
091:
092: /**
093: * Constructor. Must be overridden with a parameterless public
094: * constructor by derived class.
095: */
096: protected WorkflowServiceFactory() {
097: }
098:
099: /**
100: * Obtain a new instance of a
101: * <code>WorkflowServiceFactory</code>. This static method
102: * creates a new factory instance. The method uses the following
103: * ordered lookup procedure to determine the
104: * <code>WorkflowServiceFactory</code> implementation class
105: * to load:
106: * <ul>
107: * <li>If an {@link javax.naming.InitialContext initial naming context}
108: * is available, look for a a classname in
109: * <code>java:comp/env/de.danet.an.workflow.api.WorkflowServiceFactory</code>.
110: * The configuration for a class as workflow service factory
111: * thus looks like:
112: * <PRE><env-entry>
113: * <description>Configure the workflow service factory</description>
114: * <env-entry-name>de.danet.an.workflow.api.WorkflowServiceFactory</env-entry-name>
115: * <env-entry-type>java.lang.String</env-entry-type>
116: * <env-entry-value><i>FactoryImplementationClass</i></env-entry-value>
117: * </env-entry></PRE>
118: * Note that this environment entry must be inserted in the
119: * <code>ejb-jar.xml</code> or <code>web.xml</code> for every EJB
120: * resp. servlet that calls the
121: * {@link de.danet.an.workflow.api.WorkflowServiceFactory#newInstance
122: * <code>newInstance</code>} method of
123: * <code>WorkflowServiceFactory</code>.</li>
124: *
125: * <li>Use the services API (as detailed in the JAR specification),
126: * if available, to determine the classname. The Services API
127: * will look for a classname in the file
128: * <code>META-INF/services/de.danet.an.workflow.api.WorkflowServiceFactory</code>.
129: * in jars available to the runtime.</li>
130: * </ul>
131: *
132: * Note that the specified workflow service factory may need
133: * additional configuration parameters.
134: *
135: * @return an instance of the <code>WorkflowServiceFactory</code>.
136: * @throws FactoryConfigurationError if a factory instance can't be
137: * created.
138: */
139: public static WorkflowServiceFactory newInstance()
140: throws FactoryConfigurationError {
141: if (factoryClass == null) {
142: factoryClass = findFactoryClass();
143: }
144: try {
145: return ((WorkflowServiceFactory) factoryClass.newInstance());
146: } catch (InstantiationException ie) {
147: throw new FactoryConfigurationError();
148: } catch (IllegalAccessException iae) {
149: throw new FactoryConfigurationError();
150: }
151: }
152:
153: private static Class factoryClass = null;
154:
155: private static Class findFactoryClass()
156: throws FactoryConfigurationError {
157: if (factoryClass != null) {
158: return factoryClass;
159: }
160: // Lookup in JNDI
161: try {
162: InitialContext initialContext = new InitialContext();
163: String clsname = (String) initialContext
164: .lookup("java:comp/env/de.danet.an.workflow.api"
165: + ".WorkflowServiceFactory");
166: ClassLoader cl = Thread.currentThread()
167: .getContextClassLoader();
168: factoryClass = cl.loadClass(clsname);
169: return factoryClass;
170: } catch (NamingException ne) {
171: // Name not defined
172: } catch (ClassNotFoundException cnfe) {
173: throw new FactoryConfigurationError();
174: } catch (ClassCastException cce) {
175: throw new FactoryConfigurationError();
176: }
177: // Lookup as service in JARs
178: List cls = providerClassesFromJARs(WorkflowServiceFactory.class);
179: if (cls.size() > 0) {
180: factoryClass = (Class) cls.get(0);
181: return factoryClass;
182: }
183: throw new FactoryConfigurationError();
184: }
185:
186: /**
187: * Find all service providers (factories) defined in
188: * <code>META-INF/services</code> directories of all jars in the
189: * classpath. See the JAR specification for details.
190: *
191: * @param service The class of the service, used to construct the file
192: * name as
193: * <code>META-INF/services/<i>service.getName()</i></code> and to verify
194: * the classes specified in those files
195: * (must be derived from <code>service</code>).
196: * @return A list of classes that implement <code>service</code>.
197: */
198: private static List providerClassesFromJARs(Class service) {
199: ClassLoader cl = Thread.currentThread().getContextClassLoader();
200: List res = new ArrayList();
201:
202: try {
203: String serviceDef = "META-INF/services/"
204: + service.getName();
205: Enumeration urls = cl.getResources(serviceDef);
206: // there are buggy classloaders that do not implement
207: // getResources properly. As a workaround, we make an
208: // additional call to getResource if getResources returns
209: // an empty result.
210: if (!urls.hasMoreElements()) {
211: Object r = cl.getResource(serviceDef);
212: if (r != null) {
213: Vector v = new Vector(1);
214: v.add(r);
215: urls = v.elements();
216: }
217: }
218: while (urls.hasMoreElements()) {
219: URL url = (URL) urls.nextElement();
220: BufferedReader data = new BufferedReader(
221: new InputStreamReader(url.openStream(), "UTF-8"));
222: while (true) {
223: String line = data.readLine();
224: if (line == null) {
225: break;
226: }
227:
228: int hashIdx = line.indexOf('#');
229: if (hashIdx >= 0) {
230: line = line.substring(0, hashIdx);
231: }
232: line = line.trim();
233: if (line.length() == 0) {
234: continue;
235: }
236: try {
237: Class c = cl.loadClass(line);
238: if (service.isAssignableFrom(c)) {
239: res.add(c);
240: }
241: } catch (Exception e) {
242: }
243: }
244: }
245: } catch (IOException ioe) {
246: }
247: return res;
248: }
249:
250: /**
251: * Sets a property which is passed to the
252: * <code>WorkflowService</code> produced by this factory. <P>
253: *
254: * Valid properties generally depend on the underlying
255: * implementation. There are, however, a few exceptions. <P>
256: *
257: * If the workflow service implementation is based on the J2EE
258: * environment, clients derive the connection to a server from an
259: * {@link javax.naming.InitialContext
260: * <code>InitialContext</code>}. There are cases when the user
261: * wants or needs to override the initial context used by the
262: * workflow service implementation. It is therefore defined that
263: * setting the property "<code>javax.naming.InitialContext</code>"
264: * to a value of type {@link javax.naming.Context
265: * <code>Context</code>} overrides any default method used by the
266: * workflow service implementation to obtain the initial context.<P>
267: *
268: * As an alternative, the property
269: * "<code>javax.naming.InitialContext.Environment</code>" may be
270: * set to a <code>Hashtable</code> that contains the environment
271: * to be used when creating an <code>InitialContext</code>.<P>
272: *
273: * Subsequent versions of this interface may define additional
274: * common properties. We therefore recommended to use "fully
275: * qulified" (i.e. package style) names for properties that are
276: * specific to a workflow service implementation.
277: *
278: * @param name the name of the property
279: * @param value the value of the property
280: */
281: public void setProperty(String name, Object value) {
282: properties.put(name, value);
283: }
284:
285: /**
286: * A convenience method that sets all properties in the Map.
287: * @param props the properties to be set
288: */
289: public void setProperties(Map props) {
290: properties.putAll(props);
291: }
292:
293: /**
294: * Used by derived classes to access the properties.
295: * @return the defined properties
296: */
297: protected Map getProperties() {
298: return properties;
299: }
300:
301: /**
302: * Creates a new instance of a {@link WorkflowService
303: * <code>WorkflowService</code>}.<P>
304: *
305: * This API does not specify how a workflow service factory or workflow
306: * service should be implemented. If, however, the implementation is
307: * J2EE/EJB based, the following additional rules apply to achieve common
308: * bahaviour for J2EE based implementations.<P>
309: *
310: * In the J2EE environment, clients usually obtain the connection to a
311: * server from a directory service represented by an
312: * {@link javax.naming.InitialContext <code>InitialContext</code>}
313: * instance. This instance need not be the default initial context
314: * available to the client (think of a servlet running in a servlet
315: * container that wants to access the workflow engine running in an
316: * application server on a different machine).<P>
317: *
318: * In an environment that uses an {@link javax.naming.InitialContext
319: * <code>InitialContext</code>} to obtain the connection to the server
320: * (as described above), the following ordered lookup procedure must
321: * be implemented to determine this initial context.
322: * <ul>
323: * <li>
324: * If the property "<code>javax.naming.InitialContext</code>"
325: * has been set, use it as initial context. if property
326: * "<code>javax.naming.InitialContext.Environment</code>" has been
327: * set, use it to obtain the initial context
328: * (see {@link #setProperty(String, Object) <code>setProperty</code>}.
329: * </li>
330: * <li>
331: * If a default <code>InitialContext</code> is available during the
332: * execution of <code>newInstance</code>
333: * (i.e. "<code>new InitialContext()</code> succeeds), and entries
334: * <code>java:comp/env/de.danet.an.workflow.api.WorkflowService.NAMING_CONTEXT_FACTORY</code>
335: * and
336: * <code>java:comp/env/de.danet.an.workflow.api.WorkflowService.NAMING_CONTEXT_URL</code>
337: * exist, use them to obtain the initial context.
338: * </li>
339: * <li>
340: * If defined, execute vendor specific procedures to obtain an initial
341: * context.
342: * </li>
343: * <li>
344: * If a default <code>InitialContext</code> is available during the
345: * execution of <code>newInstance</code> use it (i.e. do not try
346: * to obtain another initial context (this is the common situation
347: * where a servlet based client and the workflow engine run in one
348: * application server).
349: * </li>
350: * </ul>
351: *
352: * @return the workflow service.
353: * @throws FactoryConfigurationError if not all required resources
354: * can be obtained.
355: */
356: public abstract WorkflowService newWorkflowService()
357: throws FactoryConfigurationError;
358:
359: }
|