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