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
42 import org.collectionspace.services.client.CollectionSpaceClient;
\r
43 import org.collectionspace.services.client.IRelationsManager;
\r
44 import org.collectionspace.services.client.PoxPayloadIn;
\r
45 import org.collectionspace.services.client.PoxPayloadOut;
\r
46 import org.collectionspace.services.common.ServiceMain;
\r
47 import org.collectionspace.services.common.StoredValuesUriTemplate;
\r
48 import org.collectionspace.services.common.UriTemplateFactory;
\r
49 import org.collectionspace.services.common.UriTemplateRegistry;
\r
50 import org.collectionspace.services.common.UriTemplateRegistryKey;
\r
51 import org.collectionspace.services.common.context.ServiceContext;
\r
52 import org.collectionspace.services.common.context.AbstractServiceContextImpl;
\r
53 import org.collectionspace.services.common.api.RefNameUtils;
\r
54 import org.collectionspace.services.common.api.Tools;
\r
55 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
\r
56 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
\r
57 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
\r
58 import org.collectionspace.services.common.context.ServiceBindingUtils;
\r
59 import org.collectionspace.services.common.document.DocumentException;
\r
60 import org.collectionspace.services.common.document.DocumentFilter;
\r
61 import org.collectionspace.services.common.document.DocumentNotFoundException;
\r
62 import org.collectionspace.services.common.document.DocumentUtils;
\r
63 import org.collectionspace.services.common.document.DocumentWrapper;
\r
64 import org.collectionspace.services.common.query.QueryManager;
\r
65 import org.collectionspace.services.common.relation.RelationUtils;
\r
66 import org.collectionspace.services.common.repository.RepositoryClient;
\r
67 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
\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 RepositoryInstance 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 RepositoryInstance 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
269 DocumentModelList docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
270 serviceTypes, refName, refPropName, queriedServiceBindings, authRefFieldsByService,
\r
271 filter.getWhereClause(), null, 0 /* pageSize */, 0 /* pageNum */, computeTotal);
\r
273 if (docList == null) { // found no authRef fields - nothing to process
\r
274 return wrapperList;
\r
277 // set the fieldsReturned list. Even though this is a fixed schema, app layer treats
\r
278 // this like other abstract common lists
\r
280 * <xs:element name="docType" type="xs:string" minOccurs="1" />
\r
281 * <xs:element name="docId" type="xs:string" minOccurs="1" />
\r
282 * <xs:element name="docNumber" type="xs:string" minOccurs="0" />
\r
283 * <xs:element name="docName" type="xs:string" minOccurs="0" />
\r
284 * <xs:element name="sourceField" type="xs:string" minOccurs="1" />
\r
285 * <xs:element name="uri" type="xs:anyURI" minOccurs="1" />
\r
286 * <xs:element name="refName" type="xs:String" minOccurs="1" />
\r
287 * <xs:element name="updatedAt" type="xs:string" minOccurs="1" />
\r
288 * <xs:element name="workflowState" type="xs:string" minOccurs="1"
\r
291 String fieldList = "docType|docId|docNumber|docName|sourceField|uri|refName|updatedAt|workflowState";
\r
292 commonList.setFieldsReturned(fieldList);
\r
294 // As a side-effect, the method called below modifies the value of
\r
295 // the 'list' variable, which holds the list of references to
\r
296 // an authority item.
\r
298 // There can be more than one reference to a particular authority
\r
299 // item within any individual document scanned, so the number of
\r
300 // authority references may potentially exceed the total number
\r
301 // of documents scanned.
\r
303 // Strip off displayName and only match the base, so we get references to all
\r
304 // the NPTs as well as the PT.
\r
305 String strippedRefName = RefNameUtils.stripAuthorityTermDisplayName(refName);
\r
307 // *** Need to pass in pagination info here.
\r
308 int nRefsFound = processRefObjsDocListForList(docList, ctx.getTenantId(), strippedRefName,
\r
309 queriedServiceBindings, authRefFieldsByService, // the actual list size needs to be updated to the size of "list"
\r
310 list, pageSize, pageNum);
\r
312 commonList.setPageSize(pageSize);
\r
314 // Values returned in the pagination block above the list items
\r
315 // need to reflect the number of references to authority items
\r
316 // returned, rather than the number of documents originally scanned
\r
317 // to find such references.
\r
318 // This will be an estimate only...
\r
319 commonList.setPageNum(pageNum);
\r
320 commonList.setTotalItems(nRefsFound); // Accurate if total was scanned, otherwise, just an estimate
\r
321 commonList.setItemsInPage(list.size());
\r
323 /* Pagination is now handled in the processing step
\r
324 // Slice the list to return only the specified page of items
\r
325 // in the list results.
\r
327 // FIXME: There may well be a pattern-based way to do this
\r
328 // in our framework, and if we can eliminate much of the
\r
329 // non-DRY code below, that would be desirable.
\r
331 int startIndex = 0;
\r
334 // Return all results if pageSize is 0.
\r
335 if (pageSize == 0) {
\r
337 endIndex = list.size();
\r
339 startIndex = pageNum * pageSize;
\r
342 // Return an empty list when the start of the requested page is
\r
343 // beyond the last item in the list.
\r
344 if (startIndex > list.size()) {
\r
345 wrapperList.getAuthorityRefDocItem().clear();
\r
346 commonList.setItemsInPage(wrapperList.getAuthorityRefDocItem().size());
\r
347 return wrapperList;
\r
350 // Otherwise, return a list of items from the start of the specified
\r
351 // page through the last item on that page, or otherwise through the
\r
352 // last item in the entire list, if that occurs earlier than the end
\r
353 // of the specified page.
\r
354 if (endIndex == 0) {
\r
355 int pageEndIndex = ((startIndex + pageSize));
\r
356 endIndex = (pageEndIndex > list.size()) ? list.size() : pageEndIndex;
\r
359 // Slice the list to return only the specified page of results.
\r
360 // Note: the second argument to List.subList(), endIndex, is
\r
361 // exclusive of the item at its index position, reflecting the
\r
362 // zero-index nature of the list.
\r
363 List<AuthorityRefDocList.AuthorityRefDocItem> currentPageList =
\r
364 new ArrayList<AuthorityRefDocList.AuthorityRefDocItem>(list.subList(startIndex, endIndex));
\r
365 wrapperList.getAuthorityRefDocItem().clear();
\r
366 wrapperList.getAuthorityRefDocItem().addAll(currentPageList);
\r
367 commonList.setItemsInPage(currentPageList.size());
\r
370 if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {
\r
371 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.
\r
373 } catch (Exception e) {
\r
374 logger.error("Could not retrieve a list of documents referring to the specified authority item", e);
\r
375 wrapperList = null;
\r
378 return wrapperList;
\r
381 private static ArrayList<String> getRefNameServiceTypes() {
\r
382 if (refNameServiceTypes == null) {
\r
383 refNameServiceTypes = new ArrayList<String>();
\r
384 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);
\r
385 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);
\r
386 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);
\r
388 return refNameServiceTypes;
\r
391 // Seems like a good value - no real data to set this well.
\r
392 // Note: can set this value lower during debugging; e.g. to 3 - ADR 2012-07-10
\r
393 private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;
\r
395 public static int updateAuthorityRefDocs(
\r
396 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
397 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
398 RepositoryInstance repoSession,
\r
401 String refPropName) throws Exception {
\r
402 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
403 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
405 int docsScanned = 0;
\r
406 int nRefsFound = 0;
\r
407 int currentPage = 0;
\r
408 int docsInCurrentPage = 0;
\r
409 final String WHERE_CLAUSE_ADDITIONS_VALUE = null;
\r
410 final String ORDER_BY_VALUE = CollectionSpaceClient.CORE_CREATED_AT; // "collectionspace_core:createdAt";
\r
412 if (repoClient instanceof RepositoryJavaClientImpl == false) {
\r
413 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");
\r
416 try { // REM - How can we deal with transaction and timeout issues here?
\r
417 final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;
\r
418 DocumentModelList docList;
\r
419 boolean morePages = true;
\r
420 while (morePages) {
\r
422 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
423 getRefNameServiceTypes(), oldRefName, refPropName,
\r
424 queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);
\r
426 if (docList == null) {
\r
427 logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");
\r
430 docsInCurrentPage = docList.size();
\r
431 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);
\r
432 if (docsInCurrentPage == 0) {
\r
433 logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");
\r
436 if (docsInCurrentPage < pageSize) {
\r
437 logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");
\r
441 // Only match complete refNames - unless and until we decide how to resolve changes
\r
442 // to NPTs we will defer that and only change PTs or refNames as passed in.
\r
443 int nRefsFoundThisPage = processRefObjsDocListForUpdate(docList, ctx.getTenantId(), oldRefName,
\r
444 queriedServiceBindings, authRefFieldsByService, // Perform the refName updates on the list of document models
\r
446 if (nRefsFoundThisPage > 0) {
\r
447 ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true); // Flush the document model list out to Nuxeo storage
\r
448 nRefsFound += nRefsFoundThisPage;
\r
451 // FIXME: Per REM, set a limit of num objects - something like
\r
452 // 1000K objects - and also add a log Warning after some threshold
\r
453 docsScanned += docsInCurrentPage;
\r
459 } catch (Exception e) {
\r
460 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());
\r
461 logger.debug(Tools.errorToString(e, true));
\r
464 logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");
\r
468 private static DocumentModelList findAuthorityRefDocs(
\r
469 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
470 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
471 RepositoryInstance repoSession, List<String> serviceTypes,
\r
473 String refPropName,
\r
474 Map<String, ServiceBindingType> queriedServiceBindings,
\r
475 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
476 String whereClauseAdditions,
\r
477 String orderByClause,
\r
480 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
482 // Get the service bindings for this tenant
\r
483 TenantBindingConfigReaderImpl tReader =
\r
484 ServiceMain.getInstance().getTenantBindingConfigReader();
\r
485 // We need to get all the procedures, authorities, and objects.
\r
486 List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);
\r
487 if (servicebindings == null || servicebindings.isEmpty()) {
\r
488 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");
\r
491 // Filter the list for current user rights
\r
492 servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);
\r
494 ArrayList<String> docTypes = new ArrayList<String>();
\r
496 String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings, // REM - Side effect that docTypes array gets set. Any others?
\r
497 queriedServiceBindings, authRefFieldsByService);
\r
498 if (query == null) { // found no authRef fields - nothing to query
\r
501 // Additional qualifications, like workflow state
\r
502 if (Tools.notBlank(whereClauseAdditions)) {
\r
503 query += " AND " + whereClauseAdditions;
\r
505 // Now we have to issue the search
\r
506 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
507 DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,
\r
508 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);
\r
509 // Now we gather the info for each document into the list and return
\r
510 DocumentModelList docList = docListWrapper.getWrappedObject();
\r
513 private static final boolean READY_FOR_COMPLEX_QUERY = true;
\r
515 private static String computeWhereClauseForAuthorityRefDocs(
\r
517 String refPropName,
\r
518 ArrayList<String> docTypes,
\r
519 List<ServiceBindingType> servicebindings,
\r
520 Map<String, ServiceBindingType> queriedServiceBindings,
\r
521 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {
\r
523 boolean fFirst = true;
\r
524 List<String> authRefFieldPaths;
\r
525 for (ServiceBindingType sb : servicebindings) {
\r
526 // Gets the property names for each part, qualified with the part label (which
\r
527 // is also the table name, the way that the repository works).
\r
528 authRefFieldPaths =
\r
529 ServiceBindingUtils.getAllPartsPropertyValues(sb,
\r
530 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
531 if (authRefFieldPaths.isEmpty()) {
\r
534 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();
\r
535 for (String spec : authRefFieldPaths) {
\r
536 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
537 authRefsInfo.add(arci);
\r
540 String docType = sb.getObject().getName();
\r
541 queriedServiceBindings.put(docType, sb);
\r
542 authRefFieldsByService.put(docType, authRefsInfo);
\r
543 docTypes.add(docType);
\r
546 if (fFirst) { // found no authRef fields - nothing to query
\r
549 // We used to build a complete matches query, but that was too complex.
\r
550 // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID
\r
551 // Note that this will also match the Item itself, but that will get filtered out when
\r
552 // we compute actual matches.
\r
553 AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);
\r
555 String keywords = RefNameUtils.URN_PREFIX
\r
556 + " AND " + (authTermInfo.inAuthority.name != null
\r
557 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)
\r
558 + " AND " + (authTermInfo.name != null
\r
559 ? authTermInfo.name : authTermInfo.csid);
\r
561 String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);
\r
563 if (logger.isTraceEnabled()) {
\r
564 logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);
\r
567 return whereClauseStr;
\r
570 // TODO there are multiple copies of this that should be put somewhere common.
\r
571 protected static String getRefname(DocumentModel docModel) throws ClientException {
\r
572 String result = (String)docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
\r
573 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
\r
577 private static int processRefObjsDocListForUpdate(
\r
578 DocumentModelList docList,
\r
581 Map<String, ServiceBindingType> queriedServiceBindings,
\r
582 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
583 String newAuthorityRefName) {
\r
584 return processRefObjsDocList(docList, tenantId, refName, false, queriedServiceBindings,
\r
585 authRefFieldsByService, null, 0, 0, newAuthorityRefName);
\r
588 private static int processRefObjsDocListForList(
\r
589 DocumentModelList docList,
\r
592 Map<String, ServiceBindingType> queriedServiceBindings,
\r
593 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
594 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
595 int pageSize, int pageNum) {
\r
596 return processRefObjsDocList(docList, tenantId, refName, true, queriedServiceBindings,
\r
597 authRefFieldsByService, list, pageSize, pageNum, null);
\r
602 * Runs through the list of found docs, processing them. If list is
\r
603 * non-null, then processing means gather the info for items. If list is
\r
604 * null, and newRefName is non-null, then processing means replacing and
\r
605 * updating. If processing/updating, this must be called in the context of
\r
606 * an open session, and caller must release Session after calling this.
\r
609 private static int processRefObjsDocList(
\r
610 DocumentModelList docList,
\r
613 boolean matchBaseOnly,
\r
614 Map<String, ServiceBindingType> queriedServiceBindings,
\r
615 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
616 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
617 int pageSize, int pageNum, // Only used when constructing a list.
\r
618 String newAuthorityRefName) {
\r
619 UriTemplateRegistry registry = ServiceMain.getInstance().getUriTemplateRegistry();
\r
620 Iterator<DocumentModel> iter = docList.iterator();
\r
621 int nRefsFoundTotal = 0;
\r
622 boolean foundSelf = false;
\r
624 // When paginating results, we have to guess at the total. First guess is the number of docs returned
\r
625 // by the query. However, this returns some false positives, so may be high.
\r
626 // In addition, we can match multiple fields per doc, so this may be low. Fun, eh?
\r
627 int nDocsReturnedInQuery = (int)docList.totalSize();
\r
628 int nDocsProcessed = 0;
\r
629 int firstItemInPage = pageNum*pageSize;
\r
630 while (iter.hasNext()) {
\r
631 DocumentModel docModel = iter.next();
\r
632 AuthorityRefDocList.AuthorityRefDocItem ilistItem;
\r
634 String docType = docModel.getDocumentType().getName(); // REM - This will be a tentant qualified document type
\r
635 docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);
\r
636 ServiceBindingType sb = queriedServiceBindings.get(docType);
\r
638 throw new RuntimeException(
\r
639 "getAuthorityRefDocs: No Service Binding for docType: " + docType);
\r
642 if (list == null) { // no list - should be update refName case.
\r
643 if (newAuthorityRefName == null) {
\r
644 throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");
\r
648 firstItemInPage = 0; // Do not paginate if updating, rather than building list
\r
649 } else { // Have a list - refObjs case
\r
650 if (newAuthorityRefName != null) {
\r
651 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");
\r
653 if(firstItemInPage > 100) {
\r
654 logger.warn("Processing a large offset (size:{}, num:{}) for refObjs - will be expensive!!!",
\r
655 pageSize, pageNum);
\r
657 // Note that we have to go through check all the fields to determine the actual page start
\r
658 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
659 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
\r
661 String itemRefName = getRefname(docModel);
\r
662 ilistItem.setRefName(itemRefName);
\r
663 } catch (ClientException ce) {
\r
664 throw new RuntimeException(
\r
665 "processRefObjsDocList: Problem fetching refName from item Object: "
\r
666 + ce.getLocalizedMessage());
\r
668 ilistItem.setDocId(csid);
\r
670 UriTemplateRegistryKey key = new UriTemplateRegistryKey(tenantId, docType);
\r
671 StoredValuesUriTemplate template = registry.get(key);
\r
672 if (template != null) {
\r
673 Map<String, String> additionalValues = new HashMap<String, String>();
\r
674 if (template.getUriTemplateType() == UriTemplateFactory.RESOURCE) {
\r
675 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, csid);
\r
676 uri = template.buildUri(additionalValues);
\r
677 } else if (template.getUriTemplateType() == UriTemplateFactory.ITEM) {
\r
679 String inAuthorityCsid = (String) docModel.getPropertyValue("inAuthority"); // AuthorityItemJAXBSchema.IN_AUTHORITY
\r
680 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, inAuthorityCsid);
\r
681 additionalValues.put(UriTemplateFactory.ITEM_IDENTIFIER_VAR, csid);
\r
682 uri = template.buildUri(additionalValues);
\r
683 } catch (Exception e) {
\r
684 logger.warn("Could not extract inAuthority property from authority item record: " + e.getMessage());
\r
686 } else if (template.getUriTemplateType() == UriTemplateFactory.CONTACT) {
\r
687 // FIXME: Generating contact sub-resource URIs requires additional work,
\r
688 // as a follow-on to CSPACE-5271 - ADR 2012-08-16
\r
689 // Sets the default (empty string) value for uri, for now
\r
691 logger.warn("Unrecognized URI template type = " + template.getUriTemplateType());
\r
692 // Sets the default (empty string) value for uri
\r
694 } else { // (if template == null)
\r
695 logger.warn("Could not retrieve URI template from registry via tenant ID "
\r
696 + tenantId + " and docType " + docType);
\r
697 // Sets the default (empty string) value for uri
\r
699 ilistItem.setUri(uri);
\r
701 ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());
\r
702 ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));
\r
703 } catch (Exception e) {
\r
704 logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());
\r
706 ilistItem.setDocType(docType);
\r
707 ilistItem.setDocNumber(
\r
708 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));
\r
709 ilistItem.setDocName(
\r
710 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));
\r
712 // Now, we have to loop over the authRefFieldsByService to figure
\r
713 // out which field(s) matched this.
\r
714 List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);
\r
715 if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {
\r
716 throw new RuntimeException(
\r
717 "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");
\r
719 //String authRefAncestorField = "";
\r
720 //String authRefDescendantField = "";
\r
721 //String sourceField = "";
\r
723 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
\r
725 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, matchBaseOnly, foundProps); // REM - side effect that foundProps is set
\r
726 if(!foundProps.isEmpty()) {
\r
727 int nRefsFoundInDoc = 0;
\r
728 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {
\r
729 if (ilistItem != null) {
\r
730 // So this is a true positive, and not a false one. We have to consider pagination now.
\r
731 if(nRefsFoundTotal >= firstItemInPage) { // skipped enough already
\r
732 if (nRefsFoundInDoc == 0) { // First one?
\r
733 ilistItem.setSourceField(ari.getQualifiedDisplayName());
\r
734 } else { // duplicates from one object
\r
735 ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());
\r
737 list.add(ilistItem);
\r
738 nRefsFoundInDoc++; // Only increment if processed, or clone logic above will fail
\r
740 } else { // update refName case
\r
741 Property propToUpdate = ari.getProperty();
\r
742 propToUpdate.setValue(newAuthorityRefName);
\r
744 nRefsFoundTotal++; // Whether we processed or not, we found - essential to pagination logic
\r
746 } else if(ilistItem != null) {
\r
747 String docRefName = ilistItem.getRefName();
\r
749 (docRefName!=null && docRefName.startsWith(refName))
\r
750 :refName.equals(docRefName)) {
\r
751 // We found the self for an item
\r
753 logger.debug("getAuthorityRefDocs: Result: "
\r
754 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
755 + "] appears to be self for: ["
\r
758 logger.debug("getAuthorityRefDocs: Result: "
\r
759 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
760 + "] does not reference ["
\r
764 } catch (ClientException ce) {
\r
765 throw new RuntimeException(
\r
766 "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());
\r
769 // Done processing that doc. Are we done with the whole page?
\r
770 // Note pageSize <=0 means do them all
\r
771 if((pageSize > 0) && ((nRefsFoundTotal-firstItemInPage)>=pageSize)) {
\r
772 // Quitting early, so we need to estimate the total. Assume one per doc
\r
773 // for the rest of the docs we matched in the query
\r
774 int unprocessedDocs = nDocsReturnedInQuery - nDocsProcessed;
\r
775 if(unprocessedDocs>0) {
\r
776 // We generally match ourselves in the keyword search. If we already saw ourselves
\r
777 // then do not try to correct for this. Otherwise, decrement the total.
\r
778 // Yes, this is fairly goofy, but the whole estimation mechanism is goofy.
\r
781 nRefsFoundTotal += unprocessedDocs;
\r
785 } // close while(iterator)
\r
786 return nRefsFoundTotal;
\r
789 private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(
\r
790 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {
\r
791 AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
792 newlistItem.setDocId(ilistItem.getDocId());
\r
793 newlistItem.setDocName(ilistItem.getDocName());
\r
794 newlistItem.setDocNumber(ilistItem.getDocNumber());
\r
795 newlistItem.setDocType(ilistItem.getDocType());
\r
796 newlistItem.setUri(ilistItem.getUri());
\r
797 newlistItem.setSourceField(sourceField);
\r
798 return newlistItem;
\r
801 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
802 DocumentModel docModel,
\r
803 List<AuthRefConfigInfo> authRefFieldInfo,
\r
804 String refNameToMatch,
\r
805 List<AuthRefInfo> foundProps) {
\r
806 return findAuthRefPropertiesInDoc(docModel, authRefFieldInfo,
\r
807 refNameToMatch, false, foundProps);
\r
810 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
811 DocumentModel docModel,
\r
812 List<AuthRefConfigInfo> authRefFieldInfo,
\r
813 String refNameToMatch,
\r
814 boolean matchBaseOnly,
\r
815 List<AuthRefInfo> foundProps) {
\r
816 // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)
\r
817 // and the values are elPaths to the field, where intervening group structures in
\r
818 // lists of complex structures are replaced with "*". Thus, valid paths include
\r
819 // the following (note that the ServiceBindingUtils prepend schema names to configured values):
\r
820 // "schemaname:fieldname"
\r
821 // "schemaname:scalarlistname"
\r
822 // "schemaname:complexfieldname/fieldname"
\r
823 // "schemaname:complexlistname/*/fieldname"
\r
824 // "schemaname:complexlistname/*/scalarlistname"
\r
825 // "schemaname:complexlistname/*/complexfieldname/fieldname"
\r
826 // "schemaname:complexlistname/*/complexlistname/*/fieldname"
\r
828 for (AuthRefConfigInfo arci : authRefFieldInfo) {
\r
830 // Get first property and work down as needed.
\r
831 Property prop = docModel.getProperty(arci.pathEls[0]);
\r
832 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch, matchBaseOnly);
\r
833 } catch (Exception e) {
\r
834 logger.error("Problem fetching property: " + arci.pathEls[0]);
\r
840 private static List<AuthRefInfo> findAuthRefPropertiesInProperty(
\r
841 List<AuthRefInfo> foundProps,
\r
843 AuthRefConfigInfo arci,
\r
844 int pathStartIndex, // Supports recursion and we work down the path
\r
845 String refNameToMatch,
\r
846 boolean matchBaseOnly ) {
\r
847 if (pathStartIndex >= arci.pathEls.length) {
\r
848 throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "
\r
849 + arci.pathEls.toString());
\r
851 AuthRefInfo ari = null;
\r
852 if (prop == null) {
\r
856 if (prop instanceof StringProperty) { // scalar string
\r
857 addARIifMatches(refNameToMatch, matchBaseOnly, arci, prop, foundProps);
\r
858 } else if (prop instanceof List) {
\r
859 List<Property> propList = (List<Property>) prop;
\r
860 // run through list. Must either be list of Strings, or Complex
\r
861 for (Property listItemProp : propList) {
\r
862 if (listItemProp instanceof StringProperty) {
\r
863 if (arci.pathEls.length - pathStartIndex != 1) {
\r
864 logger.error("Configuration for authRefs does not match schema structure: "
\r
865 + arci.pathEls.toString());
\r
868 addARIifMatches(refNameToMatch, matchBaseOnly, arci, listItemProp, foundProps);
\r
870 } else if (listItemProp.isComplex()) {
\r
871 // Just recurse to handle this. Note that since this is a list of complex,
\r
872 // which should look like listName/*/... we add 2 to the path start index
\r
873 findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,
\r
874 pathStartIndex + 2, refNameToMatch, matchBaseOnly);
\r
876 logger.error("Configuration for authRefs does not match schema structure: "
\r
877 + arci.pathEls.toString());
\r
881 } else if (prop.isComplex()) {
\r
882 String localPropName = arci.pathEls[pathStartIndex];
\r
884 Property localProp = prop.get(localPropName);
\r
885 // Now just recurse, pushing down the path 1 step
\r
886 findAuthRefPropertiesInProperty(foundProps, localProp, arci,
\r
887 pathStartIndex, refNameToMatch, matchBaseOnly);
\r
888 } catch (PropertyNotFoundException pnfe) {
\r
889 logger.error("Could not find property: [" + localPropName + "] in path: "
\r
890 + arci.getFullPath());
\r
891 // Fall through - ari will be null and we will continue...
\r
894 logger.error("Configuration for authRefs does not match schema structure: "
\r
895 + arci.pathEls.toString());
\r
899 foundProps.add(ari); //FIXME: REM - This is dead code. 'ari' is never touched after being initalized to null. Why?
\r
905 private static void addARIifMatches(
\r
906 String refNameToMatch,
\r
907 boolean matchBaseOnly,
\r
908 AuthRefConfigInfo arci,
\r
910 List<AuthRefInfo> foundProps) {
\r
911 // Need to either match a passed refName
\r
912 // OR have no refName to match but be non-empty
\r
914 String value = (String) prop.getValue();
\r
915 if (((refNameToMatch != null) &&
\r
917 (value!=null && value.startsWith(refNameToMatch))
\r
918 :refNameToMatch.equals(value)))
\r
919 || ((refNameToMatch == null) && Tools.notBlank(value))) {
\r
921 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");
\r
922 AuthRefInfo ari = new AuthRefInfo(arci, prop);
\r
923 foundProps.add(ari);
\r
925 } catch (PropertyException pe) {
\r
926 logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());
\r
931 * Identifies whether the refName was found in the supplied field. If passed
\r
932 * a new RefName, will set that into fields in which the old one was found.
\r
934 * Only works for: * Scalar fields * Repeatable scalar fields (aka
\r
935 * multi-valued fields)
\r
937 * Does not work for: * Structured fields (complexTypes) * Repeatable
\r
938 * structured fields (repeatable complexTypes) private static int
\r
939 * refNameFoundInField(String oldRefName, Property fieldValue, String
\r
940 * newRefName) { int nFound = 0; if (fieldValue instanceof List) {
\r
941 * List<Property> fieldValueList = (List) fieldValue; for (Property
\r
942 * listItemValue : fieldValueList) { try { if ((listItemValue instanceof
\r
943 * StringProperty) &&
\r
944 * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {
\r
945 * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else
\r
946 * { // We cannot quit after the first, if we are replacing values. // If we
\r
947 * are just looking (not replacing), finding one is enough. break; } } }
\r
948 * catch( PropertyException pe ) {} } } else { try { if ((fieldValue
\r
949 * instanceof StringProperty) &&
\r
950 * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;
\r
951 * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(
\r
952 * PropertyException pe ) {} } return nFound; }
\r