]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
eedc243c7cc9fd1f4957e85bb72f355ca7fa04bd
[tmp/jakarta-migration.git] /
1 /**\r
2  *  This document is a part of the source code and related artifacts\r
3  *  for CollectionSpace, an open source collections management system\r
4  *  for museums and related institutions:\r
5 \r
6  *  http://www.collectionspace.org\r
7  *  http://wiki.collectionspace.org\r
8 \r
9  *  Copyright 2009 University of California at Berkeley\r
10 \r
11  *  Licensed under the Educational Community License (ECL), Version 2.0.\r
12  *  You may not use this file except in compliance with this License.\r
13 \r
14  *  You may obtain a copy of the ECL 2.0 License at\r
15 \r
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt\r
17 \r
18  *  Unless required by applicable law or agreed to in writing, software\r
19  *  distributed under the License is distributed on an "AS IS" BASIS,\r
20  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
21  *  See the License for the specific language governing permissions and\r
22  *  limitations under the License.\r
23  */\r
24 package org.collectionspace.services.common.vocabulary;\r
25 \r
26 import java.util.ArrayList;\r
27 import java.util.Collection;\r
28 import java.util.HashMap;\r
29 import java.util.Iterator;\r
30 import java.util.List;\r
31 import java.util.Map;\r
32 \r
33 import org.nuxeo.ecm.core.api.ClientException;\r
34 import org.nuxeo.ecm.core.api.DocumentModel;\r
35 import org.nuxeo.ecm.core.api.DocumentModelList;\r
36 import org.nuxeo.ecm.core.api.model.Property;\r
37 import org.nuxeo.ecm.core.api.model.PropertyException;\r
38 import org.nuxeo.ecm.core.api.model.impl.primitives.StringProperty;\r
39 import org.slf4j.Logger;\r
40 import org.slf4j.LoggerFactory;\r
41 \r
42 import org.collectionspace.services.common.ServiceMain;\r
43 import org.collectionspace.services.common.context.ServiceContext;\r
44 import org.collectionspace.services.common.api.Tools;\r
45 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;\r
46 import org.collectionspace.services.common.authorityref.AuthorityRefList;\r
47 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;\r
48 import org.collectionspace.services.common.context.ServiceBindingUtils;\r
49 import org.collectionspace.services.common.document.DocumentException;\r
50 import org.collectionspace.services.common.document.DocumentNotFoundException;\r
51 import org.collectionspace.services.common.document.DocumentUtils;\r
52 import org.collectionspace.services.common.document.DocumentWrapper;\r
53 import org.collectionspace.services.common.repository.RepositoryClient;\r
54 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;\r
55 import org.collectionspace.services.common.service.ServiceBindingType;\r
56 import org.collectionspace.services.jaxb.AbstractCommonList;\r
57 import org.collectionspace.services.nuxeo.util.NuxeoUtils;\r
58 \r
59 /**\r
60  * RefNameServiceUtils is a collection of services utilities related to refName usage.\r
61  *\r
62  * $LastChangedRevision: $\r
63  * $LastChangedDate: $\r
64  */\r
65 public class RefNameServiceUtils {\r
66 \r
67     private static final Logger logger = LoggerFactory.getLogger(RefNameServiceUtils.class);\r
68     \r
69     private static ArrayList<String> refNameServiceTypes = null;\r
70 \r
71     public static AuthorityRefDocList getAuthorityRefDocs(ServiceContext ctx,\r
72             RepositoryClient repoClient,\r
73             List<String> serviceTypes,\r
74             String refName,\r
75             String refPropName,\r
76             int pageSize, int pageNum, boolean computeTotal) throws DocumentException, DocumentNotFoundException {\r
77         AuthorityRefDocList wrapperList = new AuthorityRefDocList();\r
78         AbstractCommonList commonList = (AbstractCommonList) wrapperList;\r
79         commonList.setPageNum(pageNum);\r
80         commonList.setPageSize(pageSize);\r
81         List<AuthorityRefDocList.AuthorityRefDocItem> list =\r
82                 wrapperList.getAuthorityRefDocItem();\r
83         \r
84         Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();\r
85         Map<String, Map<String, String>> authRefFieldsByService = new HashMap<String, Map<String, String>>();\r
86 \r
87         DocumentModelList docList = findAuthorityRefDocs(ctx, repoClient, serviceTypes, refName, refPropName,\r
88                         queriedServiceBindings, authRefFieldsByService, pageSize, pageNum, computeTotal);\r
89 \r
90         if (docList == null) { // found no authRef fields - nothing to process\r
91             return wrapperList;\r
92         }\r
93         // Set num of items in list. this is useful to our testing framework.\r
94         commonList.setItemsInPage(docList.size());\r
95         // set the total result size\r
96         commonList.setTotalItems(docList.totalSize());\r
97         \r
98         int nRefsFound = processRefObjsDocList(docList, refName, queriedServiceBindings, authRefFieldsByService,\r
99                                                         list, null);\r
100         if(logger.isDebugEnabled()  && (nRefsFound < docList.size())) {\r
101                 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched...");\r
102         }\r
103         return wrapperList;\r
104     }\r
105     \r
106     private static ArrayList<String> getRefNameServiceTypes() {\r
107         if(refNameServiceTypes == null) {\r
108                 refNameServiceTypes = new ArrayList<String>();\r
109                 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);\r
110                 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);\r
111                 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);\r
112         }\r
113         return refNameServiceTypes;\r
114     }\r
115     \r
116     public static int updateAuthorityRefDocs(ServiceContext ctx,\r
117             RepositoryClient repoClient,\r
118             String oldRefName,\r
119             String newRefName,\r
120             String refPropName ) {\r
121         Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();\r
122         Map<String, Map<String, String>> authRefFieldsByService = new HashMap<String, Map<String, String>>();\r
123         int nRefsFound = 0;\r
124         if(!(repoClient instanceof RepositoryJavaClientImpl)) {\r
125                 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");\r
126         }\r
127         try {\r
128                 final int pageSize = 100;       // Seems like a good value - no real data to set this well.\r
129                 int pageNumProcessed = 1;\r
130                 while(true) {   // Keep looping until we find all the refs.\r
131                         logger.debug("updateAuthorityRefDocs working on page: "+pageNumProcessed);\r
132                         // Note that we always ask the Repo for the first page, since each page we process\r
133                         // should not be found in successive searches.\r
134                         DocumentModelList docList = findAuthorityRefDocs(ctx, repoClient, getRefNameServiceTypes(), oldRefName, refPropName,\r
135                                         queriedServiceBindings, authRefFieldsByService, pageSize, 0, false);\r
136                 \r
137                         if((docList == null)                    // found no authRef fields - nothing to do\r
138                                 || (docList.size() == 0)) {     // No more to handle\r
139                                 logger.debug("updateAuthorityRefDocs no more results");\r
140                             break;\r
141                         }\r
142                         logger.debug("updateAuthorityRefDocs curr page result list size: "+docList.size());\r
143                         int nRefsFoundThisPage = processRefObjsDocList(docList, oldRefName, queriedServiceBindings, authRefFieldsByService,\r
144                                                                         null, newRefName);\r
145                         if(nRefsFoundThisPage>0) {\r
146                                 ((RepositoryJavaClientImpl)repoClient).saveDocListWithoutHandlerProcessing(ctx, docList, true);\r
147                                 nRefsFound += nRefsFoundThisPage;\r
148                         }\r
149                         pageNumProcessed++;\r
150                 }\r
151         } catch(Exception e) {\r
152                 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());\r
153                 logger.debug(Tools.errorToString(e, true));\r
154         }\r
155                 logger.debug("updateAuthorityRefDocs replaced a total of: "+nRefsFound);\r
156         return nRefsFound;\r
157     }\r
158     \r
159     private static DocumentModelList findAuthorityRefDocs(ServiceContext ctx,\r
160             RepositoryClient repoClient,\r
161             List<String> serviceTypes,\r
162             String refName,\r
163             String refPropName,\r
164             Map<String, ServiceBindingType> queriedServiceBindings,\r
165             Map<String, Map<String, String>> authRefFieldsByService,\r
166             int pageSize, int pageNum, boolean computeTotal) throws DocumentException, DocumentNotFoundException {\r
167 \r
168         // Get the service bindings for this tenant\r
169         TenantBindingConfigReaderImpl tReader =\r
170                 ServiceMain.getInstance().getTenantBindingConfigReader();\r
171         // We need to get all the procedures, authorities, and objects.\r
172         List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);\r
173         if (servicebindings == null || servicebindings.isEmpty()) {\r
174                 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");\r
175             return null;\r
176         }\r
177         \r
178         // Need to escape the quotes in the refName\r
179         // TODO What if they are already escaped?\r
180         String escapedRefName = refName.replaceAll("'", "\\\\'");\r
181         ArrayList<String> docTypes = new ArrayList<String>();\r
182         \r
183         String query = computeWhereClauseForAuthorityRefDocs(escapedRefName, refPropName, docTypes, servicebindings, \r
184                                                                                                 queriedServiceBindings, authRefFieldsByService );\r
185         if (query == null) { // found no authRef fields - nothing to query\r
186             return null;\r
187         }\r
188         // Now we have to issue the search\r
189         DocumentWrapper<DocumentModelList> docListWrapper = repoClient.findDocs(ctx,\r
190                 docTypes, query, pageSize, pageNum, computeTotal);\r
191         // Now we gather the info for each document into the list and return\r
192         DocumentModelList docList = docListWrapper.getWrappedObject();\r
193         return docList;\r
194     }\r
195     \r
196     private static String computeWhereClauseForAuthorityRefDocs(\r
197                 String escapedRefName,\r
198                 String refPropName,\r
199                 ArrayList<String> docTypes,\r
200                 List<ServiceBindingType> servicebindings,\r
201                 Map<String, ServiceBindingType> queriedServiceBindings,\r
202                 Map<String, Map<String, String>> authRefFieldsByService ) {\r
203         StringBuilder whereClause = new StringBuilder();\r
204         boolean fFirst = true;\r
205         List<String> authRefFieldPaths = new ArrayList<String>();\r
206         for (ServiceBindingType sb : servicebindings) {\r
207                 // Gets the property names for each part, qualified with the part label (which\r
208                 // is also the table name, the way that the repository works).\r
209             authRefFieldPaths =\r
210                     ServiceBindingUtils.getAllPartsPropertyValues(sb,\r
211                                 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);\r
212             if (authRefFieldPaths.isEmpty()) {\r
213                 continue;\r
214             }\r
215             String authRefPath = "";\r
216             String ancestorAuthRefFieldName = "";\r
217             Map<String, String> authRefFields = new HashMap<String, String>();\r
218             for (int i = 0; i < authRefFieldPaths.size(); i++) {\r
219                 // fieldName = DocumentUtils.getDescendantOrAncestor(authRefFields.get(i));\r
220                 // For simple field values, we just search on the item.\r
221                 // For simple repeating scalars, we just search the group field \r
222                 // For repeating complex types, we will need to do more.\r
223                 authRefPath = authRefFieldPaths.get(i);\r
224                 ancestorAuthRefFieldName = DocumentUtils.getAncestorAuthRefFieldName(authRefFieldPaths.get(i));\r
225                 authRefFields.put(authRefPath, ancestorAuthRefFieldName);\r
226             }\r
227 \r
228             String docType = sb.getObject().getName();\r
229             queriedServiceBindings.put(docType, sb);\r
230             authRefFieldsByService.put(docType, authRefFields);\r
231             docTypes.add(docType);\r
232             Collection<String> fields = authRefFields.values();\r
233             for (String field : fields) {\r
234                 // Build up the where clause for each authRef field\r
235                 if (fFirst) {\r
236                     fFirst = false;\r
237                 } else {\r
238                     whereClause.append(" OR ");\r
239                 }\r
240                 //whereClause.append(prefix);\r
241                 whereClause.append(field);\r
242                 whereClause.append("='");\r
243                 whereClause.append(escapedRefName);\r
244                 whereClause.append("'");\r
245             }\r
246         }\r
247         String whereClauseStr = whereClause.toString(); // for debugging\r
248         if (fFirst) { // found no authRef fields - nothing to query\r
249             return null;\r
250         } else {\r
251                 return whereClause.toString(); \r
252         }\r
253     }\r
254     \r
255     /*\r
256      * Runs through the list of found docs, processing them. \r
257      * If list is non-null, then processing means gather the info for items.\r
258      * If list is null, and newRefName is non-null, then processing means replacing and updating. \r
259      *   If processing/updating, this must be called in teh context of an open session, and caller\r
260      *   must release Session after calling this.\r
261      * \r
262      */\r
263     private static int processRefObjsDocList(\r
264                 DocumentModelList docList,\r
265                 String refName,\r
266                 Map<String, ServiceBindingType> queriedServiceBindings,\r
267                 Map<String, Map<String, String>> authRefFieldsByService,\r
268                         List<AuthorityRefDocList.AuthorityRefDocItem> list, \r
269                         String newAuthorityRefName) {\r
270         if(newAuthorityRefName==null) {\r
271                 if(list==null) {\r
272                         throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");\r
273                 }\r
274         } else if(list!=null) {\r
275                 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");\r
276         }\r
277 \r
278         Iterator<DocumentModel> iter = docList.iterator();\r
279         int nRefsFoundTotal = 0;\r
280         while (iter.hasNext()) {\r
281             DocumentModel docModel = iter.next();\r
282             AuthorityRefDocList.AuthorityRefDocItem ilistItem;\r
283 \r
284             String docType = docModel.getDocumentType().getName();\r
285             ServiceBindingType sb = queriedServiceBindings.get(docType);\r
286             if (sb == null) {\r
287                 throw new RuntimeException(\r
288                         "getAuthorityRefDocs: No Service Binding for docType: " + docType);\r
289             }\r
290             String serviceContextPath = "/" + sb.getName().toLowerCase() + "/";\r
291             \r
292             if(list == null) {\r
293                 ilistItem = null;\r
294             } else {\r
295                 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();\r
296                 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());\r
297                 ilistItem.setDocId(csid);\r
298                 ilistItem.setUri(serviceContextPath + csid);\r
299                 // The id and URI are the same on all doctypes\r
300                 ilistItem.setDocType(docType);\r
301                 ilistItem.setDocNumber(\r
302                         ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));\r
303                 ilistItem.setDocName(\r
304                         ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));\r
305             }\r
306             // Now, we have to loop over the authRefFieldsByService to figure\r
307             // out which field(s) matched this.\r
308             Map<String,String> matchingAuthRefFields = authRefFieldsByService.get(docType);\r
309             if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {\r
310                 throw new RuntimeException(\r
311                         "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");\r
312             }\r
313             String authRefAncestorField = "";\r
314             String authRefDescendantField = "";\r
315             String sourceField = "";\r
316             int nRefsFoundInDoc = 0;\r
317             // Use this if we go to qualified field names\r
318             for (String path : matchingAuthRefFields.keySet()) {\r
319                 try {\r
320                         // This is the field name we show in the return info\r
321                         // Returned as a schema-qualified property path\r
322                     authRefAncestorField = (String) matchingAuthRefFields.get(path);\r
323                     // This is the qualified field we have to get from the doc model\r
324                     authRefDescendantField = DocumentUtils.getDescendantOrAncestor(path);\r
325                     // The ancestor field is part-schema (tablename) qualified\r
326                     //String[] strings = authRefAncestorField.split(":");\r
327                     //if (strings.length != 2) {\r
328                     //   throw new RuntimeException(\r
329                     //            "getAuthorityRefDocs: Bad configuration of path to authority reference field.");\r
330                     //}\r
331                     // strings[0] holds a schema name, such as "intakes_common"\r
332                     //\r
333                     // strings[1] holds:\r
334                     // * The name of an authority reference field, such as "depositor";\r
335                     //   or\r
336                     // * The name of an ancestor (e.g. parent, grandparent ...) field,\r
337                     //   such as "fieldCollectors", of a repeatable authority reference\r
338                     //   field, such as "fieldCollector".\r
339                     // TODO - if the value is not simple, or repeating scalar, need a more\r
340                     // sophisticated fetch. \r
341                     // Change this to an XPath model\r
342                     //Object fieldValue = docModel.getProperty(strings[0], strings[1]);\r
343                     // This will have to handle repeating complex fields by iterating over the possibilities\r
344                     // and finding the one that matches.\r
345                     Property fieldValue = docModel.getProperty(authRefAncestorField);\r
346                     // We know this doc should have a match somewhere, but it may not be in this field\r
347                     // If we are just building up the refItems, then it is enough to know we found a match.\r
348                     // If we are patching refName values, then we have to replace each match.\r
349                     int nRefsMatchedInField = refNameFoundInField(refName, fieldValue, newAuthorityRefName);\r
350                     if (nRefsMatchedInField > 0) {\r
351                         sourceField = authRefDescendantField;\r
352                         // Handle multiple fields matching in one Doc. See CSPACE-2863.\r
353                         if(nRefsFoundInDoc > 0) {\r
354                                 // We already added ilistItem, so we need to clone that and add again\r
355                                 if(ilistItem != null) {\r
356                                         ilistItem = cloneAuthRefDocItem(ilistItem, sourceField);\r
357                                 }\r
358                         } else {\r
359                                 if(ilistItem != null) {\r
360                                         ilistItem.setSourceField(sourceField);\r
361                                 }\r
362                         }\r
363                                 if(ilistItem != null) {\r
364                                         list.add(ilistItem);\r
365                                 }\r
366                                 nRefsFoundInDoc += nRefsMatchedInField;\r
367                     }\r
368 \r
369                 } catch (ClientException ce) {\r
370                     throw new RuntimeException(\r
371                             "getAuthorityRefDocs: Problem fetching: " + sourceField, ce);\r
372                 }\r
373             }\r
374             if (nRefsFoundInDoc == 0) {\r
375                 throw new RuntimeException(\r
376                         "getAuthorityRefDocs: Could not find refname in object:"\r
377                         + docType + ":" + NuxeoUtils.getCsid(docModel));\r
378             }\r
379             nRefsFoundTotal += nRefsFoundInDoc;\r
380         }\r
381         return nRefsFoundTotal;\r
382     }\r
383     \r
384     private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(\r
385                 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {\r
386         AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();\r
387         newlistItem.setDocId(ilistItem.getDocId());\r
388         newlistItem.setDocName(ilistItem.getDocName());\r
389         newlistItem.setDocNumber(ilistItem.getDocNumber());\r
390         newlistItem.setDocType(ilistItem.getDocType());\r
391         newlistItem.setUri(ilistItem.getUri());\r
392         newlistItem.setSourceField(sourceField);\r
393         return newlistItem;\r
394     }\r
395 \r
396     /*\r
397      * Identifies whether the refName was found in the supplied field.\r
398      * If passed a new RefName, will set that into fields in which the old one was found.\r
399      *\r
400      * Only works for:\r
401      * * Scalar fields\r
402      * * Repeatable scalar fields (aka multi-valued fields)\r
403      *\r
404      * Does not work for:\r
405      * * Structured fields (complexTypes)\r
406      * * Repeatable structured fields (repeatable complexTypes)\r
407      */\r
408     private static int refNameFoundInField(String oldRefName, Property fieldValue, String newRefName) {\r
409         int nFound = 0;\r
410         if (fieldValue instanceof List) {\r
411                 List<Property> fieldValueList = (List) fieldValue;\r
412                 for (Property listItemValue : fieldValueList) {\r
413                         try {\r
414                                 if ((listItemValue instanceof StringProperty)\r
415                                                 && oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {\r
416                                         nFound++;\r
417                                         if(newRefName!=null) {\r
418                                                 fieldValue.setValue(newRefName);\r
419                                         } else {\r
420                                                 // We cannot quit after the first, if we are replacing values.\r
421                                                 // If we are just looking (not replacing), finding one is enough.\r
422                                                 break;\r
423                                         }\r
424                                 }\r
425                         } catch( PropertyException pe ) {}\r
426                 }\r
427         } else {\r
428                 try {\r
429                         if ((fieldValue instanceof StringProperty)\r
430                                         && oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) {\r
431                                         nFound++;\r
432                                 if(newRefName!=null) {\r
433                                         fieldValue.setValue(newRefName);\r
434                                 }\r
435                         }\r
436                 } catch( PropertyException pe ) {}\r
437         }\r
438         return nFound;\r
439     }\r
440 }\r
441 \r