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.PoxPayloadIn;
\r
43 import org.collectionspace.services.client.PoxPayloadOut;
\r
44 import org.collectionspace.services.common.ServiceMain;
\r
45 import org.collectionspace.services.common.StoredValuesUriTemplate;
\r
46 import org.collectionspace.services.common.UriTemplateFactory;
\r
47 import org.collectionspace.services.common.UriTemplateRegistry;
\r
48 import org.collectionspace.services.common.UriTemplateRegistryKey;
\r
49 import org.collectionspace.services.common.context.ServiceContext;
\r
50 import org.collectionspace.services.common.context.AbstractServiceContextImpl;
\r
51 import org.collectionspace.services.common.api.RefNameUtils;
\r
52 import org.collectionspace.services.common.api.Tools;
\r
53 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
\r
54 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
\r
55 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
\r
56 import org.collectionspace.services.common.context.ServiceBindingUtils;
\r
57 import org.collectionspace.services.common.document.DocumentException;
\r
58 import org.collectionspace.services.common.document.DocumentFilter;
\r
59 import org.collectionspace.services.common.document.DocumentNotFoundException;
\r
60 import org.collectionspace.services.common.document.DocumentUtils;
\r
61 import org.collectionspace.services.common.document.DocumentWrapper;
\r
62 import org.collectionspace.services.common.query.QueryManager;
\r
63 import org.collectionspace.services.common.repository.RepositoryClient;
\r
64 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
\r
65 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
\r
66 import org.collectionspace.services.common.security.SecurityUtils;
\r
67 import org.collectionspace.services.config.service.ServiceBindingType;
\r
68 import org.collectionspace.services.jaxb.AbstractCommonList;
\r
69 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
\r
70 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
208 private static final Logger logger = LoggerFactory.getLogger(RefNameServiceUtils.class);
\r
209 private static ArrayList<String> refNameServiceTypes = null;
\r
211 public static List<AuthRefConfigInfo> getConfiguredAuthorityRefs(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
\r
212 List<String> authRefFields =
\r
213 ((AbstractServiceContextImpl) ctx).getAllPartsPropertyValues(
\r
214 ServiceBindingUtils.AUTH_REF_PROP, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
215 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>(authRefFields.size());
\r
216 for (String spec : authRefFields) {
\r
217 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
218 authRefsInfo.add(arci);
\r
220 return authRefsInfo;
\r
223 public static AuthorityRefDocList getAuthorityRefDocs(
\r
224 RepositoryInstance repoSession,
\r
225 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
226 UriTemplateRegistry uriTemplateRegistry,
\r
227 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
228 List<String> serviceTypes,
\r
230 String refPropName,
\r
231 DocumentFilter filter, boolean computeTotal)
\r
232 throws DocumentException, DocumentNotFoundException {
\r
233 AuthorityRefDocList wrapperList = new AuthorityRefDocList();
\r
234 AbstractCommonList commonList = (AbstractCommonList) wrapperList;
\r
235 int pageNum = filter.getStartPage();
\r
236 int pageSize = filter.getPageSize();
\r
238 List<AuthorityRefDocList.AuthorityRefDocItem> list =
\r
239 wrapperList.getAuthorityRefDocItem();
\r
241 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
242 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
244 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
246 // Ignore any provided page size and number query parameters in
\r
247 // the following call, as they pertain to the list of authority
\r
248 // references to be returned, not to the list of documents to be
\r
249 // scanned for those references.
\r
250 DocumentModelList docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
251 serviceTypes, refName, refPropName, queriedServiceBindings, authRefFieldsByService,
\r
252 filter.getWhereClause(), null, 0 /* pageSize */, 0 /* pageNum */, computeTotal);
\r
254 if (docList == null) { // found no authRef fields - nothing to process
\r
255 return wrapperList;
\r
258 // set the fieldsReturned list. Even though this is a fixed schema, app layer treats
\r
259 // this like other abstract common lists
\r
261 * <xs:element name="docType" type="xs:string" minOccurs="1" />
\r
262 * <xs:element name="docId" type="xs:string" minOccurs="1" />
\r
263 * <xs:element name="docNumber" type="xs:string" minOccurs="0" />
\r
264 * <xs:element name="docName" type="xs:string" minOccurs="0" />
\r
265 * <xs:element name="sourceField" type="xs:string" minOccurs="1" />
\r
266 * <xs:element name="uri" type="xs:anyURI" minOccurs="1" />
\r
267 * <xs:element name="updatedAt" type="xs:string" minOccurs="1" />
\r
268 * <xs:element name="workflowState" type="xs:string" minOccurs="1"
\r
271 String fieldList = "docType|docId|docNumber|docName|sourceField|uri|updatedAt|workflowState";
\r
272 commonList.setFieldsReturned(fieldList);
\r
274 // As a side-effect, the method called below modifies the value of
\r
275 // the 'list' variable, which holds the list of references to
\r
276 // an authority item.
\r
278 // There can be more than one reference to a particular authority
\r
279 // item within any individual document scanned, so the number of
\r
280 // authority references may potentially exceed the total number
\r
281 // of documents scanned.
\r
282 int nRefsFound = processRefObjsDocList(docList, ctx.getTenantId(), refName, queriedServiceBindings, authRefFieldsByService, // the actual list size needs to be updated to the size of "list"
\r
285 commonList.setPageSize(pageSize);
\r
287 // Values returned in the pagination block above the list items
\r
288 // need to reflect the number of references to authority items
\r
289 // returned, rather than the number of documents originally scanned
\r
290 // to find such references.
\r
291 commonList.setPageNum(pageNum);
\r
292 commonList.setTotalItems(list.size());
\r
294 // Slice the list to return only the specified page of items
\r
295 // in the list results.
\r
297 // FIXME: There may well be a pattern-based way to do this
\r
298 // in our framework, and if we can eliminate much of the
\r
299 // non-DRY code below, that would be desirable.
\r
301 int startIndex = 0;
\r
304 // Return all results if pageSize is 0.
\r
305 if (pageSize == 0) {
\r
307 endIndex = list.size();
\r
309 startIndex = pageNum * pageSize;
\r
312 // Return an empty list when the start of the requested page is
\r
313 // beyond the last item in the list.
\r
314 if (startIndex > list.size()) {
\r
315 wrapperList.getAuthorityRefDocItem().clear();
\r
316 commonList.setItemsInPage(wrapperList.getAuthorityRefDocItem().size());
\r
317 return wrapperList;
\r
320 // Otherwise, return a list of items from the start of the specified
\r
321 // page through the last item on that page, or otherwise through the
\r
322 // last item in the entire list, if that occurs earlier than the end
\r
323 // of the specified page.
\r
324 if (endIndex == 0) {
\r
325 int pageEndIndex = ((startIndex + pageSize));
\r
326 endIndex = (pageEndIndex > list.size()) ? list.size() : pageEndIndex;
\r
329 // Slice the list to return only the specified page of results.
\r
330 // Note: the second argument to List.subList(), endIndex, is
\r
331 // exclusive of the item at its index position, reflecting the
\r
332 // zero-index nature of the list.
\r
333 List<AuthorityRefDocList.AuthorityRefDocItem> currentPageList =
\r
334 new ArrayList<AuthorityRefDocList.AuthorityRefDocItem>(list.subList(startIndex, endIndex));
\r
335 wrapperList.getAuthorityRefDocItem().clear();
\r
336 wrapperList.getAuthorityRefDocItem().addAll(currentPageList);
\r
337 commonList.setItemsInPage(currentPageList.size());
\r
339 if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {
\r
340 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.
\r
342 } catch (Exception e) {
\r
343 logger.error("Could not retrieve a list of documents referring to the specified authority item", e);
\r
344 wrapperList = null;
\r
347 return wrapperList;
\r
350 private static ArrayList<String> getRefNameServiceTypes() {
\r
351 if (refNameServiceTypes == null) {
\r
352 refNameServiceTypes = new ArrayList<String>();
\r
353 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);
\r
354 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);
\r
355 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);
\r
357 return refNameServiceTypes;
\r
359 // Seems like a good value - no real data to set this well.
\r
360 // Note: can set this value lower during debugging; e.g. to 3 - ADR 2012-07-10
\r
361 private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;
\r
363 public static int updateAuthorityRefDocs(
\r
364 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
365 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
366 RepositoryInstance repoSession,
\r
369 String refPropName) throws Exception {
\r
370 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
371 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
373 int docsScanned = 0;
\r
374 int nRefsFound = 0;
\r
375 int currentPage = 0;
\r
376 int docsInCurrentPage = 0;
\r
377 final String WHERE_CLAUSE_ADDITIONS_VALUE = null;
\r
378 final String ORDER_BY_VALUE = "collectionspace_core:createdAt";
\r
380 if (!(repoClient instanceof RepositoryJavaClientImpl)) {
\r
381 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");
\r
383 try { // REM - How can we deal with transaction and timeout issues here.
\r
384 final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;
\r
385 DocumentModelList docList;
\r
386 boolean morePages = true;
\r
387 while (morePages) {
\r
389 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
390 getRefNameServiceTypes(), oldRefName, refPropName,
\r
391 queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);
\r
393 if (docList == null) {
\r
394 logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");
\r
397 docsInCurrentPage = docList.size();
\r
398 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);
\r
399 if (docsInCurrentPage == 0) {
\r
400 logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");
\r
403 if (docsInCurrentPage < pageSize) {
\r
404 logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");
\r
408 int nRefsFoundThisPage = processRefObjsDocList(docList, ctx.getTenantId(), oldRefName, queriedServiceBindings, authRefFieldsByService,
\r
410 if (nRefsFoundThisPage > 0) {
\r
411 ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true);
\r
412 nRefsFound += nRefsFoundThisPage;
\r
415 // FIXME: Per REM, set a limit of num objects - something like
\r
416 // 1000K objects - and also add a log Warning after some threshold
\r
417 docsScanned += docsInCurrentPage;
\r
423 } catch (Exception e) {
\r
424 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());
\r
425 logger.debug(Tools.errorToString(e, true));
\r
428 logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");
\r
432 private static DocumentModelList findAuthorityRefDocs(
\r
433 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
434 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
435 RepositoryInstance repoSession, List<String> serviceTypes,
\r
437 String refPropName,
\r
438 Map<String, ServiceBindingType> queriedServiceBindings,
\r
439 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
440 String whereClauseAdditions,
\r
441 String orderByClause,
\r
444 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
446 // Get the service bindings for this tenant
\r
447 TenantBindingConfigReaderImpl tReader =
\r
448 ServiceMain.getInstance().getTenantBindingConfigReader();
\r
449 // We need to get all the procedures, authorities, and objects.
\r
450 List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);
\r
451 if (servicebindings == null || servicebindings.isEmpty()) {
\r
452 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");
\r
455 // Filter the list for current user rights
\r
456 servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);
\r
458 ArrayList<String> docTypes = new ArrayList<String>();
\r
460 String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings,
\r
461 queriedServiceBindings, authRefFieldsByService);
\r
462 if (query == null) { // found no authRef fields - nothing to query
\r
465 // Additional qualifications, like workflow state
\r
466 if (Tools.notBlank(whereClauseAdditions)) {
\r
467 query += " AND " + whereClauseAdditions;
\r
469 // Now we have to issue the search
\r
470 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
471 DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,
\r
472 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);
\r
473 // Now we gather the info for each document into the list and return
\r
474 DocumentModelList docList = docListWrapper.getWrappedObject();
\r
477 private static final boolean READY_FOR_COMPLEX_QUERY = true;
\r
479 private static String computeWhereClauseForAuthorityRefDocs(
\r
481 String refPropName,
\r
482 ArrayList<String> docTypes,
\r
483 List<ServiceBindingType> servicebindings,
\r
484 Map<String, ServiceBindingType> queriedServiceBindings,
\r
485 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {
\r
487 boolean fFirst = true;
\r
488 List<String> authRefFieldPaths;
\r
489 for (ServiceBindingType sb : servicebindings) {
\r
490 // Gets the property names for each part, qualified with the part label (which
\r
491 // is also the table name, the way that the repository works).
\r
492 authRefFieldPaths =
\r
493 ServiceBindingUtils.getAllPartsPropertyValues(sb,
\r
494 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
495 if (authRefFieldPaths.isEmpty()) {
\r
498 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();
\r
499 for (String spec : authRefFieldPaths) {
\r
500 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
501 authRefsInfo.add(arci);
\r
504 String docType = sb.getObject().getName();
\r
505 queriedServiceBindings.put(docType, sb);
\r
506 authRefFieldsByService.put(docType, authRefsInfo);
\r
507 docTypes.add(docType);
\r
510 if (fFirst) { // found no authRef fields - nothing to query
\r
513 // We used to build a complete matches query, but that was too complex.
\r
514 // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID
\r
515 // Note that this will also match the Item itself, but that will get filtered out when
\r
516 // we compute actual matches.
\r
517 AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);
\r
519 String keywords = RefNameUtils.URN_PREFIX
\r
520 + " AND " + (authTermInfo.inAuthority.name != null
\r
521 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)
\r
522 + " AND " + (authTermInfo.name != null
\r
523 ? authTermInfo.name : authTermInfo.csid);
\r
525 String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);
\r
527 if (logger.isTraceEnabled()) {
\r
528 logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);
\r
531 return whereClauseStr;
\r
535 * Runs through the list of found docs, processing them. If list is
\r
536 * non-null, then processing means gather the info for items. If list is
\r
537 * null, and newRefName is non-null, then processing means replacing and
\r
538 * updating. If processing/updating, this must be called in teh context of
\r
539 * an open session, and caller must release Session after calling this.
\r
542 private static int processRefObjsDocList(
\r
543 DocumentModelList docList,
\r
546 Map<String, ServiceBindingType> queriedServiceBindings,
\r
547 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
548 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
549 String newAuthorityRefName) {
\r
550 Iterator<DocumentModel> iter = docList.iterator();
\r
551 int nRefsFoundTotal = 0;
\r
552 while (iter.hasNext()) {
\r
553 DocumentModel docModel = iter.next();
\r
554 AuthorityRefDocList.AuthorityRefDocItem ilistItem;
\r
556 String docType = docModel.getDocumentType().getName();
\r
557 docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);
\r
558 ServiceBindingType sb = queriedServiceBindings.get(docType);
\r
560 throw new RuntimeException(
\r
561 "getAuthorityRefDocs: No Service Binding for docType: " + docType);
\r
564 if (list == null) { // no list - should be update refName case.
\r
565 if (newAuthorityRefName == null) {
\r
566 throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");
\r
569 } else { // Have a list - refObjs case
\r
570 if (newAuthorityRefName != null) {
\r
571 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");
\r
573 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
574 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
\r
575 ilistItem.setDocId(csid);
\r
576 UriTemplateRegistry registry = ServiceMain.getInstance().getUriTemplateRegistry();
\r
577 UriTemplateRegistryKey key = new UriTemplateRegistryKey(tenantId, docType);
\r
578 StoredValuesUriTemplate template = registry.get(key);
\r
579 Map<String, String> additionalValues = new HashMap<String, String>();
\r
580 if (template.getUriTemplateType() == UriTemplateFactory.ITEM) {
\r
582 String inAuthorityCsid = (String) docModel.getPropertyValue("inAuthority"); // AuthorityItemJAXBSchema.IN_AUTHORITY
\r
583 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, inAuthorityCsid);
\r
584 additionalValues.put(UriTemplateFactory.ITEM_IDENTIFIER_VAR, csid);
\r
585 } catch (Exception e) {
\r
586 logger.warn("Could not extract inAuthority property from authority item record: " + e.getMessage());
\r
589 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, csid);
\r
591 String uriStr = template.buildUri(additionalValues);
\r
592 ilistItem.setUri(uriStr);
\r
594 ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());
\r
595 ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));
\r
596 } catch (Exception e) {
\r
597 logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());
\r
599 ilistItem.setDocType(docType);
\r
600 ilistItem.setDocNumber(
\r
601 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));
\r
602 ilistItem.setDocName(
\r
603 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));
\r
605 // Now, we have to loop over the authRefFieldsByService to figure
\r
606 // out which field(s) matched this.
\r
607 List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);
\r
608 if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {
\r
609 throw new RuntimeException(
\r
610 "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");
\r
612 //String authRefAncestorField = "";
\r
613 //String authRefDescendantField = "";
\r
614 //String sourceField = "";
\r
615 int nRefsFoundInDoc = 0;
\r
617 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
\r
619 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, foundProps);
\r
620 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {
\r
621 if (ilistItem != null) {
\r
622 if (nRefsFoundInDoc == 0) { // First one?
\r
623 ilistItem.setSourceField(ari.getQualifiedDisplayName());
\r
624 } else { // duplicates from one object
\r
625 ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());
\r
627 list.add(ilistItem);
\r
628 } else { // update refName case
\r
629 Property propToUpdate = ari.getProperty();
\r
630 propToUpdate.setValue(newAuthorityRefName);
\r
634 } catch (ClientException ce) {
\r
635 throw new RuntimeException(
\r
636 "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());
\r
638 if (nRefsFoundInDoc == 0) {
\r
640 "getAuthorityRefDocs: Result: "
\r
641 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
642 + "] does not reference ["
\r
645 nRefsFoundTotal += nRefsFoundInDoc;
\r
647 return nRefsFoundTotal;
\r
650 private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(
\r
651 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {
\r
652 AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
653 newlistItem.setDocId(ilistItem.getDocId());
\r
654 newlistItem.setDocName(ilistItem.getDocName());
\r
655 newlistItem.setDocNumber(ilistItem.getDocNumber());
\r
656 newlistItem.setDocType(ilistItem.getDocType());
\r
657 newlistItem.setUri(ilistItem.getUri());
\r
658 newlistItem.setSourceField(sourceField);
\r
659 return newlistItem;
\r
662 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
663 DocumentModel docModel,
\r
664 List<AuthRefConfigInfo> authRefFieldInfo,
\r
665 String refNameToMatch,
\r
666 List<AuthRefInfo> foundProps) {
\r
667 // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)
\r
668 // and the values are elPaths to the field, where intervening group structures in
\r
669 // lists of complex structures are replaced with "*". Thus, valid paths include
\r
670 // the following (note that the ServiceBindingUtils prepend schema names to configured values):
\r
671 // "schemaname:fieldname"
\r
672 // "schemaname:scalarlistname"
\r
673 // "schemaname:complexfieldname/fieldname"
\r
674 // "schemaname:complexlistname/*/fieldname"
\r
675 // "schemaname:complexlistname/*/scalarlistname"
\r
676 // "schemaname:complexlistname/*/complexfieldname/fieldname"
\r
677 // "schemaname:complexlistname/*/complexlistname/*/fieldname"
\r
679 for (AuthRefConfigInfo arci : authRefFieldInfo) {
\r
681 // Get first property and work down as needed.
\r
682 Property prop = docModel.getProperty(arci.pathEls[0]);
\r
683 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch);
\r
684 } catch (Exception e) {
\r
685 logger.error("Problem fetching property: " + arci.pathEls[0]);
\r
691 public static List<AuthRefInfo> findAuthRefPropertiesInProperty(
\r
692 List<AuthRefInfo> foundProps,
\r
694 AuthRefConfigInfo arci,
\r
695 int pathStartIndex, // Supports recursion and we work down the path
\r
696 String refNameToMatch) {
\r
697 if (pathStartIndex >= arci.pathEls.length) {
\r
698 throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "
\r
699 + arci.pathEls.toString());
\r
701 AuthRefInfo ari = null;
\r
702 if (prop == null) {
\r
706 if (prop instanceof StringProperty) { // scalar string
\r
707 addARIifMatches(refNameToMatch, arci, prop, foundProps);
\r
708 } else if (prop instanceof List) {
\r
709 List<Property> propList = (List<Property>) prop;
\r
710 // run through list. Must either be list of Strings, or Complex
\r
711 for (Property listItemProp : propList) {
\r
712 if (listItemProp instanceof StringProperty) {
\r
713 if (arci.pathEls.length - pathStartIndex != 1) {
\r
714 logger.error("Configuration for authRefs does not match schema structure: "
\r
715 + arci.pathEls.toString());
\r
718 addARIifMatches(refNameToMatch, arci, listItemProp, foundProps);
\r
720 } else if (listItemProp.isComplex()) {
\r
721 // Just recurse to handle this. Note that since this is a list of complex,
\r
722 // which should look like listName/*/... we add 2 to the path start index
\r
723 findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,
\r
724 pathStartIndex + 2, refNameToMatch);
\r
726 logger.error("Configuration for authRefs does not match schema structure: "
\r
727 + arci.pathEls.toString());
\r
731 } else if (prop.isComplex()) {
\r
732 String localPropName = arci.pathEls[pathStartIndex];
\r
734 Property localProp = prop.get(localPropName);
\r
735 // Now just recurse, pushing down the path 1 step
\r
736 findAuthRefPropertiesInProperty(foundProps, localProp, arci,
\r
737 pathStartIndex, refNameToMatch);
\r
738 } catch (PropertyNotFoundException pnfe) {
\r
739 logger.error("Could not find property: [" + localPropName + "] in path: "
\r
740 + arci.getFullPath());
\r
741 // Fall through - ari will be null and we will continue...
\r
744 logger.error("Configuration for authRefs does not match schema structure: "
\r
745 + arci.pathEls.toString());
\r
749 foundProps.add(ari); //FIXME: REM - This is dead code. 'ari' is never touched after being initalized to null. Why?
\r
755 private static void addARIifMatches(
\r
756 String refNameToMatch,
\r
757 AuthRefConfigInfo arci,
\r
759 List<AuthRefInfo> foundProps) {
\r
760 // Need to either match a passed refName
\r
761 // OR have no refName to match but be non-empty
\r
763 String value = (String) prop.getValue();
\r
764 if (((refNameToMatch != null) && refNameToMatch.equals(value))
\r
765 || ((refNameToMatch == null) && Tools.notBlank(value))) {
\r
767 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");
\r
768 AuthRefInfo ari = new AuthRefInfo(arci, prop);
\r
769 foundProps.add(ari);
\r
771 } catch (PropertyException pe) {
\r
772 logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());
\r
777 * Identifies whether the refName was found in the supplied field. If passed
\r
778 * a new RefName, will set that into fields in which the old one was found.
\r
780 * Only works for: * Scalar fields * Repeatable scalar fields (aka
\r
781 * multi-valued fields)
\r
783 * Does not work for: * Structured fields (complexTypes) * Repeatable
\r
784 * structured fields (repeatable complexTypes) private static int
\r
785 * refNameFoundInField(String oldRefName, Property fieldValue, String
\r
786 * newRefName) { int nFound = 0; if (fieldValue instanceof List) {
\r
787 * List<Property> fieldValueList = (List) fieldValue; for (Property
\r
788 * listItemValue : fieldValueList) { try { if ((listItemValue instanceof
\r
789 * StringProperty) &&
\r
790 * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {
\r
791 * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else
\r
792 * { // We cannot quit after the first, if we are replacing values. // If we
\r
793 * are just looking (not replacing), finding one is enough. break; } } }
\r
794 * catch( PropertyException pe ) {} } } else { try { if ((fieldValue
\r
795 * instanceof StringProperty) &&
\r
796 * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;
\r
797 * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(
\r
798 * PropertyException pe ) {} } return nFound; }
\r