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