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