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