001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.workflow.loader;
006:
007: import com.opensymphony.module.propertyset.PropertySet;
008:
009: import com.opensymphony.workflow.FactoryException;
010: import com.opensymphony.workflow.FunctionProvider;
011:
012: import org.apache.commons.logging.Log;
013: import org.apache.commons.logging.LogFactory;
014:
015: import org.xml.sax.SAXException;
016:
017: import java.io.*;
018:
019: import java.sql.*;
020:
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.Map;
024:
025: import javax.naming.InitialContext;
026: import javax.naming.NamingException;
027:
028: import javax.sql.DataSource;
029:
030: /**
031: * Workflow Factory that stores workflows in a database.
032: * The database requires a property called 'datasource' which is the JNDI
033: * name of the datasource for this factory.
034: * <p>
035: * Also required is a database table called OS_WORKFLOWDEFS with two columns,
036: * WF_NAME which contains the workflow name, and WF_DEFINITION which will contain the xml
037: * workflow descriptor, the latter can be either a TEXT or BINARY type.
038: * <p>
039: * Note that this class is provided as an example, and users are encouraged to use
040: * their own implementations that are more suited to their particular needs.
041: *
042: * @author Hubert Felber, Philipp Hug
043: * Date: May 01, 2003
044: * Time: 11:17:06 AM
045: */
046: public class JDBCWorkflowFactory extends XMLWorkflowFactory implements
047: FunctionProvider {
048: //~ Static fields/initializers /////////////////////////////////////////////
049:
050: private static final Log log = LogFactory
051: .getLog(JDBCWorkflowFactory.class);
052: final static String wfTable = "OS_WORKFLOWDEFS";
053: final static String wfName = "WF_NAME";
054: final static String wfDefinition = "WF_DEFINITION";
055:
056: //~ Instance fields ////////////////////////////////////////////////////////
057:
058: protected DataSource ds;
059: protected Map workflows;
060: protected boolean reload;
061:
062: //~ Methods ////////////////////////////////////////////////////////////////
063:
064: public WorkflowDescriptor getWorkflow(String name, boolean validate)
065: throws FactoryException {
066: WfConfig c = (WfConfig) workflows.get(name);
067:
068: if (c == null) {
069: throw new RuntimeException("Unknown workflow name \""
070: + name + "\"");
071: }
072:
073: if (log.isDebugEnabled()) {
074: log.debug("getWorkflow " + name + " descriptor="
075: + c.descriptor);
076: }
077:
078: if (c.descriptor != null) {
079: if (reload) {
080: //@todo check timestamp
081: try {
082: c.descriptor = load(c.wfName, validate);
083: } catch (FactoryException e) {
084: throw e;
085: } catch (Exception e) {
086: throw new FactoryException(
087: "Error reloading workflow", e);
088: }
089: }
090: } else {
091: try {
092: c.descriptor = load(c.wfName, validate);
093: } catch (FactoryException e) {
094: throw e;
095: } catch (Exception e) {
096: throw new FactoryException("Error loading workflow", e);
097: }
098: }
099:
100: return c.descriptor;
101: }
102:
103: public String[] getWorkflowNames() {
104: int i = 0;
105: String[] res = new String[workflows.keySet().size()];
106: Iterator it = workflows.keySet().iterator();
107:
108: while (it.hasNext()) {
109: res[i++] = (String) it.next();
110: }
111:
112: return res;
113: }
114:
115: public void execute(Map transientVars, Map args, PropertySet ps) {
116: String name = (String) args.get("name");
117: WorkflowDescriptor wfds = (WorkflowDescriptor) transientVars
118: .get("descriptor");
119:
120: try {
121: saveWorkflow(name, wfds, false);
122: } catch (Exception e) {
123: }
124: }
125:
126: public void initDone() throws FactoryException {
127: Connection conn = null;
128:
129: try {
130: init();
131: reload = getProperties().getProperty("reload", "false")
132: .equalsIgnoreCase("true");
133:
134: conn = ds.getConnection();
135:
136: PreparedStatement ps = conn.prepareStatement("SELECT "
137: + wfName + "," + wfDefinition + " FROM " + wfTable);
138: ResultSet rs = ps.executeQuery();
139:
140: while (rs.next()) {
141: String name = rs.getString(1);
142: WfConfig config = new WfConfig(name);
143: workflows.put(rs.getString(1), config);
144: }
145:
146: rs.close();
147: ps.close();
148: } catch (Exception e) {
149: throw new FactoryException(
150: "Could not read workflow names from datasource", e);
151: } finally {
152: try {
153: conn.close();
154: } catch (Exception ex) {
155: }
156: }
157: }
158:
159: public byte[] read(String workflowname) throws SQLException {
160: byte[] wf = new byte[0];
161:
162: Connection conn = null;
163:
164: try {
165: conn = ds.getConnection();
166:
167: PreparedStatement ps = conn.prepareStatement("SELECT "
168: + wfDefinition + " FROM " + wfTable + " WHERE "
169: + wfName + " = ?");
170: ps.setString(1, workflowname);
171:
172: ResultSet rs = ps.executeQuery();
173:
174: if (rs.next()) {
175: wf = rs.getBytes(1);
176: }
177:
178: rs.close();
179: ps.close();
180: } finally {
181: if (conn != null) {
182: try {
183: conn.close();
184: } catch (Exception ex) {
185: }
186: }
187: }
188:
189: return wf;
190: }
191:
192: public boolean removeWorkflow(String name) throws FactoryException {
193: boolean removed = false;
194:
195: try {
196: Connection conn = ds.getConnection();
197: PreparedStatement ps = conn.prepareStatement("DELETE FROM "
198: + wfTable + " WHERE " + wfName + " = ?");
199: ps.setString(1, name);
200:
201: int rows = ps.executeUpdate();
202:
203: if (rows == 1) {
204: removed = true;
205: workflows.remove(name);
206: }
207:
208: ps.close();
209: conn.close();
210: } catch (SQLException e) {
211: throw new FactoryException("Unable to remove workflow: "
212: + e.toString(), e);
213: }
214:
215: return removed;
216: }
217:
218: public boolean saveWorkflow(String name,
219: WorkflowDescriptor descriptor, boolean replace)
220: throws FactoryException {
221: WfConfig c = (WfConfig) workflows.get(name);
222:
223: if ((c != null) && !replace) {
224: return false;
225: }
226:
227: ByteArrayOutputStream bout = new ByteArrayOutputStream();
228: Writer out = new OutputStreamWriter(bout);
229:
230: PrintWriter writer = new PrintWriter(out);
231: writer.println(WorkflowDescriptor.XML_HEADER);
232: writer.println(WorkflowDescriptor.DOCTYPE_DECL);
233: descriptor.writeXML(writer, 0);
234: writer.flush();
235: writer.close();
236:
237: //@todo is a backup necessary?
238: try {
239: return write(name, bout.toByteArray());
240: } catch (SQLException e) {
241: throw new FactoryException("Unable to save workflow: "
242: + e.toString(), e);
243: } finally {
244: WfConfig config = new WfConfig(name);
245: workflows.put(name, config);
246: }
247: }
248:
249: public boolean write(String workflowname, byte[] wf)
250: throws SQLException {
251: boolean written = false;
252: Connection conn = null;
253:
254: try {
255: conn = ds.getConnection();
256:
257: PreparedStatement ps;
258:
259: if (exists(workflowname, conn)) {
260: ps = conn.prepareStatement("UPDATE " + wfTable
261: + " SET " + wfDefinition + " = ?" + "WHERE "
262: + wfName + "= ?");
263:
264: try {
265: ps.setBytes(1, wf);
266: } catch (Exception e) {
267: }
268:
269: ps.setString(2, workflowname);
270: } else {
271: ps = conn.prepareStatement("INSERT INTO " + wfTable
272: + " (" + wfName + ", " + wfDefinition
273: + ") VALUES (?, ?)");
274: ps.setString(1, workflowname);
275:
276: try {
277: ps.setBytes(2, wf);
278: } catch (Exception e) {
279: }
280: }
281:
282: ps.executeUpdate();
283: ps.close();
284: conn.close();
285: written = true;
286: } finally {
287: if (conn != null) {
288: try {
289: conn.close();
290: } catch (Exception e) {
291: }
292: }
293: }
294:
295: return written;
296: }
297:
298: private boolean exists(String workflowname, Connection conn) {
299: boolean exists = false;
300:
301: try {
302: PreparedStatement ps = conn.prepareStatement("SELECT "
303: + wfName + " FROM " + wfTable + " WHERE " + wfName
304: + " = ?");
305: ps.setString(1, workflowname);
306:
307: ResultSet rs = ps.executeQuery();
308:
309: if (rs.next()) {
310: exists = true;
311: }
312:
313: rs.close();
314: ps.close();
315: } catch (SQLException e) {
316: log.fatal("Could not check if [" + workflowname
317: + "] exists", e);
318: }
319:
320: return exists;
321: }
322:
323: private void init() throws NamingException {
324: workflows = new HashMap();
325:
326: ds = (DataSource) new InitialContext().lookup(getProperties()
327: .getProperty("datasource"));
328: }
329:
330: private WorkflowDescriptor load(final String wfName,
331: boolean validate) throws IOException, SAXException,
332: FactoryException {
333: byte[] wf;
334:
335: try {
336: wf = read(wfName);
337: } catch (SQLException e) {
338: throw new FactoryException("Error loading workflow:" + e, e);
339: }
340:
341: ByteArrayInputStream is = new ByteArrayInputStream(wf);
342:
343: return WorkflowLoader.load(is, validate);
344: }
345:
346: //~ Inner Classes //////////////////////////////////////////////////////////
347:
348: class WfConfig {
349: String wfName;
350: WorkflowDescriptor descriptor;
351: long lastModified;
352:
353: public WfConfig(String name) {
354: wfName = name;
355: }
356: }
357: }
|