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