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