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