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
306 int nRefsFound = processRefObjsDocList(docList, ctx.getTenantId(), strippedRefName, true, queriedServiceBindings, authRefFieldsByService, // the actual list size needs to be updated to the size of "list"
\r
309 commonList.setPageSize(pageSize);
\r
311 // Values returned in the pagination block above the list items
\r
312 // need to reflect the number of references to authority items
\r
313 // returned, rather than the number of documents originally scanned
\r
314 // to find such references.
\r
315 commonList.setPageNum(pageNum);
\r
316 commonList.setTotalItems(list.size());
\r
318 // Slice the list to return only the specified page of items
\r
319 // in the list results.
\r
321 // FIXME: There may well be a pattern-based way to do this
\r
322 // in our framework, and if we can eliminate much of the
\r
323 // non-DRY code below, that would be desirable.
\r
325 int startIndex = 0;
\r
328 // Return all results if pageSize is 0.
\r
329 if (pageSize == 0) {
\r
331 endIndex = list.size();
\r
333 startIndex = pageNum * pageSize;
\r
336 // Return an empty list when the start of the requested page is
\r
337 // beyond the last item in the list.
\r
338 if (startIndex > list.size()) {
\r
339 wrapperList.getAuthorityRefDocItem().clear();
\r
340 commonList.setItemsInPage(wrapperList.getAuthorityRefDocItem().size());
\r
341 return wrapperList;
\r
344 // Otherwise, return a list of items from the start of the specified
\r
345 // page through the last item on that page, or otherwise through the
\r
346 // last item in the entire list, if that occurs earlier than the end
\r
347 // of the specified page.
\r
348 if (endIndex == 0) {
\r
349 int pageEndIndex = ((startIndex + pageSize));
\r
350 endIndex = (pageEndIndex > list.size()) ? list.size() : pageEndIndex;
\r
353 // Slice the list to return only the specified page of results.
\r
354 // Note: the second argument to List.subList(), endIndex, is
\r
355 // exclusive of the item at its index position, reflecting the
\r
356 // zero-index nature of the list.
\r
357 List<AuthorityRefDocList.AuthorityRefDocItem> currentPageList =
\r
358 new ArrayList<AuthorityRefDocList.AuthorityRefDocItem>(list.subList(startIndex, endIndex));
\r
359 wrapperList.getAuthorityRefDocItem().clear();
\r
360 wrapperList.getAuthorityRefDocItem().addAll(currentPageList);
\r
361 commonList.setItemsInPage(currentPageList.size());
\r
363 if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {
\r
364 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.
\r
366 } catch (Exception e) {
\r
367 logger.error("Could not retrieve a list of documents referring to the specified authority item", e);
\r
368 wrapperList = null;
\r
371 return wrapperList;
\r
374 private static ArrayList<String> getRefNameServiceTypes() {
\r
375 if (refNameServiceTypes == null) {
\r
376 refNameServiceTypes = new ArrayList<String>();
\r
377 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);
\r
378 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);
\r
379 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);
\r
381 return refNameServiceTypes;
\r
384 // Seems like a good value - no real data to set this well.
\r
385 // Note: can set this value lower during debugging; e.g. to 3 - ADR 2012-07-10
\r
386 private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;
\r
388 public static int updateAuthorityRefDocs(
\r
389 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
390 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
391 RepositoryInstance repoSession,
\r
394 String refPropName) throws Exception {
\r
395 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
396 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
398 int docsScanned = 0;
\r
399 int nRefsFound = 0;
\r
400 int currentPage = 0;
\r
401 int docsInCurrentPage = 0;
\r
402 final String WHERE_CLAUSE_ADDITIONS_VALUE = null;
\r
403 final String ORDER_BY_VALUE = CollectionSpaceClient.CORE_CREATED_AT; // "collectionspace_core:createdAt";
\r
405 if (repoClient instanceof RepositoryJavaClientImpl == false) {
\r
406 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");
\r
409 try { // REM - How can we deal with transaction and timeout issues here?
\r
410 final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;
\r
411 DocumentModelList docList;
\r
412 boolean morePages = true;
\r
413 while (morePages) {
\r
415 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
416 getRefNameServiceTypes(), oldRefName, refPropName,
\r
417 queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);
\r
419 if (docList == null) {
\r
420 logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");
\r
423 docsInCurrentPage = docList.size();
\r
424 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);
\r
425 if (docsInCurrentPage == 0) {
\r
426 logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");
\r
429 if (docsInCurrentPage < pageSize) {
\r
430 logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");
\r
434 // Only match complete refNames - unless and until we decide how to resolve changes
\r
435 // to NPTs we will defer that and only change PTs or refNames as passed in.
\r
436 int nRefsFoundThisPage = processRefObjsDocList(docList, ctx.getTenantId(), oldRefName, false, queriedServiceBindings, authRefFieldsByService, // Perform the refName updates on the list of document models
\r
438 if (nRefsFoundThisPage > 0) {
\r
439 ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true); // Flush the document model list out to Nuxeo storage
\r
440 nRefsFound += nRefsFoundThisPage;
\r
443 // FIXME: Per REM, set a limit of num objects - something like
\r
444 // 1000K objects - and also add a log Warning after some threshold
\r
445 docsScanned += docsInCurrentPage;
\r
451 } catch (Exception e) {
\r
452 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());
\r
453 logger.debug(Tools.errorToString(e, true));
\r
456 logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");
\r
460 private static DocumentModelList findAuthorityRefDocs(
\r
461 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
462 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
463 RepositoryInstance repoSession, List<String> serviceTypes,
\r
465 String refPropName,
\r
466 Map<String, ServiceBindingType> queriedServiceBindings,
\r
467 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
468 String whereClauseAdditions,
\r
469 String orderByClause,
\r
472 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
474 // Get the service bindings for this tenant
\r
475 TenantBindingConfigReaderImpl tReader =
\r
476 ServiceMain.getInstance().getTenantBindingConfigReader();
\r
477 // We need to get all the procedures, authorities, and objects.
\r
478 List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);
\r
479 if (servicebindings == null || servicebindings.isEmpty()) {
\r
480 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");
\r
483 // Filter the list for current user rights
\r
484 servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);
\r
486 ArrayList<String> docTypes = new ArrayList<String>();
\r
488 String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings, // REM - Side effect that docTypes array gets set. Any others?
\r
489 queriedServiceBindings, authRefFieldsByService);
\r
490 if (query == null) { // found no authRef fields - nothing to query
\r
493 // Additional qualifications, like workflow state
\r
494 if (Tools.notBlank(whereClauseAdditions)) {
\r
495 query += " AND " + whereClauseAdditions;
\r
497 // Now we have to issue the search
\r
498 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
499 DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,
\r
500 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);
\r
501 // Now we gather the info for each document into the list and return
\r
502 DocumentModelList docList = docListWrapper.getWrappedObject();
\r
505 private static final boolean READY_FOR_COMPLEX_QUERY = true;
\r
507 private static String computeWhereClauseForAuthorityRefDocs(
\r
509 String refPropName,
\r
510 ArrayList<String> docTypes,
\r
511 List<ServiceBindingType> servicebindings,
\r
512 Map<String, ServiceBindingType> queriedServiceBindings,
\r
513 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {
\r
515 boolean fFirst = true;
\r
516 List<String> authRefFieldPaths;
\r
517 for (ServiceBindingType sb : servicebindings) {
\r
518 // Gets the property names for each part, qualified with the part label (which
\r
519 // is also the table name, the way that the repository works).
\r
520 authRefFieldPaths =
\r
521 ServiceBindingUtils.getAllPartsPropertyValues(sb,
\r
522 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
523 if (authRefFieldPaths.isEmpty()) {
\r
526 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();
\r
527 for (String spec : authRefFieldPaths) {
\r
528 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
529 authRefsInfo.add(arci);
\r
532 String docType = sb.getObject().getName();
\r
533 queriedServiceBindings.put(docType, sb);
\r
534 authRefFieldsByService.put(docType, authRefsInfo);
\r
535 docTypes.add(docType);
\r
538 if (fFirst) { // found no authRef fields - nothing to query
\r
541 // We used to build a complete matches query, but that was too complex.
\r
542 // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID
\r
543 // Note that this will also match the Item itself, but that will get filtered out when
\r
544 // we compute actual matches.
\r
545 AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);
\r
547 String keywords = RefNameUtils.URN_PREFIX
\r
548 + " AND " + (authTermInfo.inAuthority.name != null
\r
549 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)
\r
550 + " AND " + (authTermInfo.name != null
\r
551 ? authTermInfo.name : authTermInfo.csid);
\r
553 String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);
\r
555 if (logger.isTraceEnabled()) {
\r
556 logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);
\r
559 return whereClauseStr;
\r
562 // TODO there are multiple copies of this that should be put somewhere common.
\r
563 protected static String getRefname(DocumentModel docModel) throws ClientException {
\r
564 String result = (String)docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
\r
565 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
\r
571 * Runs through the list of found docs, processing them. If list is
\r
572 * non-null, then processing means gather the info for items. If list is
\r
573 * null, and newRefName is non-null, then processing means replacing and
\r
574 * updating. If processing/updating, this must be called in the context of
\r
575 * an open session, and caller must release Session after calling this.
\r
578 private static int processRefObjsDocList(
\r
579 DocumentModelList docList,
\r
582 boolean matchBaseOnly,
\r
583 Map<String, ServiceBindingType> queriedServiceBindings,
\r
584 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
585 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
586 String newAuthorityRefName) {
\r
587 Iterator<DocumentModel> iter = docList.iterator();
\r
588 int nRefsFoundTotal = 0;
\r
589 while (iter.hasNext()) {
\r
590 DocumentModel docModel = iter.next();
\r
591 AuthorityRefDocList.AuthorityRefDocItem ilistItem;
\r
593 String docType = docModel.getDocumentType().getName(); // REM - This will be a tentant qualified document type
\r
594 docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);
\r
595 ServiceBindingType sb = queriedServiceBindings.get(docType);
\r
597 throw new RuntimeException(
\r
598 "getAuthorityRefDocs: No Service Binding for docType: " + docType);
\r
601 if (list == null) { // no list - should be update refName case.
\r
602 if (newAuthorityRefName == null) {
\r
603 throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");
\r
606 } else { // Have a list - refObjs case
\r
607 if (newAuthorityRefName != null) {
\r
608 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");
\r
610 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
611 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
\r
613 String itemRefName = getRefname(docModel);
\r
614 ilistItem.setRefName(itemRefName);
\r
615 } catch (ClientException ce) {
\r
616 throw new RuntimeException(
\r
617 "processRefObjsDocList: Problem fetching refName from item Object: "
\r
618 + ce.getLocalizedMessage());
\r
620 ilistItem.setDocId(csid);
\r
622 UriTemplateRegistry registry = ServiceMain.getInstance().getUriTemplateRegistry();
\r
623 UriTemplateRegistryKey key = new UriTemplateRegistryKey(tenantId, docType);
\r
624 StoredValuesUriTemplate template = registry.get(key);
\r
625 if (template != null) {
\r
626 Map<String, String> additionalValues = new HashMap<String, String>();
\r
627 if (template.getUriTemplateType() == UriTemplateFactory.RESOURCE) {
\r
628 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, csid);
\r
629 uri = template.buildUri(additionalValues);
\r
630 } else if (template.getUriTemplateType() == UriTemplateFactory.ITEM) {
\r
632 String inAuthorityCsid = (String) docModel.getPropertyValue("inAuthority"); // AuthorityItemJAXBSchema.IN_AUTHORITY
\r
633 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, inAuthorityCsid);
\r
634 additionalValues.put(UriTemplateFactory.ITEM_IDENTIFIER_VAR, csid);
\r
635 uri = template.buildUri(additionalValues);
\r
636 } catch (Exception e) {
\r
637 logger.warn("Could not extract inAuthority property from authority item record: " + e.getMessage());
\r
639 } else if (template.getUriTemplateType() == UriTemplateFactory.CONTACT) {
\r
640 // FIXME: Generating contact sub-resource URIs requires additional work,
\r
641 // as a follow-on to CSPACE-5271 - ADR 2012-08-16
\r
642 // Sets the default (empty string) value for uri, for now
\r
644 logger.warn("Unrecognized URI template type = " + template.getUriTemplateType());
\r
645 // Sets the default (empty string) value for uri
\r
647 } else { // (if template == null)
\r
648 logger.warn("Could not retrieve URI template from registry via tenant ID "
\r
649 + tenantId + " and docType " + docType);
\r
650 // Sets the default (empty string) value for uri
\r
652 ilistItem.setUri(uri);
\r
654 ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());
\r
655 ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));
\r
656 } catch (Exception e) {
\r
657 logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());
\r
659 ilistItem.setDocType(docType);
\r
660 ilistItem.setDocNumber(
\r
661 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));
\r
662 ilistItem.setDocName(
\r
663 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));
\r
665 // Now, we have to loop over the authRefFieldsByService to figure
\r
666 // out which field(s) matched this.
\r
667 List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);
\r
668 if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {
\r
669 throw new RuntimeException(
\r
670 "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");
\r
672 //String authRefAncestorField = "";
\r
673 //String authRefDescendantField = "";
\r
674 //String sourceField = "";
\r
675 int nRefsFoundInDoc = 0;
\r
677 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
\r
679 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, matchBaseOnly, foundProps); // REM - side effect that foundProps is set
\r
680 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {
\r
681 if (ilistItem != null) {
\r
682 if (nRefsFoundInDoc == 0) { // First one?
\r
683 ilistItem.setSourceField(ari.getQualifiedDisplayName());
\r
684 } else { // duplicates from one object
\r
685 ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());
\r
687 list.add(ilistItem);
\r
688 } else { // update refName case
\r
689 Property propToUpdate = ari.getProperty();
\r
690 propToUpdate.setValue(newAuthorityRefName);
\r
694 } catch (ClientException ce) {
\r
695 throw new RuntimeException(
\r
696 "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());
\r
698 if (nRefsFoundInDoc == 0) {
\r
700 "getAuthorityRefDocs: Result: "
\r
701 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
702 + "] does not reference ["
\r
705 nRefsFoundTotal += nRefsFoundInDoc;
\r
707 return nRefsFoundTotal;
\r
710 private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(
\r
711 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {
\r
712 AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
713 newlistItem.setDocId(ilistItem.getDocId());
\r
714 newlistItem.setDocName(ilistItem.getDocName());
\r
715 newlistItem.setDocNumber(ilistItem.getDocNumber());
\r
716 newlistItem.setDocType(ilistItem.getDocType());
\r
717 newlistItem.setUri(ilistItem.getUri());
\r
718 newlistItem.setSourceField(sourceField);
\r
719 return newlistItem;
\r
722 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
723 DocumentModel docModel,
\r
724 List<AuthRefConfigInfo> authRefFieldInfo,
\r
725 String refNameToMatch,
\r
726 List<AuthRefInfo> foundProps) {
\r
727 return findAuthRefPropertiesInDoc(docModel, authRefFieldInfo,
\r
728 refNameToMatch, false, foundProps);
\r
731 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
732 DocumentModel docModel,
\r
733 List<AuthRefConfigInfo> authRefFieldInfo,
\r
734 String refNameToMatch,
\r
735 boolean matchBaseOnly,
\r
736 List<AuthRefInfo> foundProps) {
\r
737 // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)
\r
738 // and the values are elPaths to the field, where intervening group structures in
\r
739 // lists of complex structures are replaced with "*". Thus, valid paths include
\r
740 // the following (note that the ServiceBindingUtils prepend schema names to configured values):
\r
741 // "schemaname:fieldname"
\r
742 // "schemaname:scalarlistname"
\r
743 // "schemaname:complexfieldname/fieldname"
\r
744 // "schemaname:complexlistname/*/fieldname"
\r
745 // "schemaname:complexlistname/*/scalarlistname"
\r
746 // "schemaname:complexlistname/*/complexfieldname/fieldname"
\r
747 // "schemaname:complexlistname/*/complexlistname/*/fieldname"
\r
749 for (AuthRefConfigInfo arci : authRefFieldInfo) {
\r
751 // Get first property and work down as needed.
\r
752 Property prop = docModel.getProperty(arci.pathEls[0]);
\r
753 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch, matchBaseOnly);
\r
754 } catch (Exception e) {
\r
755 logger.error("Problem fetching property: " + arci.pathEls[0]);
\r
761 private static List<AuthRefInfo> findAuthRefPropertiesInProperty(
\r
762 List<AuthRefInfo> foundProps,
\r
764 AuthRefConfigInfo arci,
\r
765 int pathStartIndex, // Supports recursion and we work down the path
\r
766 String refNameToMatch,
\r
767 boolean matchBaseOnly ) {
\r
768 if (pathStartIndex >= arci.pathEls.length) {
\r
769 throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "
\r
770 + arci.pathEls.toString());
\r
772 AuthRefInfo ari = null;
\r
773 if (prop == null) {
\r
777 if (prop instanceof StringProperty) { // scalar string
\r
778 addARIifMatches(refNameToMatch, matchBaseOnly, arci, prop, foundProps);
\r
779 } else if (prop instanceof List) {
\r
780 List<Property> propList = (List<Property>) prop;
\r
781 // run through list. Must either be list of Strings, or Complex
\r
782 for (Property listItemProp : propList) {
\r
783 if (listItemProp instanceof StringProperty) {
\r
784 if (arci.pathEls.length - pathStartIndex != 1) {
\r
785 logger.error("Configuration for authRefs does not match schema structure: "
\r
786 + arci.pathEls.toString());
\r
789 addARIifMatches(refNameToMatch, matchBaseOnly, arci, listItemProp, foundProps);
\r
791 } else if (listItemProp.isComplex()) {
\r
792 // Just recurse to handle this. Note that since this is a list of complex,
\r
793 // which should look like listName/*/... we add 2 to the path start index
\r
794 findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,
\r
795 pathStartIndex + 2, refNameToMatch, matchBaseOnly);
\r
797 logger.error("Configuration for authRefs does not match schema structure: "
\r
798 + arci.pathEls.toString());
\r
802 } else if (prop.isComplex()) {
\r
803 String localPropName = arci.pathEls[pathStartIndex];
\r
805 Property localProp = prop.get(localPropName);
\r
806 // Now just recurse, pushing down the path 1 step
\r
807 findAuthRefPropertiesInProperty(foundProps, localProp, arci,
\r
808 pathStartIndex, refNameToMatch, matchBaseOnly);
\r
809 } catch (PropertyNotFoundException pnfe) {
\r
810 logger.error("Could not find property: [" + localPropName + "] in path: "
\r
811 + arci.getFullPath());
\r
812 // Fall through - ari will be null and we will continue...
\r
815 logger.error("Configuration for authRefs does not match schema structure: "
\r
816 + arci.pathEls.toString());
\r
820 foundProps.add(ari); //FIXME: REM - This is dead code. 'ari' is never touched after being initalized to null. Why?
\r
826 private static void addARIifMatches(
\r
827 String refNameToMatch,
\r
828 boolean matchBaseOnly,
\r
829 AuthRefConfigInfo arci,
\r
831 List<AuthRefInfo> foundProps) {
\r
832 // Need to either match a passed refName
\r
833 // OR have no refName to match but be non-empty
\r
835 String value = (String) prop.getValue();
\r
836 if (((refNameToMatch != null) &&
\r
838 (value!=null && value.startsWith(refNameToMatch))
\r
839 :refNameToMatch.equals(value)))
\r
840 || ((refNameToMatch == null) && Tools.notBlank(value))) {
\r
842 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");
\r
843 AuthRefInfo ari = new AuthRefInfo(arci, prop);
\r
844 foundProps.add(ari);
\r
846 } catch (PropertyException pe) {
\r
847 logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());
\r
852 * Identifies whether the refName was found in the supplied field. If passed
\r
853 * a new RefName, will set that into fields in which the old one was found.
\r
855 * Only works for: * Scalar fields * Repeatable scalar fields (aka
\r
856 * multi-valued fields)
\r
858 * Does not work for: * Structured fields (complexTypes) * Repeatable
\r
859 * structured fields (repeatable complexTypes) private static int
\r
860 * refNameFoundInField(String oldRefName, Property fieldValue, String
\r
861 * newRefName) { int nFound = 0; if (fieldValue instanceof List) {
\r
862 * List<Property> fieldValueList = (List) fieldValue; for (Property
\r
863 * listItemValue : fieldValueList) { try { if ((listItemValue instanceof
\r
864 * StringProperty) &&
\r
865 * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {
\r
866 * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else
\r
867 * { // We cannot quit after the first, if we are replacing values. // If we
\r
868 * are just looking (not replacing), finding one is enough. break; } } }
\r
869 * catch( PropertyException pe ) {} } } else { try { if ((fieldValue
\r
870 * instanceof StringProperty) &&
\r
871 * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;
\r
872 * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(
\r
873 * PropertyException pe ) {} } return nFound; }
\r