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.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.repository.RepositoryClient;
\r
65 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
\r
66 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
\r
67 import org.collectionspace.services.common.security.SecurityUtils;
\r
68 import org.collectionspace.services.config.service.ServiceBindingType;
\r
69 import org.collectionspace.services.jaxb.AbstractCommonList;
\r
70 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
\r
71 import org.jboss.resteasy.spi.ResteasyProviderFactory;
\r
74 * RefNameServiceUtils is a collection of services utilities related to refName
\r
77 * $LastChangedRevision: $ $LastChangedDate: $
\r
79 public class RefNameServiceUtils {
\r
81 public static class AuthRefConfigInfo {
\r
83 public String getQualifiedDisplayName() {
\r
84 return (Tools.isBlank(schema))
\r
85 ? displayName : DocumentUtils.appendSchemaName(schema, displayName);
\r
88 public String getDisplayName() {
\r
92 public void setDisplayName(String displayName) {
\r
93 this.displayName = displayName;
\r
98 public String getSchema() {
\r
102 public void setSchema(String schema) {
\r
103 this.schema = schema;
\r
106 public String getFullPath() {
\r
110 public void setFullPath(String fullPath) {
\r
111 this.fullPath = fullPath;
\r
114 protected String[] pathEls;
\r
116 public AuthRefConfigInfo(AuthRefConfigInfo arci) {
\r
117 this.displayName = arci.displayName;
\r
118 this.schema = arci.schema;
\r
119 this.fullPath = arci.fullPath;
\r
120 this.pathEls = arci.pathEls;
\r
121 // Skip the pathElse check, since we are creatign from another (presumably valid) arci.
\r
124 public AuthRefConfigInfo(String displayName, String schema, String fullPath, String[] pathEls) {
\r
125 this.displayName = displayName;
\r
126 this.schema = schema;
\r
127 this.fullPath = fullPath;
\r
128 this.pathEls = pathEls;
\r
132 // Split a config value string like "intakes_common:collector", or
\r
133 // "collectionobjects_common:contentPeoples|contentPeople"
\r
134 // "collectionobjects_common:assocEventGroupList/*/assocEventPlace"
\r
135 // If has a pipe ('|') second part is a displayLabel, and first is path
\r
136 // Otherwise, entry is a path, and can use the last pathElement as displayName
\r
137 // Should be schema qualified.
\r
138 public AuthRefConfigInfo(String configString) {
\r
139 String[] pair = configString.split("\\|", 2);
\r
141 String displayName, fullPath;
\r
142 if (pair.length == 1) {
\r
143 // no label specifier, so we'll defer getting label
\r
144 fullPath = pair[0];
\r
145 pathEls = pair[0].split("/");
\r
146 displayName = pathEls[pathEls.length - 1];
\r
148 fullPath = pair[0];
\r
149 pathEls = pair[0].split("/");
\r
150 displayName = pair[1];
\r
152 String[] schemaSplit = pathEls[0].split(":", 2);
\r
154 if (schemaSplit.length == 1) { // schema not specified
\r
157 schema = schemaSplit[0];
\r
158 if (pair.length == 1 && pathEls.length == 1) { // simplest case of field in top level schema, no labelll
\r
159 displayName = schemaSplit[1]; // Have to fix up displayName to have no schema
\r
162 this.displayName = displayName;
\r
163 this.schema = schema;
\r
164 this.fullPath = fullPath;
\r
165 this.pathEls = pathEls;
\r
169 protected void checkPathEls() {
\r
170 int len = pathEls.length;
\r
172 throw new InternalError("Bad values in authRef info - caller screwed up:" + fullPath);
\r
174 // Handle case of them putting a leading slash on the path
\r
175 if (len > 1 && pathEls[0].endsWith(":")) {
\r
177 String[] newArray = new String[len];
\r
178 newArray[0] = pathEls[0] + pathEls[1];
\r
180 System.arraycopy(pathEls, 2, newArray, 1, len - 1);
\r
182 pathEls = newArray;
\r
187 public static class AuthRefInfo extends AuthRefConfigInfo {
\r
189 public Property getProperty() {
\r
193 public void setProperty(Property property) {
\r
194 this.property = property;
\r
198 public AuthRefInfo(String displayName, String schema, String fullPath, String[] pathEls, Property prop) {
\r
199 super(displayName, schema, fullPath, pathEls);
\r
200 this.property = prop;
\r
203 public AuthRefInfo(AuthRefConfigInfo arci, Property prop) {
\r
205 this.property = prop;
\r
209 private static final Logger logger = LoggerFactory.getLogger(RefNameServiceUtils.class);
\r
210 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 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 updateRefNamesInRelations(ctx, repoClient, repoSession, IRelationsManager.OBJECT_REFNAME, oldRefName, newRefName);
\r
231 public static List<AuthRefConfigInfo> getConfiguredAuthorityRefs(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
\r
232 List<String> authRefFields =
\r
233 ((AbstractServiceContextImpl) ctx).getAllPartsPropertyValues(
\r
234 ServiceBindingUtils.AUTH_REF_PROP, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
235 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>(authRefFields.size());
\r
236 for (String spec : authRefFields) {
\r
237 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
238 authRefsInfo.add(arci);
\r
240 return authRefsInfo;
\r
243 public static AuthorityRefDocList getAuthorityRefDocs(
\r
244 RepositoryInstance repoSession,
\r
245 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
246 UriTemplateRegistry uriTemplateRegistry,
\r
247 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
248 List<String> serviceTypes,
\r
250 String refPropName,
\r
251 DocumentFilter filter, boolean computeTotal)
\r
252 throws DocumentException, DocumentNotFoundException {
\r
253 AuthorityRefDocList wrapperList = new AuthorityRefDocList();
\r
254 AbstractCommonList commonList = (AbstractCommonList) wrapperList;
\r
255 int pageNum = filter.getStartPage();
\r
256 int pageSize = filter.getPageSize();
\r
258 List<AuthorityRefDocList.AuthorityRefDocItem> list =
\r
259 wrapperList.getAuthorityRefDocItem();
\r
261 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
262 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
264 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
266 // Ignore any provided page size and number query parameters in
\r
267 // the following call, as they pertain to the list of authority
\r
268 // references to be returned, not to the list of documents to be
\r
269 // scanned for those references.
\r
270 DocumentModelList docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
271 serviceTypes, refName, refPropName, queriedServiceBindings, authRefFieldsByService,
\r
272 filter.getWhereClause(), null, 0 /* pageSize */, 0 /* pageNum */, computeTotal);
\r
274 if (docList == null) { // found no authRef fields - nothing to process
\r
275 return wrapperList;
\r
278 // set the fieldsReturned list. Even though this is a fixed schema, app layer treats
\r
279 // this like other abstract common lists
\r
281 * <xs:element name="docType" type="xs:string" minOccurs="1" />
\r
282 * <xs:element name="docId" type="xs:string" minOccurs="1" />
\r
283 * <xs:element name="docNumber" type="xs:string" minOccurs="0" />
\r
284 * <xs:element name="docName" type="xs:string" minOccurs="0" />
\r
285 * <xs:element name="sourceField" type="xs:string" minOccurs="1" />
\r
286 * <xs:element name="uri" type="xs:anyURI" 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|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
302 int nRefsFound = processRefObjsDocList(docList, ctx.getTenantId(), refName, queriedServiceBindings, authRefFieldsByService, // the actual list size needs to be updated to the size of "list"
\r
305 commonList.setPageSize(pageSize);
\r
307 // Values returned in the pagination block above the list items
\r
308 // need to reflect the number of references to authority items
\r
309 // returned, rather than the number of documents originally scanned
\r
310 // to find such references.
\r
311 commonList.setPageNum(pageNum);
\r
312 commonList.setTotalItems(list.size());
\r
314 // Slice the list to return only the specified page of items
\r
315 // in the list results.
\r
317 // FIXME: There may well be a pattern-based way to do this
\r
318 // in our framework, and if we can eliminate much of the
\r
319 // non-DRY code below, that would be desirable.
\r
321 int startIndex = 0;
\r
324 // Return all results if pageSize is 0.
\r
325 if (pageSize == 0) {
\r
327 endIndex = list.size();
\r
329 startIndex = pageNum * pageSize;
\r
332 // Return an empty list when the start of the requested page is
\r
333 // beyond the last item in the list.
\r
334 if (startIndex > list.size()) {
\r
335 wrapperList.getAuthorityRefDocItem().clear();
\r
336 commonList.setItemsInPage(wrapperList.getAuthorityRefDocItem().size());
\r
337 return wrapperList;
\r
340 // Otherwise, return a list of items from the start of the specified
\r
341 // page through the last item on that page, or otherwise through the
\r
342 // last item in the entire list, if that occurs earlier than the end
\r
343 // of the specified page.
\r
344 if (endIndex == 0) {
\r
345 int pageEndIndex = ((startIndex + pageSize));
\r
346 endIndex = (pageEndIndex > list.size()) ? list.size() : pageEndIndex;
\r
349 // Slice the list to return only the specified page of results.
\r
350 // Note: the second argument to List.subList(), endIndex, is
\r
351 // exclusive of the item at its index position, reflecting the
\r
352 // zero-index nature of the list.
\r
353 List<AuthorityRefDocList.AuthorityRefDocItem> currentPageList =
\r
354 new ArrayList<AuthorityRefDocList.AuthorityRefDocItem>(list.subList(startIndex, endIndex));
\r
355 wrapperList.getAuthorityRefDocItem().clear();
\r
356 wrapperList.getAuthorityRefDocItem().addAll(currentPageList);
\r
357 commonList.setItemsInPage(currentPageList.size());
\r
359 if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {
\r
360 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.
\r
362 } catch (Exception e) {
\r
363 logger.error("Could not retrieve a list of documents referring to the specified authority item", e);
\r
364 wrapperList = null;
\r
367 return wrapperList;
\r
370 private static ArrayList<String> getRefNameServiceTypes() {
\r
371 if (refNameServiceTypes == null) {
\r
372 refNameServiceTypes = new ArrayList<String>();
\r
373 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);
\r
374 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);
\r
375 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);
\r
377 return refNameServiceTypes;
\r
380 // Seems like a good value - no real data to set this well.
\r
381 // Note: can set this value lower during debugging; e.g. to 3 - ADR 2012-07-10
\r
382 private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;
\r
384 public static int updateAuthorityRefDocs(
\r
385 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
386 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
387 RepositoryInstance repoSession,
\r
390 String refPropName) throws Exception {
\r
391 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
392 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
394 int docsScanned = 0;
\r
395 int nRefsFound = 0;
\r
396 int currentPage = 0;
\r
397 int docsInCurrentPage = 0;
\r
398 final String WHERE_CLAUSE_ADDITIONS_VALUE = null;
\r
399 final String ORDER_BY_VALUE = "collectionspace_core:createdAt";
\r
401 if (!(repoClient instanceof RepositoryJavaClientImpl)) {
\r
402 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");
\r
404 try { // REM - How can we deal with transaction and timeout issues here?
\r
405 final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;
\r
406 DocumentModelList docList;
\r
407 boolean morePages = true;
\r
408 while (morePages) {
\r
410 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
411 getRefNameServiceTypes(), oldRefName, refPropName,
\r
412 queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);
\r
414 if (docList == null) {
\r
415 logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");
\r
418 docsInCurrentPage = docList.size();
\r
419 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);
\r
420 if (docsInCurrentPage == 0) {
\r
421 logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");
\r
424 if (docsInCurrentPage < pageSize) {
\r
425 logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");
\r
429 int nRefsFoundThisPage = processRefObjsDocList(docList, ctx.getTenantId(), oldRefName, queriedServiceBindings, authRefFieldsByService,
\r
431 if (nRefsFoundThisPage > 0) {
\r
432 ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true);
\r
433 nRefsFound += nRefsFoundThisPage;
\r
436 // FIXME: Per REM, set a limit of num objects - something like
\r
437 // 1000K objects - and also add a log Warning after some threshold
\r
438 docsScanned += docsInCurrentPage;
\r
444 } catch (Exception e) {
\r
445 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());
\r
446 logger.debug(Tools.errorToString(e, true));
\r
449 logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");
\r
453 private static DocumentModelList findAuthorityRefDocs(
\r
454 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
455 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
456 RepositoryInstance repoSession, List<String> serviceTypes,
\r
458 String refPropName,
\r
459 Map<String, ServiceBindingType> queriedServiceBindings,
\r
460 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
461 String whereClauseAdditions,
\r
462 String orderByClause,
\r
465 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
467 // Get the service bindings for this tenant
\r
468 TenantBindingConfigReaderImpl tReader =
\r
469 ServiceMain.getInstance().getTenantBindingConfigReader();
\r
470 // We need to get all the procedures, authorities, and objects.
\r
471 List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);
\r
472 if (servicebindings == null || servicebindings.isEmpty()) {
\r
473 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");
\r
476 // Filter the list for current user rights
\r
477 servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);
\r
479 ArrayList<String> docTypes = new ArrayList<String>();
\r
481 String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings,
\r
482 queriedServiceBindings, authRefFieldsByService);
\r
483 if (query == null) { // found no authRef fields - nothing to query
\r
486 // Additional qualifications, like workflow state
\r
487 if (Tools.notBlank(whereClauseAdditions)) {
\r
488 query += " AND " + whereClauseAdditions;
\r
490 // Now we have to issue the search
\r
491 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
492 DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,
\r
493 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);
\r
494 // Now we gather the info for each document into the list and return
\r
495 DocumentModelList docList = docListWrapper.getWrappedObject();
\r
498 private static final boolean READY_FOR_COMPLEX_QUERY = true;
\r
500 private static String computeWhereClauseForAuthorityRefDocs(
\r
502 String refPropName,
\r
503 ArrayList<String> docTypes,
\r
504 List<ServiceBindingType> servicebindings,
\r
505 Map<String, ServiceBindingType> queriedServiceBindings,
\r
506 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {
\r
508 boolean fFirst = true;
\r
509 List<String> authRefFieldPaths;
\r
510 for (ServiceBindingType sb : servicebindings) {
\r
511 // Gets the property names for each part, qualified with the part label (which
\r
512 // is also the table name, the way that the repository works).
\r
513 authRefFieldPaths =
\r
514 ServiceBindingUtils.getAllPartsPropertyValues(sb,
\r
515 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
516 if (authRefFieldPaths.isEmpty()) {
\r
519 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();
\r
520 for (String spec : authRefFieldPaths) {
\r
521 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
522 authRefsInfo.add(arci);
\r
525 String docType = sb.getObject().getName();
\r
526 queriedServiceBindings.put(docType, sb);
\r
527 authRefFieldsByService.put(docType, authRefsInfo);
\r
528 docTypes.add(docType);
\r
531 if (fFirst) { // found no authRef fields - nothing to query
\r
534 // We used to build a complete matches query, but that was too complex.
\r
535 // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID
\r
536 // Note that this will also match the Item itself, but that will get filtered out when
\r
537 // we compute actual matches.
\r
538 AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);
\r
540 String keywords = RefNameUtils.URN_PREFIX
\r
541 + " AND " + (authTermInfo.inAuthority.name != null
\r
542 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)
\r
543 + " AND " + (authTermInfo.name != null
\r
544 ? authTermInfo.name : authTermInfo.csid);
\r
546 String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);
\r
548 if (logger.isTraceEnabled()) {
\r
549 logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);
\r
552 return whereClauseStr;
\r
556 * Runs through the list of found docs, processing them. If list is
\r
557 * non-null, then processing means gather the info for items. If list is
\r
558 * null, and newRefName is non-null, then processing means replacing and
\r
559 * updating. If processing/updating, this must be called in teh context of
\r
560 * an open session, and caller must release Session after calling this.
\r
563 private static int processRefObjsDocList(
\r
564 DocumentModelList docList,
\r
567 Map<String, ServiceBindingType> queriedServiceBindings,
\r
568 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
569 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
570 String newAuthorityRefName) {
\r
571 Iterator<DocumentModel> iter = docList.iterator();
\r
572 int nRefsFoundTotal = 0;
\r
573 while (iter.hasNext()) {
\r
574 DocumentModel docModel = iter.next();
\r
575 AuthorityRefDocList.AuthorityRefDocItem ilistItem;
\r
577 String docType = docModel.getDocumentType().getName(); // REM - This will be a tentant qualified document type
\r
578 docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);
\r
579 ServiceBindingType sb = queriedServiceBindings.get(docType);
\r
581 throw new RuntimeException(
\r
582 "getAuthorityRefDocs: No Service Binding for docType: " + docType);
\r
585 if (list == null) { // no list - should be update refName case.
\r
586 if (newAuthorityRefName == null) {
\r
587 throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");
\r
590 } else { // Have a list - refObjs case
\r
591 if (newAuthorityRefName != null) {
\r
592 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");
\r
594 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
595 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
\r
596 ilistItem.setDocId(csid);
\r
598 UriTemplateRegistry registry = ServiceMain.getInstance().getUriTemplateRegistry();
\r
599 UriTemplateRegistryKey key = new UriTemplateRegistryKey(tenantId, docType);
\r
600 StoredValuesUriTemplate template = registry.get(key);
\r
601 if (template != null) {
\r
602 Map<String, String> additionalValues = new HashMap<String, String>();
\r
603 if (template.getUriTemplateType() == UriTemplateFactory.RESOURCE) {
\r
604 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, csid);
\r
605 uri = template.buildUri(additionalValues);
\r
606 } else if (template.getUriTemplateType() == UriTemplateFactory.ITEM) {
\r
608 String inAuthorityCsid = (String) docModel.getPropertyValue("inAuthority"); // AuthorityItemJAXBSchema.IN_AUTHORITY
\r
609 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, inAuthorityCsid);
\r
610 additionalValues.put(UriTemplateFactory.ITEM_IDENTIFIER_VAR, csid);
\r
611 uri = template.buildUri(additionalValues);
\r
612 } catch (Exception e) {
\r
613 logger.warn("Could not extract inAuthority property from authority item record: " + e.getMessage());
\r
615 } else if (template.getUriTemplateType() == UriTemplateFactory.CONTACT) {
\r
616 // FIXME: Generating contact sub-resource URIs requires additional work,
\r
617 // as a follow-on to CSPACE-5271 - ADR 2012-08-16
\r
618 // Sets the default (empty string) value for uri, for now
\r
620 logger.warn("Unrecognized URI template type = " + template.getUriTemplateType());
\r
621 // Sets the default (empty string) value for uri
\r
623 } else { // (if template == null)
\r
624 logger.warn("Could not retrieve URI template from registry via tenant ID "
\r
625 + tenantId + " and docType " + docType);
\r
626 // Sets the default (empty string) value for uri
\r
628 ilistItem.setUri(uri);
\r
630 ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());
\r
631 ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));
\r
632 } catch (Exception e) {
\r
633 logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());
\r
635 ilistItem.setDocType(docType);
\r
636 ilistItem.setDocNumber(
\r
637 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));
\r
638 ilistItem.setDocName(
\r
639 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));
\r
641 // Now, we have to loop over the authRefFieldsByService to figure
\r
642 // out which field(s) matched this.
\r
643 List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);
\r
644 if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {
\r
645 throw new RuntimeException(
\r
646 "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");
\r
648 //String authRefAncestorField = "";
\r
649 //String authRefDescendantField = "";
\r
650 //String sourceField = "";
\r
651 int nRefsFoundInDoc = 0;
\r
653 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
\r
655 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, foundProps);
\r
656 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {
\r
657 if (ilistItem != null) {
\r
658 if (nRefsFoundInDoc == 0) { // First one?
\r
659 ilistItem.setSourceField(ari.getQualifiedDisplayName());
\r
660 } else { // duplicates from one object
\r
661 ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());
\r
663 list.add(ilistItem);
\r
664 } else { // update refName case
\r
665 Property propToUpdate = ari.getProperty();
\r
666 propToUpdate.setValue(newAuthorityRefName);
\r
670 } catch (ClientException ce) {
\r
671 throw new RuntimeException(
\r
672 "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());
\r
674 if (nRefsFoundInDoc == 0) {
\r
676 "getAuthorityRefDocs: Result: "
\r
677 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
678 + "] does not reference ["
\r
681 nRefsFoundTotal += nRefsFoundInDoc;
\r
683 return nRefsFoundTotal;
\r
686 private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(
\r
687 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {
\r
688 AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
689 newlistItem.setDocId(ilistItem.getDocId());
\r
690 newlistItem.setDocName(ilistItem.getDocName());
\r
691 newlistItem.setDocNumber(ilistItem.getDocNumber());
\r
692 newlistItem.setDocType(ilistItem.getDocType());
\r
693 newlistItem.setUri(ilistItem.getUri());
\r
694 newlistItem.setSourceField(sourceField);
\r
695 return newlistItem;
\r
698 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
699 DocumentModel docModel,
\r
700 List<AuthRefConfigInfo> authRefFieldInfo,
\r
701 String refNameToMatch,
\r
702 List<AuthRefInfo> foundProps) {
\r
703 // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)
\r
704 // and the values are elPaths to the field, where intervening group structures in
\r
705 // lists of complex structures are replaced with "*". Thus, valid paths include
\r
706 // the following (note that the ServiceBindingUtils prepend schema names to configured values):
\r
707 // "schemaname:fieldname"
\r
708 // "schemaname:scalarlistname"
\r
709 // "schemaname:complexfieldname/fieldname"
\r
710 // "schemaname:complexlistname/*/fieldname"
\r
711 // "schemaname:complexlistname/*/scalarlistname"
\r
712 // "schemaname:complexlistname/*/complexfieldname/fieldname"
\r
713 // "schemaname:complexlistname/*/complexlistname/*/fieldname"
\r
715 for (AuthRefConfigInfo arci : authRefFieldInfo) {
\r
717 // Get first property and work down as needed.
\r
718 Property prop = docModel.getProperty(arci.pathEls[0]);
\r
719 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch);
\r
720 } catch (Exception e) {
\r
721 logger.error("Problem fetching property: " + arci.pathEls[0]);
\r
727 public static List<AuthRefInfo> findAuthRefPropertiesInProperty(
\r
728 List<AuthRefInfo> foundProps,
\r
730 AuthRefConfigInfo arci,
\r
731 int pathStartIndex, // Supports recursion and we work down the path
\r
732 String refNameToMatch) {
\r
733 if (pathStartIndex >= arci.pathEls.length) {
\r
734 throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "
\r
735 + arci.pathEls.toString());
\r
737 AuthRefInfo ari = null;
\r
738 if (prop == null) {
\r
742 if (prop instanceof StringProperty) { // scalar string
\r
743 addARIifMatches(refNameToMatch, arci, prop, foundProps);
\r
744 } else if (prop instanceof List) {
\r
745 List<Property> propList = (List<Property>) prop;
\r
746 // run through list. Must either be list of Strings, or Complex
\r
747 for (Property listItemProp : propList) {
\r
748 if (listItemProp instanceof StringProperty) {
\r
749 if (arci.pathEls.length - pathStartIndex != 1) {
\r
750 logger.error("Configuration for authRefs does not match schema structure: "
\r
751 + arci.pathEls.toString());
\r
754 addARIifMatches(refNameToMatch, arci, listItemProp, foundProps);
\r
756 } else if (listItemProp.isComplex()) {
\r
757 // Just recurse to handle this. Note that since this is a list of complex,
\r
758 // which should look like listName/*/... we add 2 to the path start index
\r
759 findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,
\r
760 pathStartIndex + 2, refNameToMatch);
\r
762 logger.error("Configuration for authRefs does not match schema structure: "
\r
763 + arci.pathEls.toString());
\r
767 } else if (prop.isComplex()) {
\r
768 String localPropName = arci.pathEls[pathStartIndex];
\r
770 Property localProp = prop.get(localPropName);
\r
771 // Now just recurse, pushing down the path 1 step
\r
772 findAuthRefPropertiesInProperty(foundProps, localProp, arci,
\r
773 pathStartIndex, refNameToMatch);
\r
774 } catch (PropertyNotFoundException pnfe) {
\r
775 logger.error("Could not find property: [" + localPropName + "] in path: "
\r
776 + arci.getFullPath());
\r
777 // Fall through - ari will be null and we will continue...
\r
780 logger.error("Configuration for authRefs does not match schema structure: "
\r
781 + arci.pathEls.toString());
\r
785 foundProps.add(ari); //FIXME: REM - This is dead code. 'ari' is never touched after being initalized to null. Why?
\r
791 private static void addARIifMatches(
\r
792 String refNameToMatch,
\r
793 AuthRefConfigInfo arci,
\r
795 List<AuthRefInfo> foundProps) {
\r
796 // Need to either match a passed refName
\r
797 // OR have no refName to match but be non-empty
\r
799 String value = (String) prop.getValue();
\r
800 if (((refNameToMatch != null) && refNameToMatch.equals(value))
\r
801 || ((refNameToMatch == null) && Tools.notBlank(value))) {
\r
803 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");
\r
804 AuthRefInfo ari = new AuthRefInfo(arci, prop);
\r
805 foundProps.add(ari);
\r
807 } catch (PropertyException pe) {
\r
808 logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());
\r
813 * Identifies whether the refName was found in the supplied field. If passed
\r
814 * a new RefName, will set that into fields in which the old one was found.
\r
816 * Only works for: * Scalar fields * Repeatable scalar fields (aka
\r
817 * multi-valued fields)
\r
819 * Does not work for: * Structured fields (complexTypes) * Repeatable
\r
820 * structured fields (repeatable complexTypes) private static int
\r
821 * refNameFoundInField(String oldRefName, Property fieldValue, String
\r
822 * newRefName) { int nFound = 0; if (fieldValue instanceof List) {
\r
823 * List<Property> fieldValueList = (List) fieldValue; for (Property
\r
824 * listItemValue : fieldValueList) { try { if ((listItemValue instanceof
\r
825 * StringProperty) &&
\r
826 * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {
\r
827 * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else
\r
828 * { // We cannot quit after the first, if we are replacing values. // If we
\r
829 * are just looking (not replacing), finding one is enough. break; } } }
\r
830 * catch( PropertyException pe ) {} } } else { try { if ((fieldValue
\r
831 * instanceof StringProperty) &&
\r
832 * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;
\r
833 * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(
\r
834 * PropertyException pe ) {} } return nFound; }
\r