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