2 * This document is a part of the source code and related artifacts for
\r
3 * CollectionSpace, an open source collections management system for museums and
\r
4 * related institutions:
\r
6 * http://www.collectionspace.org http://wiki.collectionspace.org
\r
8 * Copyright 2009 University of California at Berkeley
\r
10 * Licensed under the Educational Community License (ECL), Version 2.0. You may
\r
11 * not use this file except in compliance with this License.
\r
13 * You may obtain a copy of the ECL 2.0 License at
\r
15 * https://source.collectionspace.org/collection-space/LICENSE.txt
\r
17 * Unless required by applicable law or agreed to in writing, software
\r
18 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
\r
19 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
\r
20 * License for the specific language governing permissions and limitations under
\r
23 package org.collectionspace.services.common.vocabulary;
\r
25 import java.util.ArrayList;
\r
26 import java.util.HashMap;
\r
27 import java.util.Iterator;
\r
28 import java.util.List;
\r
29 import java.util.Map;
\r
31 import org.nuxeo.ecm.core.api.ClientException;
\r
32 import org.nuxeo.ecm.core.api.DocumentModel;
\r
33 import org.nuxeo.ecm.core.api.DocumentModelList;
\r
34 import org.nuxeo.ecm.core.api.model.Property;
\r
35 import org.nuxeo.ecm.core.api.model.PropertyException;
\r
36 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
\r
37 import org.nuxeo.ecm.core.api.model.impl.primitives.StringProperty;
\r
38 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
\r
40 import org.slf4j.Logger;
\r
41 import org.slf4j.LoggerFactory;
\r
43 import org.collectionspace.services.client.CollectionSpaceClient;
\r
44 import org.collectionspace.services.client.IRelationsManager;
\r
45 import org.collectionspace.services.client.PoxPayloadIn;
\r
46 import org.collectionspace.services.client.PoxPayloadOut;
\r
47 import org.collectionspace.services.common.ServiceMain;
\r
48 import org.collectionspace.services.common.StoredValuesUriTemplate;
\r
49 import org.collectionspace.services.common.UriTemplateFactory;
\r
50 import org.collectionspace.services.common.UriTemplateRegistry;
\r
51 import org.collectionspace.services.common.UriTemplateRegistryKey;
\r
52 import org.collectionspace.services.common.context.ServiceContext;
\r
53 import org.collectionspace.services.common.context.AbstractServiceContextImpl;
\r
54 import org.collectionspace.services.common.api.RefNameUtils;
\r
55 import org.collectionspace.services.common.api.Tools;
\r
56 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
\r
57 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
\r
58 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
\r
59 import org.collectionspace.services.common.context.ServiceBindingUtils;
\r
60 import org.collectionspace.services.common.document.DocumentException;
\r
61 import org.collectionspace.services.common.document.DocumentFilter;
\r
62 import org.collectionspace.services.common.document.DocumentNotFoundException;
\r
63 import org.collectionspace.services.common.document.DocumentUtils;
\r
64 import org.collectionspace.services.common.document.DocumentWrapper;
\r
65 import org.collectionspace.services.common.query.QueryManager;
\r
66 import org.collectionspace.services.common.relation.RelationUtils;
\r
67 import org.collectionspace.services.common.repository.RepositoryClient;
\r
68 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
\r
69 import org.collectionspace.services.nuxeo.client.java.RepositoryInstanceInterface;
\r
70 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
\r
71 import org.collectionspace.services.common.security.SecurityUtils;
\r
72 import org.collectionspace.services.config.service.ServiceBindingType;
\r
73 import org.collectionspace.services.jaxb.AbstractCommonList;
\r
74 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
\r
77 * RefNameServiceUtils is a collection of services utilities related to refName
\r
80 * $LastChangedRevision: $ $LastChangedDate: $
\r
82 public class RefNameServiceUtils {
\r
84 public static class AuthRefConfigInfo {
\r
86 public String getQualifiedDisplayName() {
\r
87 return (Tools.isBlank(schema))
\r
88 ? displayName : DocumentUtils.appendSchemaName(schema, displayName);
\r
91 public String getDisplayName() {
\r
95 public void setDisplayName(String displayName) {
\r
96 this.displayName = displayName;
\r
101 public String getSchema() {
\r
105 public void setSchema(String schema) {
\r
106 this.schema = schema;
\r
109 public String getFullPath() {
\r
113 public void setFullPath(String fullPath) {
\r
114 this.fullPath = fullPath;
\r
117 protected String[] pathEls;
\r
119 public AuthRefConfigInfo(AuthRefConfigInfo arci) {
\r
120 this.displayName = arci.displayName;
\r
121 this.schema = arci.schema;
\r
122 this.fullPath = arci.fullPath;
\r
123 this.pathEls = arci.pathEls;
\r
124 // Skip the pathElse check, since we are creatign from another (presumably valid) arci.
\r
127 public AuthRefConfigInfo(String displayName, String schema, String fullPath, String[] pathEls) {
\r
128 this.displayName = displayName;
\r
129 this.schema = schema;
\r
130 this.fullPath = fullPath;
\r
131 this.pathEls = pathEls;
\r
135 // Split a config value string like "intakes_common:collector", or
\r
136 // "collectionobjects_common:contentPeoples|contentPeople"
\r
137 // "collectionobjects_common:assocEventGroupList/*/assocEventPlace"
\r
138 // If has a pipe ('|') second part is a displayLabel, and first is path
\r
139 // Otherwise, entry is a path, and can use the last pathElement as displayName
\r
140 // Should be schema qualified.
\r
141 public AuthRefConfigInfo(String configString) {
\r
142 String[] pair = configString.split("\\|", 2);
\r
144 String displayName, fullPath;
\r
145 if (pair.length == 1) {
\r
146 // no label specifier, so we'll defer getting label
\r
147 fullPath = pair[0];
\r
148 pathEls = pair[0].split("/");
\r
149 displayName = pathEls[pathEls.length - 1];
\r
151 fullPath = pair[0];
\r
152 pathEls = pair[0].split("/");
\r
153 displayName = pair[1];
\r
155 String[] schemaSplit = pathEls[0].split(":", 2);
\r
157 if (schemaSplit.length == 1) { // schema not specified
\r
160 schema = schemaSplit[0];
\r
161 if (pair.length == 1 && pathEls.length == 1) { // simplest case of field in top level schema, no labelll
\r
162 displayName = schemaSplit[1]; // Have to fix up displayName to have no schema
\r
165 this.displayName = displayName;
\r
166 this.schema = schema;
\r
167 this.fullPath = fullPath;
\r
168 this.pathEls = pathEls;
\r
172 protected void checkPathEls() {
\r
173 int len = pathEls.length;
\r
175 throw new InternalError("Bad values in authRef info - caller screwed up:" + fullPath);
\r
177 // Handle case of them putting a leading slash on the path
\r
178 if (len > 1 && pathEls[0].endsWith(":")) {
\r
180 String[] newArray = new String[len];
\r
181 newArray[0] = pathEls[0] + pathEls[1];
\r
183 System.arraycopy(pathEls, 2, newArray, 1, len - 1);
\r
185 pathEls = newArray;
\r
190 public static class AuthRefInfo extends AuthRefConfigInfo {
\r
192 public Property getProperty() {
\r
196 public void setProperty(Property property) {
\r
197 this.property = property;
\r
201 public AuthRefInfo(String displayName, String schema, String fullPath, String[] pathEls, Property prop) {
\r
202 super(displayName, schema, fullPath, pathEls);
\r
203 this.property = prop;
\r
206 public AuthRefInfo(AuthRefConfigInfo arci, Property prop) {
\r
208 this.property = prop;
\r
212 private static final Logger logger = LoggerFactory.getLogger(RefNameServiceUtils.class);
\r
213 private static ArrayList<String> refNameServiceTypes = null;
\r
215 public static void updateRefNamesInRelations(
\r
216 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
217 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
218 RepositoryInstanceInterface repoSession,
\r
220 String newRefName) {
\r
222 // First, look for and update all the places where the refName is the "subject" of the relationship
\r
224 RelationUtils.updateRefNamesInRelations(ctx, repoClient, repoSession, IRelationsManager.SUBJECT_REFNAME, oldRefName, newRefName);
\r
227 // Next, look for and update all the places where the refName is the "object" of the relationship
\r
229 RelationUtils.updateRefNamesInRelations(ctx, repoClient, repoSession, IRelationsManager.OBJECT_REFNAME, oldRefName, newRefName);
\r
232 public static List<AuthRefConfigInfo> getConfiguredAuthorityRefs(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
\r
233 List<String> authRefFields =
\r
234 ((AbstractServiceContextImpl) ctx).getAllPartsPropertyValues(
\r
235 ServiceBindingUtils.AUTH_REF_PROP, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
236 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>(authRefFields.size());
\r
237 for (String spec : authRefFields) {
\r
238 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
239 authRefsInfo.add(arci);
\r
241 return authRefsInfo;
\r
244 public static AuthorityRefDocList getAuthorityRefDocs(
\r
245 RepositoryInstanceInterface repoSession,
\r
246 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
247 UriTemplateRegistry uriTemplateRegistry,
\r
248 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
249 List<String> serviceTypes,
\r
251 String refPropName, // authRef or termRef, authorities or vocab terms.
\r
252 DocumentFilter filter, boolean computeTotal)
\r
253 throws DocumentException, DocumentNotFoundException {
\r
254 AuthorityRefDocList wrapperList = new AuthorityRefDocList();
\r
255 AbstractCommonList commonList = (AbstractCommonList) wrapperList;
\r
256 int pageNum = filter.getStartPage();
\r
257 int pageSize = filter.getPageSize();
\r
259 List<AuthorityRefDocList.AuthorityRefDocItem> list =
\r
260 wrapperList.getAuthorityRefDocItem();
\r
262 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
263 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
265 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
267 // Ignore any provided page size and number query parameters in
\r
268 // the following call, as they pertain to the list of authority
\r
269 // references to be returned, not to the list of documents to be
\r
270 // scanned for those references.
\r
272 // Get a list of possibly referencing documents. This list is
\r
273 // lazily loaded, page by page. Ideally, only one page will
\r
274 // need to be loaded to fill one page of results. Some number
\r
275 // of possibly referencing documents will be false positives,
\r
276 // so use a page size of double the requested page size to
\r
277 // account for those.
\r
278 DocumentModelList docList = findAllAuthorityRefDocs(ctx, repoClient, repoSession,
\r
279 serviceTypes, refName, refPropName, queriedServiceBindings, authRefFieldsByService,
\r
280 filter.getWhereClause(), null, 2*pageSize, computeTotal);
\r
282 if (docList == null) { // found no authRef fields - nothing to process
\r
283 return wrapperList;
\r
286 // set the fieldsReturned list. Even though this is a fixed schema, app layer treats
\r
287 // this like other abstract common lists
\r
289 * <xs:element name="docType" type="xs:string" minOccurs="1" />
\r
290 * <xs:element name="docId" type="xs:string" minOccurs="1" />
\r
291 * <xs:element name="docNumber" type="xs:string" minOccurs="0" />
\r
292 * <xs:element name="docName" type="xs:string" minOccurs="0" />
\r
293 * <xs:element name="sourceField" type="xs:string" minOccurs="1" />
\r
294 * <xs:element name="uri" type="xs:anyURI" minOccurs="1" />
\r
295 * <xs:element name="refName" type="xs:String" minOccurs="1" />
\r
296 * <xs:element name="updatedAt" type="xs:string" minOccurs="1" />
\r
297 * <xs:element name="workflowState" type="xs:string" minOccurs="1"
\r
300 String fieldList = "docType|docId|docNumber|docName|sourceField|uri|refName|updatedAt|workflowState";
\r
301 commonList.setFieldsReturned(fieldList);
\r
303 // As a side-effect, the method called below modifies the value of
\r
304 // the 'list' variable, which holds the list of references to
\r
305 // an authority item.
\r
307 // There can be more than one reference to a particular authority
\r
308 // item within any individual document scanned, so the number of
\r
309 // authority references may potentially exceed the total number
\r
310 // of documents scanned.
\r
312 // Strip off displayName and only match the base, so we get references to all
\r
313 // the NPTs as well as the PT.
\r
314 String strippedRefName = RefNameUtils.stripAuthorityTermDisplayName(refName);
\r
316 // *** Need to pass in pagination info here.
\r
317 int nRefsFound = processRefObjsDocListForList(docList, ctx.getTenantId(), strippedRefName,
\r
318 queriedServiceBindings, authRefFieldsByService, // the actual list size needs to be updated to the size of "list"
\r
319 list, pageSize, pageNum);
\r
321 commonList.setPageSize(pageSize);
\r
323 // Values returned in the pagination block above the list items
\r
324 // need to reflect the number of references to authority items
\r
325 // returned, rather than the number of documents originally scanned
\r
326 // to find such references.
\r
327 // This will be an estimate only...
\r
328 commonList.setPageNum(pageNum);
\r
329 commonList.setTotalItems(nRefsFound); // Accurate if total was scanned, otherwise, just an estimate
\r
330 commonList.setItemsInPage(list.size());
\r
332 /* Pagination is now handled in the processing step
\r
333 // Slice the list to return only the specified page of items
\r
334 // in the list results.
\r
336 // FIXME: There may well be a pattern-based way to do this
\r
337 // in our framework, and if we can eliminate much of the
\r
338 // non-DRY code below, that would be desirable.
\r
340 int startIndex = 0;
\r
343 // Return all results if pageSize is 0.
\r
344 if (pageSize == 0) {
\r
346 endIndex = list.size();
\r
348 startIndex = pageNum * pageSize;
\r
351 // Return an empty list when the start of the requested page is
\r
352 // beyond the last item in the list.
\r
353 if (startIndex > list.size()) {
\r
354 wrapperList.getAuthorityRefDocItem().clear();
\r
355 commonList.setItemsInPage(wrapperList.getAuthorityRefDocItem().size());
\r
356 return wrapperList;
\r
359 // Otherwise, return a list of items from the start of the specified
\r
360 // page through the last item on that page, or otherwise through the
\r
361 // last item in the entire list, if that occurs earlier than the end
\r
362 // of the specified page.
\r
363 if (endIndex == 0) {
\r
364 int pageEndIndex = ((startIndex + pageSize));
\r
365 endIndex = (pageEndIndex > list.size()) ? list.size() : pageEndIndex;
\r
368 // Slice the list to return only the specified page of results.
\r
369 // Note: the second argument to List.subList(), endIndex, is
\r
370 // exclusive of the item at its index position, reflecting the
\r
371 // zero-index nature of the list.
\r
372 List<AuthorityRefDocList.AuthorityRefDocItem> currentPageList =
\r
373 new ArrayList<AuthorityRefDocList.AuthorityRefDocItem>(list.subList(startIndex, endIndex));
\r
374 wrapperList.getAuthorityRefDocItem().clear();
\r
375 wrapperList.getAuthorityRefDocItem().addAll(currentPageList);
\r
376 commonList.setItemsInPage(currentPageList.size());
\r
379 if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {
\r
380 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.
\r
382 } catch (Exception e) {
\r
383 logger.error("Could not retrieve a list of documents referring to the specified authority item", e);
\r
384 wrapperList = null;
\r
387 return wrapperList;
\r
390 private static ArrayList<String> getRefNameServiceTypes() {
\r
391 if (refNameServiceTypes == null) {
\r
392 refNameServiceTypes = new ArrayList<String>();
\r
393 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);
\r
394 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);
\r
395 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);
\r
397 return refNameServiceTypes;
\r
400 // Seems like a good value - no real data to set this well.
\r
401 // Note: can set this value lower during debugging; e.g. to 3 - ADR 2012-07-10
\r
402 private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;
\r
404 public static int updateAuthorityRefDocs(
\r
405 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
406 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
407 RepositoryInstanceInterface repoSession,
\r
410 String refPropName) throws Exception {
\r
411 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
412 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
414 int docsScanned = 0;
\r
415 int nRefsFound = 0;
\r
416 int currentPage = 0;
\r
417 int docsInCurrentPage = 0;
\r
418 final String WHERE_CLAUSE_ADDITIONS_VALUE = null;
\r
419 final String ORDER_BY_VALUE = CollectionSpaceClient.CORE_CREATED_AT; // "collectionspace_core:createdAt";
\r
421 if (repoClient instanceof RepositoryJavaClientImpl == false) {
\r
422 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");
\r
425 try { // REM - How can we deal with transaction and timeout issues here?
\r
426 final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;
\r
427 DocumentModelList docList;
\r
428 boolean morePages = true;
\r
429 while (morePages) {
\r
431 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
432 getRefNameServiceTypes(), oldRefName, refPropName,
\r
433 queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);
\r
435 if (docList == null) {
\r
436 logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");
\r
439 docsInCurrentPage = docList.size();
\r
440 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);
\r
441 if (docsInCurrentPage == 0) {
\r
442 logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");
\r
445 if (docsInCurrentPage < pageSize) {
\r
446 logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");
\r
450 // Only match complete refNames - unless and until we decide how to resolve changes
\r
451 // to NPTs we will defer that and only change PTs or refNames as passed in.
\r
452 int nRefsFoundThisPage = processRefObjsDocListForUpdate(docList, ctx.getTenantId(), oldRefName,
\r
453 queriedServiceBindings, authRefFieldsByService, // Perform the refName updates on the list of document models
\r
455 if (nRefsFoundThisPage > 0) {
\r
456 ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true); // Flush the document model list out to Nuxeo storage
\r
457 nRefsFound += nRefsFoundThisPage;
\r
460 // FIXME: Per REM, set a limit of num objects - something like
\r
461 // 1000K objects - and also add a log Warning after some threshold
\r
462 docsScanned += docsInCurrentPage;
\r
468 } catch (Exception e) {
\r
469 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());
\r
470 logger.debug(Tools.errorToString(e, true));
\r
473 logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");
\r
477 private static DocumentModelList findAllAuthorityRefDocs(
\r
478 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
479 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
480 RepositoryInstanceInterface repoSession, List<String> serviceTypes,
\r
482 String refPropName,
\r
483 Map<String, ServiceBindingType> queriedServiceBindings,
\r
484 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
485 String whereClauseAdditions,
\r
486 String orderByClause,
\r
488 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
490 return new LazyAuthorityRefDocList(ctx, repoClient, repoSession,
\r
491 serviceTypes, refName, refPropName, queriedServiceBindings, authRefFieldsByService,
\r
492 whereClauseAdditions, orderByClause, pageSize, computeTotal);
\r
495 protected static DocumentModelList findAuthorityRefDocs(
\r
496 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
497 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
498 RepositoryInstanceInterface repoSession, List<String> serviceTypes,
\r
500 String refPropName,
\r
501 Map<String, ServiceBindingType> queriedServiceBindings,
\r
502 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
503 String whereClauseAdditions,
\r
504 String orderByClause,
\r
507 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
509 // Get the service bindings for this tenant
\r
510 TenantBindingConfigReaderImpl tReader =
\r
511 ServiceMain.getInstance().getTenantBindingConfigReader();
\r
512 // We need to get all the procedures, authorities, and objects.
\r
513 List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);
\r
514 if (servicebindings == null || servicebindings.isEmpty()) {
\r
515 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");
\r
518 // Filter the list for current user rights
\r
519 servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);
\r
521 ArrayList<String> docTypes = new ArrayList<String>();
\r
523 String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings, // REM - Side effect that docTypes array gets set. Any others?
\r
524 queriedServiceBindings, authRefFieldsByService);
\r
525 if (query == null) { // found no authRef fields - nothing to query
\r
528 // Additional qualifications, like workflow state
\r
529 if (Tools.notBlank(whereClauseAdditions)) {
\r
530 query += " AND " + whereClauseAdditions;
\r
532 // Now we have to issue the search
\r
533 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
534 DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,
\r
535 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);
\r
536 // Now we gather the info for each document into the list and return
\r
537 DocumentModelList docList = docListWrapper.getWrappedObject();
\r
540 private static final boolean READY_FOR_COMPLEX_QUERY = true;
\r
542 private static String computeWhereClauseForAuthorityRefDocs(
\r
544 String refPropName,
\r
545 ArrayList<String> docTypes,
\r
546 List<ServiceBindingType> servicebindings,
\r
547 Map<String, ServiceBindingType> queriedServiceBindings,
\r
548 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {
\r
550 boolean fFirst = true;
\r
551 List<String> authRefFieldPaths;
\r
552 for (ServiceBindingType sb : servicebindings) {
\r
553 // Gets the property names for each part, qualified with the part label (which
\r
554 // is also the table name, the way that the repository works).
\r
555 authRefFieldPaths =
\r
556 ServiceBindingUtils.getAllPartsPropertyValues(sb,
\r
557 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
558 if (authRefFieldPaths.isEmpty()) {
\r
561 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();
\r
562 for (String spec : authRefFieldPaths) {
\r
563 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
564 authRefsInfo.add(arci);
\r
567 String docType = sb.getObject().getName();
\r
568 queriedServiceBindings.put(docType, sb);
\r
569 authRefFieldsByService.put(docType, authRefsInfo);
\r
570 docTypes.add(docType);
\r
573 if (fFirst) { // found no authRef fields - nothing to query
\r
576 // We used to build a complete matches query, but that was too complex.
\r
577 // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID
\r
578 // Note that this will also match the Item itself, but that will get filtered out when
\r
579 // we compute actual matches.
\r
580 AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);
\r
582 String keywords = RefNameUtils.URN_PREFIX
\r
583 + " AND " + (authTermInfo.inAuthority.name != null
\r
584 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)
\r
585 + " AND " + (authTermInfo.name != null
\r
586 ? authTermInfo.name : authTermInfo.csid);
\r
588 String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);
\r
590 if (logger.isTraceEnabled()) {
\r
591 logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);
\r
594 return whereClauseStr;
\r
597 // TODO there are multiple copies of this that should be put somewhere common.
\r
598 protected static String getRefname(DocumentModel docModel) throws ClientException {
\r
599 String result = (String)docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
\r
600 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
\r
604 private static int processRefObjsDocListForUpdate(
\r
605 DocumentModelList docList,
\r
608 Map<String, ServiceBindingType> queriedServiceBindings,
\r
609 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
610 String newAuthorityRefName) {
\r
611 return processRefObjsDocList(docList, tenantId, refName, false, queriedServiceBindings,
\r
612 authRefFieldsByService, null, 0, 0, newAuthorityRefName);
\r
615 private static int processRefObjsDocListForList(
\r
616 DocumentModelList docList,
\r
619 Map<String, ServiceBindingType> queriedServiceBindings,
\r
620 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
621 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
622 int pageSize, int pageNum) {
\r
623 return processRefObjsDocList(docList, tenantId, refName, true, queriedServiceBindings,
\r
624 authRefFieldsByService, list, pageSize, pageNum, null);
\r
629 * Runs through the list of found docs, processing them. If list is
\r
630 * non-null, then processing means gather the info for items. If list is
\r
631 * null, and newRefName is non-null, then processing means replacing and
\r
632 * updating. If processing/updating, this must be called in the context of
\r
633 * an open session, and caller must release Session after calling this.
\r
636 private static int processRefObjsDocList(
\r
637 DocumentModelList docList,
\r
640 boolean matchBaseOnly,
\r
641 Map<String, ServiceBindingType> queriedServiceBindings,
\r
642 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
643 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
644 int pageSize, int pageNum, // Only used when constructing a list.
\r
645 String newAuthorityRefName) {
\r
646 UriTemplateRegistry registry = ServiceMain.getInstance().getUriTemplateRegistry();
\r
647 Iterator<DocumentModel> iter = docList.iterator();
\r
648 int nRefsFoundTotal = 0;
\r
649 boolean foundSelf = false;
\r
651 // When paginating results, we have to guess at the total. First guess is the number of docs returned
\r
652 // by the query. However, this returns some false positives, so may be high.
\r
653 // In addition, we can match multiple fields per doc, so this may be low. Fun, eh?
\r
654 int nDocsReturnedInQuery = (int)docList.totalSize();
\r
655 int nDocsProcessed = 0;
\r
656 int firstItemInPage = pageNum*pageSize;
\r
657 while (iter.hasNext()) {
\r
658 DocumentModel docModel = iter.next();
\r
659 AuthorityRefDocList.AuthorityRefDocItem ilistItem;
\r
661 String docType = docModel.getDocumentType().getName(); // REM - This will be a tentant qualified document type
\r
662 docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);
\r
663 ServiceBindingType sb = queriedServiceBindings.get(docType);
\r
665 throw new RuntimeException(
\r
666 "getAuthorityRefDocs: No Service Binding for docType: " + docType);
\r
669 if (list == null) { // no list - should be update refName case.
\r
670 if (newAuthorityRefName == null) {
\r
671 throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");
\r
675 firstItemInPage = 0; // Do not paginate if updating, rather than building list
\r
676 } else { // Have a list - refObjs case
\r
677 if (newAuthorityRefName != null) {
\r
678 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");
\r
680 if(firstItemInPage > 100) {
\r
681 logger.warn("Processing a large offset (size:{}, num:{}) for refObjs - will be expensive!!!",
\r
682 pageSize, pageNum);
\r
684 // Note that we have to go through check all the fields to determine the actual page start
\r
685 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
686 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
\r
688 String itemRefName = getRefname(docModel);
\r
689 ilistItem.setRefName(itemRefName);
\r
690 } catch (ClientException ce) {
\r
691 throw new RuntimeException(
\r
692 "processRefObjsDocList: Problem fetching refName from item Object: "
\r
693 + ce.getLocalizedMessage());
\r
695 ilistItem.setDocId(csid);
\r
697 UriTemplateRegistryKey key = new UriTemplateRegistryKey(tenantId, docType);
\r
698 StoredValuesUriTemplate template = registry.get(key);
\r
699 if (template != null) {
\r
700 Map<String, String> additionalValues = new HashMap<String, String>();
\r
701 if (template.getUriTemplateType() == UriTemplateFactory.RESOURCE) {
\r
702 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, csid);
\r
703 uri = template.buildUri(additionalValues);
\r
704 } else if (template.getUriTemplateType() == UriTemplateFactory.ITEM) {
\r
706 String inAuthorityCsid = (String) docModel.getPropertyValue("inAuthority"); // AuthorityItemJAXBSchema.IN_AUTHORITY
\r
707 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, inAuthorityCsid);
\r
708 additionalValues.put(UriTemplateFactory.ITEM_IDENTIFIER_VAR, csid);
\r
709 uri = template.buildUri(additionalValues);
\r
710 } catch (Exception e) {
\r
711 logger.warn("Could not extract inAuthority property from authority item record: " + e.getMessage());
\r
713 } else if (template.getUriTemplateType() == UriTemplateFactory.CONTACT) {
\r
714 // FIXME: Generating contact sub-resource URIs requires additional work,
\r
715 // as a follow-on to CSPACE-5271 - ADR 2012-08-16
\r
716 // Sets the default (empty string) value for uri, for now
\r
718 logger.warn("Unrecognized URI template type = " + template.getUriTemplateType());
\r
719 // Sets the default (empty string) value for uri
\r
721 } else { // (if template == null)
\r
722 logger.warn("Could not retrieve URI template from registry via tenant ID "
\r
723 + tenantId + " and docType " + docType);
\r
724 // Sets the default (empty string) value for uri
\r
726 ilistItem.setUri(uri);
\r
728 ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());
\r
729 ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));
\r
730 } catch (Exception e) {
\r
731 logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());
\r
733 ilistItem.setDocType(docType);
\r
734 ilistItem.setDocNumber(
\r
735 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));
\r
736 ilistItem.setDocName(
\r
737 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));
\r
739 // Now, we have to loop over the authRefFieldsByService to figure
\r
740 // out which field(s) matched this.
\r
741 List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);
\r
742 if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {
\r
743 throw new RuntimeException(
\r
744 "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");
\r
746 //String authRefAncestorField = "";
\r
747 //String authRefDescendantField = "";
\r
748 //String sourceField = "";
\r
750 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
\r
752 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, matchBaseOnly, foundProps); // REM - side effect that foundProps is set
\r
753 if(!foundProps.isEmpty()) {
\r
754 int nRefsFoundInDoc = 0;
\r
755 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {
\r
756 if (ilistItem != null) {
\r
757 // So this is a true positive, and not a false one. We have to consider pagination now.
\r
758 if(nRefsFoundTotal >= firstItemInPage) { // skipped enough already
\r
759 if (nRefsFoundInDoc == 0) { // First one?
\r
760 ilistItem.setSourceField(ari.getQualifiedDisplayName());
\r
761 } else { // duplicates from one object
\r
762 ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());
\r
764 list.add(ilistItem);
\r
765 nRefsFoundInDoc++; // Only increment if processed, or clone logic above will fail
\r
767 } else { // update refName case
\r
768 Property propToUpdate = ari.getProperty();
\r
769 propToUpdate.setValue(newAuthorityRefName);
\r
771 nRefsFoundTotal++; // Whether we processed or not, we found - essential to pagination logic
\r
773 } else if(ilistItem != null) {
\r
774 String docRefName = ilistItem.getRefName();
\r
776 (docRefName!=null && docRefName.startsWith(refName))
\r
777 :refName.equals(docRefName)) {
\r
778 // We found the self for an item
\r
780 logger.debug("getAuthorityRefDocs: Result: "
\r
781 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
782 + "] appears to be self for: ["
\r
785 logger.debug("getAuthorityRefDocs: Result: "
\r
786 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
787 + "] does not reference ["
\r
791 } catch (ClientException ce) {
\r
792 throw new RuntimeException(
\r
793 "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());
\r
796 // Done processing that doc. Are we done with the whole page?
\r
797 // Note pageSize <=0 means do them all
\r
798 if((pageSize > 0) && ((nRefsFoundTotal-firstItemInPage)>=pageSize)) {
\r
799 // Quitting early, so we need to estimate the total. Assume one per doc
\r
800 // for the rest of the docs we matched in the query
\r
801 int unprocessedDocs = nDocsReturnedInQuery - nDocsProcessed;
\r
802 if(unprocessedDocs>0) {
\r
803 // We generally match ourselves in the keyword search. If we already saw ourselves
\r
804 // then do not try to correct for this. Otherwise, decrement the total.
\r
805 // Yes, this is fairly goofy, but the whole estimation mechanism is goofy.
\r
808 nRefsFoundTotal += unprocessedDocs;
\r
812 } // close while(iterator)
\r
813 return nRefsFoundTotal;
\r
816 private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(
\r
817 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {
\r
818 AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
819 newlistItem.setDocId(ilistItem.getDocId());
\r
820 newlistItem.setDocName(ilistItem.getDocName());
\r
821 newlistItem.setDocNumber(ilistItem.getDocNumber());
\r
822 newlistItem.setDocType(ilistItem.getDocType());
\r
823 newlistItem.setUri(ilistItem.getUri());
\r
824 newlistItem.setSourceField(sourceField);
\r
825 return newlistItem;
\r
828 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
829 DocumentModel docModel,
\r
830 List<AuthRefConfigInfo> authRefFieldInfo,
\r
831 String refNameToMatch,
\r
832 List<AuthRefInfo> foundProps) {
\r
833 return findAuthRefPropertiesInDoc(docModel, authRefFieldInfo,
\r
834 refNameToMatch, false, foundProps);
\r
837 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
838 DocumentModel docModel,
\r
839 List<AuthRefConfigInfo> authRefFieldInfo,
\r
840 String refNameToMatch,
\r
841 boolean matchBaseOnly,
\r
842 List<AuthRefInfo> foundProps) {
\r
843 // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)
\r
844 // and the values are elPaths to the field, where intervening group structures in
\r
845 // lists of complex structures are replaced with "*". Thus, valid paths include
\r
846 // the following (note that the ServiceBindingUtils prepend schema names to configured values):
\r
847 // "schemaname:fieldname"
\r
848 // "schemaname:scalarlistname"
\r
849 // "schemaname:complexfieldname/fieldname"
\r
850 // "schemaname:complexlistname/*/fieldname"
\r
851 // "schemaname:complexlistname/*/scalarlistname"
\r
852 // "schemaname:complexlistname/*/complexfieldname/fieldname"
\r
853 // "schemaname:complexlistname/*/complexlistname/*/fieldname"
\r
855 for (AuthRefConfigInfo arci : authRefFieldInfo) {
\r
857 // Get first property and work down as needed.
\r
858 Property prop = docModel.getProperty(arci.pathEls[0]);
\r
859 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch, matchBaseOnly);
\r
860 } catch (Exception e) {
\r
861 logger.error("Problem fetching property: " + arci.pathEls[0]);
\r
867 private static List<AuthRefInfo> findAuthRefPropertiesInProperty(
\r
868 List<AuthRefInfo> foundProps,
\r
870 AuthRefConfigInfo arci,
\r
871 int pathStartIndex, // Supports recursion and we work down the path
\r
872 String refNameToMatch,
\r
873 boolean matchBaseOnly ) {
\r
874 if (pathStartIndex >= arci.pathEls.length) {
\r
875 throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "
\r
876 + arci.pathEls.toString());
\r
878 AuthRefInfo ari = null;
\r
879 if (prop == null) {
\r
883 if (prop instanceof StringProperty) { // scalar string
\r
884 addARIifMatches(refNameToMatch, matchBaseOnly, arci, prop, foundProps);
\r
885 } else if (prop instanceof List) {
\r
886 List<Property> propList = (List<Property>) prop;
\r
887 // run through list. Must either be list of Strings, or Complex
\r
888 for (Property listItemProp : propList) {
\r
889 if (listItemProp instanceof StringProperty) {
\r
890 if (arci.pathEls.length - pathStartIndex != 1) {
\r
891 logger.error("Configuration for authRefs does not match schema structure: "
\r
892 + arci.pathEls.toString());
\r
895 addARIifMatches(refNameToMatch, matchBaseOnly, arci, listItemProp, foundProps);
\r
897 } else if (listItemProp.isComplex()) {
\r
898 // Just recurse to handle this. Note that since this is a list of complex,
\r
899 // which should look like listName/*/... we add 2 to the path start index
\r
900 findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,
\r
901 pathStartIndex + 2, refNameToMatch, matchBaseOnly);
\r
903 logger.error("Configuration for authRefs does not match schema structure: "
\r
904 + arci.pathEls.toString());
\r
908 } else if (prop.isComplex()) {
\r
909 String localPropName = arci.pathEls[pathStartIndex];
\r
911 Property localProp = prop.get(localPropName);
\r
912 // Now just recurse, pushing down the path 1 step
\r
913 findAuthRefPropertiesInProperty(foundProps, localProp, arci,
\r
914 pathStartIndex, refNameToMatch, matchBaseOnly);
\r
915 } catch (PropertyNotFoundException pnfe) {
\r
916 logger.error("Could not find property: [" + localPropName + "] in path: "
\r
917 + arci.getFullPath());
\r
918 // Fall through - ari will be null and we will continue...
\r
921 logger.error("Configuration for authRefs does not match schema structure: "
\r
922 + arci.pathEls.toString());
\r
926 foundProps.add(ari); //FIXME: REM - This is dead code. 'ari' is never touched after being initalized to null. Why?
\r
932 private static void addARIifMatches(
\r
933 String refNameToMatch,
\r
934 boolean matchBaseOnly,
\r
935 AuthRefConfigInfo arci,
\r
937 List<AuthRefInfo> foundProps) {
\r
938 // Need to either match a passed refName
\r
939 // OR have no refName to match but be non-empty
\r
941 String value = (String) prop.getValue();
\r
942 if (((refNameToMatch != null) &&
\r
944 (value!=null && value.startsWith(refNameToMatch))
\r
945 :refNameToMatch.equals(value)))
\r
946 || ((refNameToMatch == null) && Tools.notBlank(value))) {
\r
948 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");
\r
949 AuthRefInfo ari = new AuthRefInfo(arci, prop);
\r
950 foundProps.add(ari);
\r
952 } catch (PropertyException pe) {
\r
953 logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());
\r
958 * Identifies whether the refName was found in the supplied field. If passed
\r
959 * a new RefName, will set that into fields in which the old one was found.
\r
961 * Only works for: * Scalar fields * Repeatable scalar fields (aka
\r
962 * multi-valued fields)
\r
964 * Does not work for: * Structured fields (complexTypes) * Repeatable
\r
965 * structured fields (repeatable complexTypes) private static int
\r
966 * refNameFoundInField(String oldRefName, Property fieldValue, String
\r
967 * newRefName) { int nFound = 0; if (fieldValue instanceof List) {
\r
968 * List<Property> fieldValueList = (List) fieldValue; for (Property
\r
969 * listItemValue : fieldValueList) { try { if ((listItemValue instanceof
\r
970 * StringProperty) &&
\r
971 * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {
\r
972 * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else
\r
973 * { // We cannot quit after the first, if we are replacing values. // If we
\r
974 * are just looking (not replacing), finding one is enough. break; } } }
\r
975 * catch( PropertyException pe ) {} } } else { try { if ((fieldValue
\r
976 * instanceof StringProperty) &&
\r
977 * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;
\r
978 * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(
\r
979 * PropertyException pe ) {} } return nFound; }
\r