]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
62a8e33bfc5197b5e40a3af0e27f2bf94a480418
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.listener;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.InputStreamReader;
7 import java.sql.Connection;
8 import java.sql.ResultSet;
9 import java.sql.SQLException;
10 import java.sql.Statement;
11 import org.apache.commons.logging.Log;
12 import org.apache.commons.logging.LogFactory;
13 import org.collectionspace.services.client.workflow.WorkflowClient;
14 import org.collectionspace.services.common.api.Tools;
15 import org.collectionspace.services.common.storage.JDBCTools;
16 import org.collectionspace.services.movement.nuxeo.MovementConstants;
17 import org.nuxeo.ecm.core.api.ClientException;
18 import org.nuxeo.ecm.core.api.DocumentModel;
19 import org.nuxeo.ecm.core.event.Event;
20 import org.nuxeo.ecm.core.event.EventContext;
21 import org.nuxeo.ecm.core.event.EventListener;
22 import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
23
24 public class UpdateObjectLocationOnMove implements EventListener {
25
26     // FIXME: We might experiment here with using log4j instead of Apache Commons Logging;
27     // am using the latter to follow Ray's pattern for now
28     final Log logger = LogFactory.getLog(UpdateObjectLocationOnMove.class);
29     private final String DATABASE_RESOURCE_DIRECTORY_NAME = "db";
30     // FIXME: Currently hard-coded; get this from JDBC utilities or equivalent
31     private final String DATABASE_SYSTEM_NAME = "postgresql";
32     private final String STORED_FUNCTION_NAME = "computecurrentlocation";
33
34     // ####################################################################
35     // FIXME: Per Rick, what happens if a relation record is updated,
36     // that either adds or removes a relation between a Movement
37     // record and a CollectionObject record?  Do we need to listen
38     // for that event as well and update the CollectionObject record's
39     // computedCurrentLocation accordingly?
40     //
41     // The following code is currently checking only for creates or
42     // updates to Movement records.
43     // ####################################################################
44     @Override
45     public void handleEvent(Event event) throws ClientException {
46
47         logger.trace("In handleEvent in UpdateObjectLocationOnMove ...");
48
49         // FIXME: Check for database product type here.
50         // If our database type is one for which we don't yet
51         // have tested SQL code to perform this operation, return here.
52
53         EventContext eventContext = event.getContext();
54         if (eventContext == null) {
55             return;
56         }
57         DocumentEventContext docEventContext = (DocumentEventContext) eventContext;
58         DocumentModel docModel = docEventContext.getSourceDocument();
59         if (isMovementDocument(docModel) && isActiveDocument(docModel)) {
60             logger.debug("A create or update event for an active Movement document was received by UpdateObjectLocationOnMove ...");
61
62             // Test whether a SQL function exists to supply the computed
63             // current location of a CollectionObject.
64             if (storedFunctionExists(STORED_FUNCTION_NAME)) {
65                 logger.debug("Stored function " + STORED_FUNCTION_NAME + "exists.");
66             } else {
67                 logger.debug("Stored function " + STORED_FUNCTION_NAME + "does NOT exist.");
68
69                 // If the function does not exist in the database, load the
70                 // SQL command to create that function from a resource
71                 // available to this class, and run a JDBC command to create
72                 // that function in the database.
73                 //
74                 // For now, assume this function will be created in the
75                 // 'nuxeo' database.
76                 //
77                 // FIXME: Future work to create per-tenant repositories will
78                 // likely require that our JDBC statements connect to the
79                 // appropriate tenant-specific database.
80                 //
81                 // It doesn't appear we can reliably create this function via
82                 // 'ant create_nuxeo db' during the build process, because
83                 // there's a substantial likelihood at that point that
84                 // tables referred to by the function (e.g. movements_common
85                 // and collectionobjects_common) will not yet exist.
86                 // (PostgreSQL will not permit the function to be created if
87                 // any of its referred-to tables do not exist.)
88                 String sqlResourcePath =
89                         DATABASE_RESOURCE_DIRECTORY_NAME + "/"
90                         + DATABASE_SYSTEM_NAME + "/"
91                         + STORED_FUNCTION_NAME + ".sql";
92                 String sql = getStringFromResource(sqlResourcePath);
93                 if (Tools.isBlank(sql)) {
94                     logger.warn("Could not create stored function to update computed current location.");
95                     logger.warn("Actions in this event listener will NOT be performed, as a result of a previous error.");
96                     return;
97                 }
98
99                 logger.debug("After reading stored function command from resource path.");
100                 logger.debug("sql=" + sql);
101
102                 // FIXME: Execute SQL command here to create stored function.
103
104                 // Pseudocode:
105
106                 // Get this Movement record's CSID via the document model.
107
108                 // Find every CollectionObject record related to this Movement record:
109                 //
110                 // Via an NXQL query, get a list of (non-deleted) relation records where:
111                 // * This movement record's CSID is the subject CSID of the relation.
112                 // * The object document type is a CollectionObject doctype.
113
114                 // Iterate through that list of Relation records and build a list of
115                 // CollectionObject CSIDs, by extracting the object CSIDs of those records.
116
117                 // For each such CollectionObject:
118
119                 // Verify that the CollectionObject record is active (use isActiveDocument(), below).
120
121                 // Via a JDBC call, invoke the SQL function to supply the last
122                 // identified location of that CollectionObject, giving it the CSID
123                 // of the CollectionObject record as an argument.
124
125                 // Check that the SQL function's returned value, which is expected
126                 // to be a reference (refName) to a storage location authority term,
127                 // is at a minimum:
128                 // * Non-null
129                 // * Capable of being successfully parsed by an authority item parser,
130                 //   returning a non-null parse result.
131
132                 // Compare that returned value to the value in the
133                 // lastIdentifiedLocation field of that CollectionObject
134
135                 // If the two values differ, update the CollectionObject record,
136                 // setting the value of the lastIdentifiedLocation field of that
137                 // CollectionObject record to the value returned from the SQL function.
138
139             }
140         }
141
142     }
143
144     /**
145      * Identifies whether a document is a Movement document
146      *
147      * @param docModel a document model
148      * @return true if the document is a Movement document; false if it is not.
149      */
150     private boolean isMovementDocument(DocumentModel docModel) {
151         return documentMatchesType(docModel, MovementConstants.NUXEO_DOCTYPE);
152     }
153
154     // FIXME: Generic methods like many of those below might be split off,
155     // into an event utilities class, base classes, or otherwise. - ADR 2012-12-05
156     //
157     // FIXME: Identify whether the equivalent of the documentMatchesType utility
158     // method is already implemented and substitute a call to the latter if so.
159     // This may well already exist.
160     /**
161      * Identifies whether a document matches a supplied document type.
162      *
163      * @param docModel a document model.
164      * @param docType a document type string.
165      * @return true if the document matches the supplied document type; false if
166      * it does not.
167      */
168     private boolean documentMatchesType(DocumentModel docModel, String docType) {
169         if (docModel == null || Tools.isBlank(docType)) {
170             return false;
171         }
172         if (docModel.getType().startsWith(docType)) {
173             return true;
174         } else {
175             return false;
176         }
177     }
178
179     /**
180      * Identifies whether a document is an active document; that is, if it is
181      * not a versioned record; not a proxy (symbolic link to an actual record);
182      * and not in the 'deleted' workflow state.
183      *
184      * (A note relating the latter: Nuxeo appears to send 'documentModified'
185      * events even on workflow transitions, such when records are 'soft deleted'
186      * by being transitioned to the 'deleted' workflow state.)
187      *
188      * @param docModel
189      * @return true if the document is an active document; false if it is not.
190      */
191     private boolean isActiveDocument(DocumentModel docModel) {
192         if (docModel == null) {
193             return false;
194         }
195         boolean isActiveDocument = false;
196         try {
197             if (!docModel.isVersion()
198                     && !docModel.isProxy()
199                     && !docModel.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
200                 isActiveDocument = true;
201             }
202         } catch (ClientException ce) {
203             logger.warn("Error while identifying whether document is an active document: ", ce);
204         }
205         return isActiveDocument;
206     }
207
208     // FIXME: The following method is specific to PostgreSQL, because of
209     // the SQL command executed; it may need to be generalized.
210     // Note: It may be necessary in some cases to provide additional
211     // parameters beyond a function name (such as a function signature)
212     // to uniquely identify a function. So far, however, this need
213     // hasn't arisen in our specific use case here.
214     /**
215      * Identifies whether a stored function exists in a database.
216      *
217      * @param functionname the name of the function.
218      * @return true if the function exists in the database; false if it does
219      * not.
220      */
221     private boolean storedFunctionExists(String functionname) {
222         if (Tools.isBlank(functionname)) {
223             return false;
224         }
225         boolean storedFunctionExists = false;
226         String sql = "SELECT proname FROM pg_proc WHERE proname='" + functionname + "'";
227         Connection conn = null;
228         Statement stmt = null;
229         ResultSet rs = null;
230         try {
231             conn = JDBCTools.getConnection(JDBCTools.getDataSource(JDBCTools.NUXEO_REPOSITORY_NAME));
232             stmt = conn.createStatement();
233             rs = stmt.executeQuery(sql);
234             if (rs.next()) {
235                 storedFunctionExists = true;
236             }
237             rs.close();
238             stmt.close();
239             conn.close();
240         } catch (Exception e) {
241             logger.debug("Error when identifying whether stored function " + functionname + "exists :", e);
242         } finally {
243             try {
244                 if (rs != null) {
245                     rs.close();
246                 }
247                 if (stmt != null) {
248                     stmt.close();
249                 }
250                 if (conn != null) {
251                     conn.close();
252                 }
253             } catch (SQLException sqle) {
254                 logger.debug("SQL Exception closing statement/connection in "
255                         + "UpdateObjectLocationOnMove.storedFunctionExists: "
256                         + sqle.getLocalizedMessage());
257             }
258         }
259         return storedFunctionExists;
260     }
261
262     /**
263      * Returns a string representation of the contents of an input stream.
264      *
265      * @param instream an input stream.
266      * @return a string representation of the contents of the input stream.
267      * @throws an IOException if an error occurs when reading the input stream.
268      */
269     private String stringFromInputStream(InputStream instream) throws IOException {
270         if (instream == null) {
271         }
272         BufferedReader bufreader = new BufferedReader(new InputStreamReader(instream));
273         StringBuilder sb = new StringBuilder();
274         String line = "";
275         while (line != null) {
276             sb.append(line);
277             line = bufreader.readLine();
278             sb.append("\n"); // FIXME: Get appropriate EOL separator rather than hard-coding
279         }
280         return sb.toString();
281     }
282
283     /**
284      * Returns a string representation of a resource available to the current
285      * class.
286      *
287      * @param resourcePath a path to the resource.
288      * @return a string representation of the resource. Returns null if the
289      * resource cannot be read, or if it cannot be successfully represented as a
290      * string.
291      */
292     private String getStringFromResource(String resourcePath) {
293         String str = "";
294         ClassLoader classLoader = getClass().getClassLoader();
295         InputStream instream = classLoader.getResourceAsStream(resourcePath);
296         if (instream == null) {
297             logger.warn("Could not read from resource from path " + resourcePath);
298             return null;
299         }
300         try {
301             str = stringFromInputStream(instream);
302         } catch (IOException ioe) {
303             logger.warn("Could not create string from stream: ", ioe);
304             return null;
305         }
306         return str;
307     }
308 }