]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
bd60deed5c1cc48e48aa2ef180e842c64aa09fb3
[tmp/jakarta-migration.git] /
1 /**\r
2  * This document is a part of the source code and related artifacts for\r
3  * CollectionSpace, an open source collections management system for museums and\r
4  * related institutions:\r
5  *\r
6  * http://www.collectionspace.org http://wiki.collectionspace.org\r
7  *\r
8  * Copyright 2009 University of California at Berkeley\r
9  *\r
10  * Licensed under the Educational Community License (ECL), Version 2.0. You may\r
11  * not use this file except in compliance with this License.\r
12  *\r
13  * You may obtain a copy of the ECL 2.0 License at\r
14  *\r
15  * https://source.collectionspace.org/collection-space/LICENSE.txt\r
16  *\r
17  * Unless required by applicable law or agreed to in writing, software\r
18  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\r
19  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
20  * License for the specific language governing permissions and limitations under\r
21  * the License.\r
22  */\r
23 package org.collectionspace.services.common.vocabulary;\r
24 \r
25 import java.util.ArrayList;\r
26 import java.util.HashMap;\r
27 import java.util.Iterator;\r
28 import java.util.List;\r
29 import java.util.Map;\r
30 \r
31 import org.nuxeo.ecm.core.api.ClientException;\r
32 import org.nuxeo.ecm.core.api.DocumentModel;\r
33 import org.nuxeo.ecm.core.api.DocumentModelList;\r
34 import org.nuxeo.ecm.core.api.model.Property;\r
35 import org.nuxeo.ecm.core.api.model.PropertyException;\r
36 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;\r
37 import org.nuxeo.ecm.core.api.model.impl.primitives.StringProperty;\r
38 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;\r
39 import org.slf4j.Logger;\r
40 import org.slf4j.LoggerFactory;\r
41 \r
42 import org.collectionspace.services.client.CollectionSpaceClient;\r
43 import org.collectionspace.services.client.IRelationsManager;\r
44 import org.collectionspace.services.client.PoxPayloadIn;\r
45 import org.collectionspace.services.client.PoxPayloadOut;\r
46 import org.collectionspace.services.common.ServiceMain;\r
47 import org.collectionspace.services.common.StoredValuesUriTemplate;\r
48 import org.collectionspace.services.common.UriTemplateFactory;\r
49 import org.collectionspace.services.common.UriTemplateRegistry;\r
50 import org.collectionspace.services.common.UriTemplateRegistryKey;\r
51 import org.collectionspace.services.common.context.ServiceContext;\r
52 import org.collectionspace.services.common.context.AbstractServiceContextImpl;\r
53 import org.collectionspace.services.common.api.RefNameUtils;\r
54 import org.collectionspace.services.common.api.Tools;\r
55 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;\r
56 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;\r
57 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;\r
58 import org.collectionspace.services.common.context.ServiceBindingUtils;\r
59 import org.collectionspace.services.common.document.DocumentException;\r
60 import org.collectionspace.services.common.document.DocumentFilter;\r
61 import org.collectionspace.services.common.document.DocumentNotFoundException;\r
62 import org.collectionspace.services.common.document.DocumentUtils;\r
63 import org.collectionspace.services.common.document.DocumentWrapper;\r
64 import org.collectionspace.services.common.query.QueryManager;\r
65 import org.collectionspace.services.common.relation.RelationUtils;\r
66 import org.collectionspace.services.common.repository.RepositoryClient;\r
67 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;\r
68 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;\r
69 import org.collectionspace.services.common.security.SecurityUtils;\r
70 import org.collectionspace.services.config.service.ServiceBindingType;\r
71 import org.collectionspace.services.jaxb.AbstractCommonList;\r
72 import org.collectionspace.services.nuxeo.util.NuxeoUtils;\r
73 \r
74 /**\r
75  * RefNameServiceUtils is a collection of services utilities related to refName\r
76  * usage.\r
77  *\r
78  * $LastChangedRevision: $ $LastChangedDate: $\r
79  */\r
80 public class RefNameServiceUtils {\r
81 \r
82     public static class AuthRefConfigInfo {\r
83 \r
84         public String getQualifiedDisplayName() {\r
85             return (Tools.isBlank(schema))\r
86                     ? displayName : DocumentUtils.appendSchemaName(schema, displayName);\r
87         }\r
88 \r
89         public String getDisplayName() {\r
90             return displayName;\r
91         }\r
92 \r
93         public void setDisplayName(String displayName) {\r
94             this.displayName = displayName;\r
95         }\r
96         String displayName;\r
97         String schema;\r
98 \r
99         public String getSchema() {\r
100             return schema;\r
101         }\r
102 \r
103         public void setSchema(String schema) {\r
104             this.schema = schema;\r
105         }\r
106 \r
107         public String getFullPath() {\r
108             return fullPath;\r
109         }\r
110 \r
111         public void setFullPath(String fullPath) {\r
112             this.fullPath = fullPath;\r
113         }\r
114         String fullPath;\r
115         protected String[] pathEls;\r
116 \r
117         public AuthRefConfigInfo(AuthRefConfigInfo arci) {\r
118             this.displayName = arci.displayName;\r
119             this.schema = arci.schema;\r
120             this.fullPath = arci.fullPath;\r
121             this.pathEls = arci.pathEls;\r
122             // Skip the pathElse check, since we are creatign from another (presumably valid) arci.\r
123         }\r
124 \r
125         public AuthRefConfigInfo(String displayName, String schema, String fullPath, String[] pathEls) {\r
126             this.displayName = displayName;\r
127             this.schema = schema;\r
128             this.fullPath = fullPath;\r
129             this.pathEls = pathEls;\r
130             checkPathEls();\r
131         }\r
132 \r
133         // Split a config value string like "intakes_common:collector", or\r
134         // "collectionobjects_common:contentPeoples|contentPeople"\r
135         // "collectionobjects_common:assocEventGroupList/*/assocEventPlace"\r
136         // If has a pipe ('|') second part is a displayLabel, and first is path\r
137         // Otherwise, entry is a path, and can use the last pathElement as displayName\r
138         // Should be schema qualified.\r
139         public AuthRefConfigInfo(String configString) {\r
140             String[] pair = configString.split("\\|", 2);\r
141             String[] pathEls;\r
142             String displayName, fullPath;\r
143             if (pair.length == 1) {\r
144                 // no label specifier, so we'll defer getting label\r
145                 fullPath = pair[0];\r
146                 pathEls = pair[0].split("/");\r
147                 displayName = pathEls[pathEls.length - 1];\r
148             } else {\r
149                 fullPath = pair[0];\r
150                 pathEls = pair[0].split("/");\r
151                 displayName = pair[1];\r
152             }\r
153             String[] schemaSplit = pathEls[0].split(":", 2);\r
154             String schema;\r
155             if (schemaSplit.length == 1) {    // schema not specified\r
156                 schema = null;\r
157             } else {\r
158                 schema = schemaSplit[0];\r
159                 if (pair.length == 1 && pathEls.length == 1) {    // simplest case of field in top level schema, no labelll\r
160                     displayName = schemaSplit[1];    // Have to fix up displayName to have no schema\r
161                 }\r
162             }\r
163             this.displayName = displayName;\r
164             this.schema = schema;\r
165             this.fullPath = fullPath;\r
166             this.pathEls = pathEls;\r
167             checkPathEls();\r
168         }\r
169 \r
170         protected void checkPathEls() {\r
171             int len = pathEls.length;\r
172             if (len < 1) {\r
173                 throw new InternalError("Bad values in authRef info - caller screwed up:" + fullPath);\r
174             }\r
175             // Handle case of them putting a leading slash on the path\r
176             if (len > 1 && pathEls[0].endsWith(":")) {\r
177                 len--;\r
178                 String[] newArray = new String[len];\r
179                 newArray[0] = pathEls[0] + pathEls[1];\r
180                 if (len >= 2) {\r
181                     System.arraycopy(pathEls, 2, newArray, 1, len - 1);\r
182                 }\r
183                 pathEls = newArray;\r
184             }\r
185         }\r
186     }\r
187 \r
188     public static class AuthRefInfo extends AuthRefConfigInfo {\r
189 \r
190         public Property getProperty() {\r
191             return property;\r
192         }\r
193 \r
194         public void setProperty(Property property) {\r
195             this.property = property;\r
196         }\r
197         Property property;\r
198 \r
199         public AuthRefInfo(String displayName, String schema, String fullPath, String[] pathEls, Property prop) {\r
200             super(displayName, schema, fullPath, pathEls);\r
201             this.property = prop;\r
202         }\r
203 \r
204         public AuthRefInfo(AuthRefConfigInfo arci, Property prop) {\r
205             super(arci);\r
206             this.property = prop;\r
207         }\r
208     }\r
209     \r
210     private static final Logger logger = LoggerFactory.getLogger(RefNameServiceUtils.class);\r
211     private static ArrayList<String> refNameServiceTypes = null;\r
212 \r
213     public static void updateRefNamesInRelations(\r
214             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,\r
215             RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,\r
216             RepositoryInstance repoSession,\r
217             String oldRefName,\r
218             String newRefName) {\r
219         //\r
220         // First, look for and update all the places where the refName is the "subject" of the relationship\r
221         //\r
222         RelationUtils.updateRefNamesInRelations(ctx, repoClient, repoSession, IRelationsManager.SUBJECT_REFNAME, oldRefName, newRefName);\r
223         \r
224         //\r
225         // Next, look for and update all the places where the refName is the "object" of the relationship\r
226         //\r
227         RelationUtils.updateRefNamesInRelations(ctx, repoClient, repoSession, IRelationsManager.OBJECT_REFNAME, oldRefName, newRefName);\r
228     }\r
229     \r
230     public static List<AuthRefConfigInfo> getConfiguredAuthorityRefs(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {\r
231         List<String> authRefFields =\r
232                 ((AbstractServiceContextImpl) ctx).getAllPartsPropertyValues(\r
233                 ServiceBindingUtils.AUTH_REF_PROP, ServiceBindingUtils.QUALIFIED_PROP_NAMES);\r
234         ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>(authRefFields.size());\r
235         for (String spec : authRefFields) {\r
236             AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);\r
237             authRefsInfo.add(arci);\r
238         }\r
239         return authRefsInfo;\r
240     }\r
241 \r
242     public static AuthorityRefDocList getAuthorityRefDocs(\r
243             RepositoryInstance repoSession,\r
244             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,\r
245             UriTemplateRegistry uriTemplateRegistry,\r
246             RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,\r
247             List<String> serviceTypes,\r
248             String refName,\r
249             String refPropName, // authRef or termRef, authorities or vocab terms.\r
250             DocumentFilter filter, boolean computeTotal)\r
251             throws DocumentException, DocumentNotFoundException {\r
252         AuthorityRefDocList wrapperList = new AuthorityRefDocList();\r
253         AbstractCommonList commonList = (AbstractCommonList) wrapperList;\r
254         int pageNum = filter.getStartPage();\r
255         int pageSize = filter.getPageSize();\r
256         \r
257         List<AuthorityRefDocList.AuthorityRefDocItem> list =\r
258                 wrapperList.getAuthorityRefDocItem();\r
259 \r
260         Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();\r
261         Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();\r
262 \r
263         RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;\r
264         try {\r
265             // Ignore any provided page size and number query parameters in\r
266             // the following call, as they pertain to the list of authority\r
267             // references to be returned, not to the list of documents to be\r
268             // scanned for those references.\r
269             DocumentModelList docList = findAuthorityRefDocs(ctx, repoClient, repoSession,\r
270                     serviceTypes, refName, refPropName, queriedServiceBindings, authRefFieldsByService,\r
271                     filter.getWhereClause(), null, 0 /* pageSize */, 0 /* pageNum */, computeTotal);\r
272 \r
273             if (docList == null) { // found no authRef fields - nothing to process\r
274                 return wrapperList;\r
275             }\r
276 \r
277             // set the fieldsReturned list. Even though this is a fixed schema, app layer treats\r
278             // this like other abstract common lists\r
279             /*\r
280              * <xs:element name="docType" type="xs:string" minOccurs="1" />\r
281              * <xs:element name="docId" type="xs:string" minOccurs="1" />\r
282              * <xs:element name="docNumber" type="xs:string" minOccurs="0" />\r
283              * <xs:element name="docName" type="xs:string" minOccurs="0" />\r
284              * <xs:element name="sourceField" type="xs:string" minOccurs="1" />\r
285              * <xs:element name="uri" type="xs:anyURI" minOccurs="1" />\r
286              * <xs:element name="refName" type="xs:String" minOccurs="1" />\r
287              * <xs:element name="updatedAt" type="xs:string" minOccurs="1" />\r
288              * <xs:element name="workflowState" type="xs:string" minOccurs="1"\r
289              * />\r
290              */\r
291             String fieldList = "docType|docId|docNumber|docName|sourceField|uri|refName|updatedAt|workflowState";\r
292             commonList.setFieldsReturned(fieldList);\r
293 \r
294             // As a side-effect, the method called below modifies the value of\r
295             // the 'list' variable, which holds the list of references to\r
296             // an authority item.\r
297             //\r
298             // There can be more than one reference to a particular authority\r
299             // item within any individual document scanned, so the number of\r
300             // authority references may potentially exceed the total number\r
301             // of documents scanned.\r
302 \r
303             // Strip off displayName and only match the base, so we get references to all \r
304             // the NPTs as well as the PT.\r
305                 String strippedRefName = RefNameUtils.stripAuthorityTermDisplayName(refName);\r
306             int nRefsFound = processRefObjsDocList(docList, ctx.getTenantId(), strippedRefName, true, queriedServiceBindings, authRefFieldsByService, // the actual list size needs to be updated to the size of "list"\r
307                     list, null);\r
308 \r
309             commonList.setPageSize(pageSize);\r
310             \r
311             // Values returned in the pagination block above the list items\r
312             // need to reflect the number of references to authority items\r
313             // returned, rather than the number of documents originally scanned\r
314             // to find such references.\r
315             commonList.setPageNum(pageNum);\r
316             commonList.setTotalItems(list.size());\r
317 \r
318             // Slice the list to return only the specified page of items\r
319             // in the list results.\r
320             //\r
321             // FIXME: There may well be a pattern-based way to do this\r
322             // in our framework, and if we can eliminate much of the\r
323             // non-DRY code below, that would be desirable.\r
324             \r
325             int startIndex = 0;\r
326             int endIndex = 0;\r
327             \r
328             // Return all results if pageSize is 0.\r
329             if (pageSize == 0) {\r
330                 startIndex = 0;\r
331                 endIndex = list.size();\r
332             } else {\r
333                startIndex = pageNum * pageSize;\r
334             }\r
335             \r
336             // Return an empty list when the start of the requested page is\r
337             // beyond the last item in the list.\r
338             if (startIndex > list.size()) {\r
339                 wrapperList.getAuthorityRefDocItem().clear();\r
340                 commonList.setItemsInPage(wrapperList.getAuthorityRefDocItem().size());\r
341                 return wrapperList;\r
342             }\r
343 \r
344             // Otherwise, return a list of items from the start of the specified\r
345             // page through the last item on that page, or otherwise through the\r
346             // last item in the entire list, if that occurs earlier than the end\r
347             // of the specified page.\r
348             if (endIndex == 0) {\r
349                 int pageEndIndex = ((startIndex + pageSize));\r
350                 endIndex = (pageEndIndex > list.size()) ? list.size() : pageEndIndex;\r
351             }\r
352             \r
353             // Slice the list to return only the specified page of results.\r
354             // Note: the second argument to List.subList(), endIndex, is\r
355             // exclusive of the item at its index position, reflecting the\r
356             // zero-index nature of the list.\r
357             List<AuthorityRefDocList.AuthorityRefDocItem> currentPageList =\r
358                     new ArrayList<AuthorityRefDocList.AuthorityRefDocItem>(list.subList(startIndex, endIndex));\r
359             wrapperList.getAuthorityRefDocItem().clear();\r
360             wrapperList.getAuthorityRefDocItem().addAll(currentPageList);\r
361             commonList.setItemsInPage(currentPageList.size());\r
362             \r
363             if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {\r
364                 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.\r
365             }\r
366         } catch (Exception e) {\r
367             logger.error("Could not retrieve a list of documents referring to the specified authority item", e);\r
368             wrapperList = null;\r
369         }\r
370 \r
371         return wrapperList;\r
372     }\r
373 \r
374     private static ArrayList<String> getRefNameServiceTypes() {\r
375         if (refNameServiceTypes == null) {\r
376             refNameServiceTypes = new ArrayList<String>();\r
377             refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);\r
378             refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);\r
379             refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);\r
380         }\r
381         return refNameServiceTypes;\r
382     }\r
383     \r
384     // Seems like a good value - no real data to set this well.\r
385     // Note: can set this value lower during debugging; e.g. to 3 - ADR 2012-07-10\r
386     private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;\r
387 \r
388     public static int updateAuthorityRefDocs(\r
389             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,\r
390             RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,\r
391             RepositoryInstance repoSession,\r
392             String oldRefName,\r
393             String newRefName,\r
394             String refPropName) throws Exception {\r
395         Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();\r
396         Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();\r
397 \r
398         int docsScanned = 0;\r
399         int nRefsFound = 0;\r
400         int currentPage = 0;\r
401         int docsInCurrentPage = 0;\r
402         final String WHERE_CLAUSE_ADDITIONS_VALUE = null;\r
403         final String ORDER_BY_VALUE = CollectionSpaceClient.CORE_CREATED_AT; // "collectionspace_core:createdAt";\r
404 \r
405         if (repoClient instanceof RepositoryJavaClientImpl == false) {\r
406             throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");\r
407         }\r
408         \r
409         try { // REM - How can we deal with transaction and timeout issues here?\r
410             final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;\r
411             DocumentModelList docList;\r
412             boolean morePages = true;\r
413             while (morePages) {\r
414 \r
415                 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,\r
416                         getRefNameServiceTypes(), oldRefName, refPropName,\r
417                         queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);\r
418 \r
419                 if (docList == null) {\r
420                     logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");\r
421                     break;\r
422                 }\r
423                 docsInCurrentPage = docList.size();\r
424                 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);\r
425                 if (docsInCurrentPage == 0) {\r
426                     logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");\r
427                     break;\r
428                 }\r
429                 if (docsInCurrentPage < pageSize) {\r
430                     logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");\r
431                     morePages = false;\r
432                 }\r
433 \r
434                 // Only match complete refNames - unless and until we decide how to resolve changes\r
435                 // to NPTs we will defer that and only change PTs or refNames as passed in.\r
436                 int nRefsFoundThisPage = processRefObjsDocList(docList, ctx.getTenantId(), oldRefName, false, queriedServiceBindings, authRefFieldsByService, // Perform the refName updates on the list of document models\r
437                         null, newRefName);\r
438                 if (nRefsFoundThisPage > 0) {\r
439                     ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true); // Flush the document model list out to Nuxeo storage\r
440                     nRefsFound += nRefsFoundThisPage;\r
441                 }\r
442 \r
443                 // FIXME: Per REM, set a limit of num objects - something like\r
444                 // 1000K objects - and also add a log Warning after some threshold\r
445                 docsScanned += docsInCurrentPage;\r
446                 if (morePages) {\r
447                     currentPage++;\r
448                 }\r
449 \r
450             }\r
451         } catch (Exception e) {\r
452             logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());\r
453             logger.debug(Tools.errorToString(e, true));\r
454             throw e;\r
455         }\r
456         logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");\r
457         return nRefsFound;\r
458     }\r
459 \r
460     private static DocumentModelList findAuthorityRefDocs(\r
461             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,\r
462             RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,\r
463             RepositoryInstance repoSession, List<String> serviceTypes,\r
464             String refName,\r
465             String refPropName,\r
466             Map<String, ServiceBindingType> queriedServiceBindings,\r
467             Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,\r
468             String whereClauseAdditions,\r
469             String orderByClause,\r
470             int pageSize,\r
471             int pageNum,\r
472             boolean computeTotal) throws DocumentException, DocumentNotFoundException {\r
473 \r
474         // Get the service bindings for this tenant\r
475         TenantBindingConfigReaderImpl tReader =\r
476                 ServiceMain.getInstance().getTenantBindingConfigReader();\r
477         // We need to get all the procedures, authorities, and objects.\r
478         List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);\r
479         if (servicebindings == null || servicebindings.isEmpty()) {\r
480             logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");\r
481             return null;\r
482         }\r
483         // Filter the list for current user rights\r
484         servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);\r
485 \r
486         ArrayList<String> docTypes = new ArrayList<String>();\r
487 \r
488         String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings, // REM - Side effect that docTypes array gets set.  Any others?\r
489                 queriedServiceBindings, authRefFieldsByService);\r
490         if (query == null) { // found no authRef fields - nothing to query\r
491             return null;\r
492         }\r
493         // Additional qualifications, like workflow state\r
494         if (Tools.notBlank(whereClauseAdditions)) {\r
495             query += " AND " + whereClauseAdditions;\r
496         }\r
497         // Now we have to issue the search\r
498         RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;\r
499         DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,\r
500                 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);\r
501         // Now we gather the info for each document into the list and return\r
502         DocumentModelList docList = docListWrapper.getWrappedObject();\r
503         return docList;\r
504     }\r
505     private static final boolean READY_FOR_COMPLEX_QUERY = true;\r
506 \r
507     private static String computeWhereClauseForAuthorityRefDocs(\r
508             String refName,\r
509             String refPropName,\r
510             ArrayList<String> docTypes,\r
511             List<ServiceBindingType> servicebindings,\r
512             Map<String, ServiceBindingType> queriedServiceBindings,\r
513             Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {\r
514 \r
515         boolean fFirst = true;\r
516         List<String> authRefFieldPaths;\r
517         for (ServiceBindingType sb : servicebindings) {\r
518             // Gets the property names for each part, qualified with the part label (which\r
519             // is also the table name, the way that the repository works).\r
520             authRefFieldPaths =\r
521                     ServiceBindingUtils.getAllPartsPropertyValues(sb,\r
522                     refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);\r
523             if (authRefFieldPaths.isEmpty()) {\r
524                 continue;\r
525             }\r
526             ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();\r
527             for (String spec : authRefFieldPaths) {\r
528                 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);\r
529                 authRefsInfo.add(arci);\r
530             }\r
531 \r
532             String docType = sb.getObject().getName();\r
533             queriedServiceBindings.put(docType, sb);\r
534             authRefFieldsByService.put(docType, authRefsInfo);\r
535             docTypes.add(docType);\r
536             fFirst = false;\r
537         }\r
538         if (fFirst) { // found no authRef fields - nothing to query\r
539             return null;\r
540         }\r
541         // We used to build a complete matches query, but that was too complex.\r
542         // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID\r
543         // Note that this will also match the Item itself, but that will get filtered out when\r
544         // we compute actual matches.\r
545         AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);\r
546 \r
547         String keywords = RefNameUtils.URN_PREFIX\r
548                 + " AND " + (authTermInfo.inAuthority.name != null\r
549                 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)\r
550                 + " AND " + (authTermInfo.name != null\r
551                 ? authTermInfo.name : authTermInfo.csid);\r
552 \r
553         String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);\r
554 \r
555         if (logger.isTraceEnabled()) {\r
556             logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);\r
557         }\r
558 \r
559         return whereClauseStr;\r
560     }\r
561     \r
562     // TODO there are multiple copies of this that should be put somewhere common.\r
563         protected static String getRefname(DocumentModel docModel) throws ClientException {\r
564                 String result = (String)docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,\r
565                                 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);\r
566                 return result;\r
567         }\r
568 \r
569 \r
570     /*\r
571      * Runs through the list of found docs, processing them. If list is\r
572      * non-null, then processing means gather the info for items. If list is\r
573      * null, and newRefName is non-null, then processing means replacing and\r
574      * updating. If processing/updating, this must be called in the context of\r
575      * an open session, and caller must release Session after calling this.\r
576      *\r
577      */\r
578     private static int processRefObjsDocList(\r
579             DocumentModelList docList,\r
580             String tenantId,\r
581             String refName,\r
582             boolean matchBaseOnly,\r
583             Map<String, ServiceBindingType> queriedServiceBindings,\r
584             Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,\r
585             List<AuthorityRefDocList.AuthorityRefDocItem> list,\r
586             String newAuthorityRefName) {\r
587         Iterator<DocumentModel> iter = docList.iterator();\r
588         int nRefsFoundTotal = 0;\r
589         while (iter.hasNext()) {\r
590             DocumentModel docModel = iter.next();\r
591             AuthorityRefDocList.AuthorityRefDocItem ilistItem;\r
592 \r
593             String docType = docModel.getDocumentType().getName(); // REM - This will be a tentant qualified document type\r
594             docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);\r
595             ServiceBindingType sb = queriedServiceBindings.get(docType);\r
596             if (sb == null) {\r
597                 throw new RuntimeException(\r
598                         "getAuthorityRefDocs: No Service Binding for docType: " + docType);\r
599             }\r
600 \r
601             if (list == null) { // no list - should be update refName case.\r
602                 if (newAuthorityRefName == null) {\r
603                     throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");\r
604                 }\r
605                 ilistItem = null;\r
606             } else {    // Have a list - refObjs case\r
607                 if (newAuthorityRefName != null) {\r
608                     throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");\r
609                 }\r
610                 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();\r
611                 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());\r
612                 try {\r
613                         String itemRefName = getRefname(docModel);\r
614                         ilistItem.setRefName(itemRefName);\r
615                 } catch (ClientException ce) {\r
616                     throw new RuntimeException(\r
617                             "processRefObjsDocList: Problem fetching refName from item Object: " \r
618                                         + ce.getLocalizedMessage());\r
619                 }\r
620                 ilistItem.setDocId(csid);\r
621                 String uri = "";\r
622                 UriTemplateRegistry registry = ServiceMain.getInstance().getUriTemplateRegistry();\r
623                 UriTemplateRegistryKey key = new UriTemplateRegistryKey(tenantId, docType);\r
624                 StoredValuesUriTemplate template = registry.get(key);\r
625                 if (template != null) {\r
626                     Map<String, String> additionalValues = new HashMap<String, String>();\r
627                     if (template.getUriTemplateType() == UriTemplateFactory.RESOURCE) {\r
628                         additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, csid);\r
629                         uri = template.buildUri(additionalValues);\r
630                     } else if (template.getUriTemplateType() == UriTemplateFactory.ITEM) {\r
631                         try {\r
632                             String inAuthorityCsid = (String) docModel.getPropertyValue("inAuthority"); // AuthorityItemJAXBSchema.IN_AUTHORITY\r
633                             additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, inAuthorityCsid);\r
634                             additionalValues.put(UriTemplateFactory.ITEM_IDENTIFIER_VAR, csid);\r
635                             uri = template.buildUri(additionalValues);\r
636                         } catch (Exception e) {\r
637                             logger.warn("Could not extract inAuthority property from authority item record: " + e.getMessage());\r
638                         }\r
639                     } else if (template.getUriTemplateType() == UriTemplateFactory.CONTACT) {\r
640                         // FIXME: Generating contact sub-resource URIs requires additional work,\r
641                         // as a follow-on to CSPACE-5271 - ADR 2012-08-16\r
642                         // Sets the default (empty string) value for uri, for now\r
643                     } else {\r
644                         logger.warn("Unrecognized URI template type = " + template.getUriTemplateType());\r
645                         // Sets the default (empty string) value for uri\r
646                     }\r
647                 } else { // (if template == null)\r
648                     logger.warn("Could not retrieve URI template from registry via tenant ID "\r
649                             + tenantId + " and docType " + docType);\r
650                     // Sets the default (empty string) value for uri\r
651                 }\r
652                 ilistItem.setUri(uri);\r
653                 try {\r
654                     ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());\r
655                     ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));\r
656                 } catch (Exception e) {\r
657                     logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());\r
658                 }\r
659                 ilistItem.setDocType(docType);\r
660                 ilistItem.setDocNumber(\r
661                         ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));\r
662                 ilistItem.setDocName(\r
663                         ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));\r
664             }\r
665             // Now, we have to loop over the authRefFieldsByService to figure\r
666             // out which field(s) matched this.\r
667             List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);\r
668             if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {\r
669                 throw new RuntimeException(\r
670                         "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");\r
671             }\r
672             //String authRefAncestorField = "";\r
673             //String authRefDescendantField = "";\r
674             //String sourceField = "";\r
675             int nRefsFoundInDoc = 0;\r
676 \r
677             ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();\r
678             try {\r
679                 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, matchBaseOnly, foundProps); // REM - side effect that foundProps is set\r
680                 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {\r
681                     if (ilistItem != null) {\r
682                         if (nRefsFoundInDoc == 0) {    // First one?\r
683                             ilistItem.setSourceField(ari.getQualifiedDisplayName());\r
684                         } else {    // duplicates from one object\r
685                             ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());\r
686                         }\r
687                         list.add(ilistItem);\r
688                     } else {    // update refName case\r
689                         Property propToUpdate = ari.getProperty();\r
690                         propToUpdate.setValue(newAuthorityRefName);\r
691                     }\r
692                     nRefsFoundInDoc++;\r
693                 }\r
694             } catch (ClientException ce) {\r
695                 throw new RuntimeException(\r
696                         "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());\r
697             }\r
698             if (nRefsFoundInDoc == 0) {\r
699                 logger.warn(\r
700                         "getAuthorityRefDocs: Result: "\r
701                         + docType + " [" + NuxeoUtils.getCsid(docModel)\r
702                         + "] does not reference ["\r
703                         + refName + "]");\r
704             }\r
705             nRefsFoundTotal += nRefsFoundInDoc;\r
706         }\r
707         return nRefsFoundTotal;\r
708     }\r
709 \r
710     private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(\r
711             AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {\r
712         AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();\r
713         newlistItem.setDocId(ilistItem.getDocId());\r
714         newlistItem.setDocName(ilistItem.getDocName());\r
715         newlistItem.setDocNumber(ilistItem.getDocNumber());\r
716         newlistItem.setDocType(ilistItem.getDocType());\r
717         newlistItem.setUri(ilistItem.getUri());\r
718         newlistItem.setSourceField(sourceField);\r
719         return newlistItem;\r
720     }\r
721 \r
722     public static List<AuthRefInfo> findAuthRefPropertiesInDoc(\r
723             DocumentModel docModel,\r
724             List<AuthRefConfigInfo> authRefFieldInfo,\r
725             String refNameToMatch,\r
726             List<AuthRefInfo> foundProps) {\r
727         return findAuthRefPropertiesInDoc(docModel, authRefFieldInfo, \r
728                                                                         refNameToMatch, false, foundProps);\r
729     }\r
730     \r
731     public static List<AuthRefInfo> findAuthRefPropertiesInDoc(\r
732             DocumentModel docModel,\r
733             List<AuthRefConfigInfo> authRefFieldInfo,\r
734             String refNameToMatch,\r
735             boolean matchBaseOnly,\r
736             List<AuthRefInfo> foundProps) {\r
737         // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)\r
738         // and the values are elPaths to the field, where intervening group structures in\r
739         // lists of complex structures are replaced with "*". Thus, valid paths include\r
740         // the following (note that the ServiceBindingUtils prepend schema names to configured values):\r
741         // "schemaname:fieldname"\r
742         // "schemaname:scalarlistname"\r
743         // "schemaname:complexfieldname/fieldname"\r
744         // "schemaname:complexlistname/*/fieldname"\r
745         // "schemaname:complexlistname/*/scalarlistname"\r
746         // "schemaname:complexlistname/*/complexfieldname/fieldname"\r
747         // "schemaname:complexlistname/*/complexlistname/*/fieldname"\r
748         // etc.\r
749         for (AuthRefConfigInfo arci : authRefFieldInfo) {\r
750             try {\r
751                 // Get first property and work down as needed.\r
752                 Property prop = docModel.getProperty(arci.pathEls[0]);\r
753                 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch, matchBaseOnly);\r
754             } catch (Exception e) {\r
755                 logger.error("Problem fetching property: " + arci.pathEls[0]);\r
756             }\r
757         }\r
758         return foundProps;\r
759     }\r
760 \r
761     private static List<AuthRefInfo> findAuthRefPropertiesInProperty(\r
762             List<AuthRefInfo> foundProps,\r
763             Property prop,\r
764             AuthRefConfigInfo arci,\r
765             int pathStartIndex, // Supports recursion and we work down the path\r
766             String refNameToMatch,\r
767             boolean matchBaseOnly ) {\r
768         if (pathStartIndex >= arci.pathEls.length) {\r
769             throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "\r
770                     + arci.pathEls.toString());\r
771         }\r
772         AuthRefInfo ari = null;\r
773         if (prop == null) {\r
774             return foundProps;\r
775         }\r
776 \r
777         if (prop instanceof StringProperty) {    // scalar string\r
778             addARIifMatches(refNameToMatch, matchBaseOnly, arci, prop, foundProps);\r
779         } else if (prop instanceof List) {\r
780             List<Property> propList = (List<Property>) prop;\r
781             // run through list. Must either be list of Strings, or Complex\r
782             for (Property listItemProp : propList) {\r
783                 if (listItemProp instanceof StringProperty) {\r
784                     if (arci.pathEls.length - pathStartIndex != 1) {\r
785                         logger.error("Configuration for authRefs does not match schema structure: "\r
786                                 + arci.pathEls.toString());\r
787                         break;\r
788                     } else {\r
789                         addARIifMatches(refNameToMatch, matchBaseOnly, arci, listItemProp, foundProps);\r
790                     }\r
791                 } else if (listItemProp.isComplex()) {\r
792                     // Just recurse to handle this. Note that since this is a list of complex, \r
793                     // which should look like listName/*/... we add 2 to the path start index \r
794                     findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,\r
795                             pathStartIndex + 2, refNameToMatch, matchBaseOnly);\r
796                 } else {\r
797                     logger.error("Configuration for authRefs does not match schema structure: "\r
798                             + arci.pathEls.toString());\r
799                     break;\r
800                 }\r
801             }\r
802         } else if (prop.isComplex()) {\r
803             String localPropName = arci.pathEls[pathStartIndex];\r
804             try {\r
805                 Property localProp = prop.get(localPropName);\r
806                 // Now just recurse, pushing down the path 1 step\r
807                 findAuthRefPropertiesInProperty(foundProps, localProp, arci,\r
808                         pathStartIndex, refNameToMatch, matchBaseOnly);\r
809             } catch (PropertyNotFoundException pnfe) {\r
810                 logger.error("Could not find property: [" + localPropName + "] in path: "\r
811                         + arci.getFullPath());\r
812                 // Fall through - ari will be null and we will continue...\r
813             }\r
814         } else {\r
815             logger.error("Configuration for authRefs does not match schema structure: "\r
816                     + arci.pathEls.toString());\r
817         }\r
818 \r
819         if (ari != null) {\r
820             foundProps.add(ari); //FIXME: REM - This is dead code.  'ari' is never touched after being initalized to null.  Why?\r
821         }\r
822 \r
823         return foundProps;\r
824     }\r
825 \r
826     private static void addARIifMatches(\r
827             String refNameToMatch,\r
828             boolean matchBaseOnly,\r
829             AuthRefConfigInfo arci,\r
830             Property prop,\r
831             List<AuthRefInfo> foundProps) {\r
832         // Need to either match a passed refName \r
833         // OR have no refName to match but be non-empty\r
834         try {\r
835             String value = (String) prop.getValue();\r
836             if (((refNameToMatch != null) && \r
837                                 (matchBaseOnly?\r
838                                         (value!=null && value.startsWith(refNameToMatch))\r
839                                         :refNameToMatch.equals(value)))\r
840                     || ((refNameToMatch == null) && Tools.notBlank(value))) {\r
841                 // Found a match\r
842                 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");\r
843                 AuthRefInfo ari = new AuthRefInfo(arci, prop);\r
844                 foundProps.add(ari);\r
845             }\r
846         } catch (PropertyException pe) {\r
847             logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());\r
848         }\r
849     }\r
850 \r
851     /*\r
852      * Identifies whether the refName was found in the supplied field. If passed\r
853      * a new RefName, will set that into fields in which the old one was found.\r
854      *\r
855      * Only works for: * Scalar fields * Repeatable scalar fields (aka\r
856      * multi-valued fields)\r
857      *\r
858      * Does not work for: * Structured fields (complexTypes) * Repeatable\r
859      * structured fields (repeatable complexTypes) private static int\r
860      * refNameFoundInField(String oldRefName, Property fieldValue, String\r
861      * newRefName) { int nFound = 0; if (fieldValue instanceof List) {\r
862      * List<Property> fieldValueList = (List) fieldValue; for (Property\r
863      * listItemValue : fieldValueList) { try { if ((listItemValue instanceof\r
864      * StringProperty) &&\r
865      * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {\r
866      * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else\r
867      * { // We cannot quit after the first, if we are replacing values. // If we\r
868      * are just looking (not replacing), finding one is enough. break; } } }\r
869      * catch( PropertyException pe ) {} } } else { try { if ((fieldValue\r
870      * instanceof StringProperty) &&\r
871      * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;\r
872      * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(\r
873      * PropertyException pe ) {} } return nFound; }\r
874      */\r
875 }\r