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.Collection;
\r
27 import java.util.HashMap;
\r
28 import java.util.Iterator;
\r
29 import java.util.List;
\r
30 import java.util.Map;
\r
32 import org.nuxeo.ecm.core.api.ClientException;
\r
33 import org.nuxeo.ecm.core.api.DocumentModel;
\r
34 import org.nuxeo.ecm.core.api.DocumentModelList;
\r
35 import org.nuxeo.ecm.core.api.model.Property;
\r
36 import org.nuxeo.ecm.core.api.model.PropertyException;
\r
37 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
\r
38 import org.nuxeo.ecm.core.api.model.impl.primitives.StringProperty;
\r
39 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
\r
40 import org.slf4j.Logger;
\r
41 import org.slf4j.LoggerFactory;
\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.context.ServiceContext;
\r
47 import org.collectionspace.services.common.context.AbstractServiceContextImpl;
\r
48 import org.collectionspace.services.common.api.RefNameUtils;
\r
49 import org.collectionspace.services.common.api.Tools;
\r
50 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
\r
51 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
\r
52 import org.collectionspace.services.common.authorityref.AuthorityRefList;
\r
53 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
\r
54 import org.collectionspace.services.common.context.ServiceBindingUtils;
\r
55 import org.collectionspace.services.common.document.DocumentException;
\r
56 import org.collectionspace.services.common.document.DocumentFilter;
\r
57 import org.collectionspace.services.common.document.DocumentNotFoundException;
\r
58 import org.collectionspace.services.common.document.DocumentUtils;
\r
59 import org.collectionspace.services.common.document.DocumentWrapper;
\r
60 import org.collectionspace.services.common.query.QueryManager;
\r
61 import org.collectionspace.services.common.repository.RepositoryClient;
\r
62 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
\r
63 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
\r
64 import org.collectionspace.services.common.security.SecurityUtils;
\r
65 import org.collectionspace.services.config.service.ServiceBindingType;
\r
66 import org.collectionspace.services.jaxb.AbstractCommonList;
\r
67 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
\r
69 import com.sun.xml.bind.v2.runtime.unmarshaller.XsiNilLoader.Array;
\r
72 * RefNameServiceUtils is a collection of services utilities related to refName
\r
75 * $LastChangedRevision: $ $LastChangedDate: $
\r
77 public class RefNameServiceUtils {
\r
79 public static class AuthRefConfigInfo {
\r
81 public String getQualifiedDisplayName() {
\r
82 return (Tools.isBlank(schema))
\r
83 ? displayName : DocumentUtils.appendSchemaName(schema, displayName);
\r
86 public String getDisplayName() {
\r
90 public void setDisplayName(String displayName) {
\r
91 this.displayName = displayName;
\r
96 public String getSchema() {
\r
100 public void setSchema(String schema) {
\r
101 this.schema = schema;
\r
104 public String getFullPath() {
\r
108 public void setFullPath(String fullPath) {
\r
109 this.fullPath = fullPath;
\r
112 protected String[] pathEls;
\r
114 public AuthRefConfigInfo(AuthRefConfigInfo arci) {
\r
115 this.displayName = arci.displayName;
\r
116 this.schema = arci.schema;
\r
117 this.fullPath = arci.fullPath;
\r
118 this.pathEls = arci.pathEls;
\r
119 // Skip the pathElse check, since we are creatign from another (presumably valid) arci.
\r
122 public AuthRefConfigInfo(String displayName, String schema, String fullPath, String[] pathEls) {
\r
123 this.displayName = displayName;
\r
124 this.schema = schema;
\r
125 this.fullPath = fullPath;
\r
126 this.pathEls = pathEls;
\r
130 // Split a config value string like "intakes_common:collector", or
\r
131 // "collectionobjects_common:contentPeoples|contentPeople"
\r
132 // "collectionobjects_common:assocEventGroupList/*/assocEventPlace"
\r
133 // If has a pipe ('|') second part is a displayLabel, and first is path
\r
134 // Otherwise, entry is a path, and can use the last pathElement as displayName
\r
135 // Should be schema qualified.
\r
136 public AuthRefConfigInfo(String configString) {
\r
137 String[] pair = configString.split("\\|", 2);
\r
139 String displayName, fullPath;
\r
140 if (pair.length == 1) {
\r
141 // no label specifier, so we'll defer getting label
\r
142 fullPath = pair[0];
\r
143 pathEls = pair[0].split("/");
\r
144 displayName = pathEls[pathEls.length - 1];
\r
146 fullPath = pair[0];
\r
147 pathEls = pair[0].split("/");
\r
148 displayName = pair[1];
\r
150 String[] schemaSplit = pathEls[0].split(":", 2);
\r
152 if (schemaSplit.length == 1) { // schema not specified
\r
155 schema = schemaSplit[0];
\r
156 if (pair.length == 1 && pathEls.length == 1) { // simplest case of field in top level schema, no labelll
\r
157 displayName = schemaSplit[1]; // Have to fix up displayName to have no schema
\r
160 this.displayName = displayName;
\r
161 this.schema = schema;
\r
162 this.fullPath = fullPath;
\r
163 this.pathEls = pathEls;
\r
167 protected void checkPathEls() {
\r
168 int len = pathEls.length;
\r
170 throw new InternalError("Bad values in authRef info - caller screwed up:" + fullPath);
\r
172 // Handle case of them putting a leading slash on the path
\r
173 if (len > 1 && pathEls[0].endsWith(":")) {
\r
175 String[] newArray = new String[len];
\r
176 newArray[0] = pathEls[0] + pathEls[1];
\r
178 System.arraycopy(pathEls, 2, newArray, 1, len - 1);
\r
180 pathEls = newArray;
\r
185 public static class AuthRefInfo extends AuthRefConfigInfo {
\r
187 public Property getProperty() {
\r
191 public void setProperty(Property property) {
\r
192 this.property = property;
\r
196 public AuthRefInfo(String displayName, String schema, String fullPath, String[] pathEls, Property prop) {
\r
197 super(displayName, schema, fullPath, pathEls);
\r
198 this.property = prop;
\r
201 public AuthRefInfo(AuthRefConfigInfo arci, Property prop) {
\r
203 this.property = prop;
\r
206 private static final Logger logger = LoggerFactory.getLogger(RefNameServiceUtils.class);
\r
207 private static ArrayList<String> refNameServiceTypes = null;
\r
209 public static List<AuthRefConfigInfo> getConfiguredAuthorityRefs(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
\r
210 List<String> authRefFields =
\r
211 ((AbstractServiceContextImpl) ctx).getAllPartsPropertyValues(
\r
212 ServiceBindingUtils.AUTH_REF_PROP, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
213 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>(authRefFields.size());
\r
214 for (String spec : authRefFields) {
\r
215 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
216 authRefsInfo.add(arci);
\r
218 return authRefsInfo;
\r
221 public static AuthorityRefDocList getAuthorityRefDocs(
\r
222 RepositoryInstance repoSession,
\r
223 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
224 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
225 List<String> serviceTypes,
\r
227 String refPropName,
\r
228 DocumentFilter filter, boolean computeTotal)
\r
229 throws DocumentException, DocumentNotFoundException {
\r
230 AuthorityRefDocList wrapperList = new AuthorityRefDocList();
\r
231 AbstractCommonList commonList = (AbstractCommonList) wrapperList;
\r
232 int pageNum = filter.getStartPage();
\r
233 int pageSize = filter.getPageSize();
\r
235 List<AuthorityRefDocList.AuthorityRefDocItem> list =
\r
236 wrapperList.getAuthorityRefDocItem();
\r
238 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
239 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
241 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
243 // Ignore any provided page size and number query parameters in
\r
244 // the following call, as they pertain to the list of authority
\r
245 // references to be returned, not to the list of documents to be
\r
246 // scanned for those references.
\r
247 DocumentModelList docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
248 serviceTypes, refName, refPropName, queriedServiceBindings, authRefFieldsByService,
\r
249 filter.getWhereClause(), null, 0 /* pageSize */, 0 /* pageNum */, computeTotal);
\r
251 if (docList == null) { // found no authRef fields - nothing to process
\r
252 return wrapperList;
\r
255 // set the fieldsReturned list. Even though this is a fixed schema, app layer treats
\r
256 // this like other abstract common lists
\r
258 * <xs:element name="docType" type="xs:string" minOccurs="1" />
\r
259 * <xs:element name="docId" type="xs:string" minOccurs="1" />
\r
260 * <xs:element name="docNumber" type="xs:string" minOccurs="0" />
\r
261 * <xs:element name="docName" type="xs:string" minOccurs="0" />
\r
262 * <xs:element name="sourceField" type="xs:string" minOccurs="1" />
\r
263 * <xs:element name="uri" type="xs:anyURI" minOccurs="1" />
\r
264 * <xs:element name="updatedAt" type="xs:string" minOccurs="1" />
\r
265 * <xs:element name="workflowState" type="xs:string" minOccurs="1"
\r
268 String fieldList = "docType|docId|docNumber|docName|sourceField|uri|updatedAt|workflowState";
\r
269 commonList.setFieldsReturned(fieldList);
\r
271 // As a side-effect, the method called below modifies the value of
\r
272 // the 'list' variable, which holds the list of references to
\r
273 // an authority item.
\r
275 // There can be more than one reference to a particular authority
\r
276 // item within any individual document scanned, so the number of
\r
277 // authority references may potentially exceed the total number
\r
278 // of documents scanned.
\r
279 int nRefsFound = processRefObjsDocList(docList, refName, queriedServiceBindings, authRefFieldsByService, // the actual list size needs to be updated to the size of "list"
\r
282 commonList.setPageSize(pageSize);
\r
284 // Values returned in the pagination block above the list items
\r
285 // need to reflect the number of references to authority items
\r
286 // returned, rather than the number of documents originally scanned
\r
287 // to find such references.
\r
288 commonList.setPageNum(pageNum);
\r
289 commonList.setTotalItems(list.size());
\r
291 // Slice the list to return only the specified page of items
\r
292 // in the list results.
\r
294 // FIXME: There may well be a pattern-based way to do this
\r
295 // in our framework.
\r
296 int startIndex = 0;
\r
298 // Return all results if pageSize is 0.
\r
299 if (pageSize == 0) {
\r
301 endIndex = list.size();
\r
303 startIndex = pageNum * pageSize;
\r
306 // Return an empty list if the start of the requested page is
\r
307 // beyond the last item in the list.
\r
308 if (startIndex > list.size()) {
\r
309 wrapperList = null;
\r
310 return wrapperList;
\r
313 // Otherwise, return a list of items from the start of the specified
\r
314 // page through the last item on that page, or otherwise through the last
\r
315 // item in the entire list, if it occurs prior to the end of that page.
\r
316 if (endIndex == 0) {
\r
317 int pageEndIndex = ((startIndex + pageSize));
\r
318 endIndex = (pageEndIndex > list.size()) ? list.size() : pageEndIndex;
\r
321 // Slice the list to return only the specified page.
\r
322 // Note: the second argument to List.subList() is exclusive of the
\r
323 // item at its index position, reflecting the zero-index nature of
\r
325 List<AuthorityRefDocList.AuthorityRefDocItem> currentPageList =
\r
326 new ArrayList<AuthorityRefDocList.AuthorityRefDocItem>(list.subList(startIndex, endIndex));
\r
327 wrapperList.getAuthorityRefDocItem().clear();
\r
328 wrapperList.getAuthorityRefDocItem().addAll(currentPageList);
\r
329 commonList.setItemsInPage(currentPageList.size());
\r
331 if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {
\r
332 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.
\r
334 } catch (Exception e) {
\r
335 logger.error("Could not retrieve a list of documents referring to the specified authority item", e);
\r
336 wrapperList = null;
\r
339 return wrapperList;
\r
342 private static ArrayList<String> getRefNameServiceTypes() {
\r
343 if (refNameServiceTypes == null) {
\r
344 refNameServiceTypes = new ArrayList<String>();
\r
345 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);
\r
346 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);
\r
347 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);
\r
349 return refNameServiceTypes;
\r
351 // Seems like a good value - no real data to set this well.
\r
352 // Note: can set this value lower; e.g. to 3 during debugging; - ADR 2012-07-10
\r
353 private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;
\r
355 public static int updateAuthorityRefDocs(
\r
356 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
357 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
358 RepositoryInstance repoSession,
\r
361 String refPropName) throws Exception {
\r
362 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
363 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
365 int docsScanned = 0;
\r
366 int nRefsFound = 0;
\r
367 int currentPage = 0;
\r
368 int docsInCurrentPage = 0;
\r
369 final String WHERE_CLAUSE_ADDITIONS_VALUE = null;
\r
370 final String ORDER_BY_VALUE = "collectionspace_core:createdAt";
\r
372 if (!(repoClient instanceof RepositoryJavaClientImpl)) {
\r
373 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");
\r
375 try { // REM - How can we deal with transaction and timeout issues here.
\r
376 final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;
\r
377 DocumentModelList docList;
\r
378 boolean morePages = true;
\r
379 while (morePages) {
\r
381 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
382 getRefNameServiceTypes(), oldRefName, refPropName,
\r
383 queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);
\r
385 if (docList == null) {
\r
386 logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");
\r
389 docsInCurrentPage = docList.size();
\r
390 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);
\r
391 if (docsInCurrentPage == 0) {
\r
392 logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");
\r
395 if (docsInCurrentPage < pageSize) {
\r
396 logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");
\r
400 int nRefsFoundThisPage = processRefObjsDocList(docList, oldRefName, queriedServiceBindings, authRefFieldsByService,
\r
402 if (nRefsFoundThisPage > 0) {
\r
403 ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true);
\r
404 nRefsFound += nRefsFoundThisPage;
\r
407 // FIXME: Per REM, set a limit of num objects - something like
\r
408 // 1000K objects - and also add a log Warning after some threshold
\r
409 docsScanned += docsInCurrentPage;
\r
415 } catch (Exception e) {
\r
416 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());
\r
417 logger.debug(Tools.errorToString(e, true));
\r
420 logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");
\r
424 private static DocumentModelList findAuthorityRefDocs(
\r
425 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
426 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
427 RepositoryInstance repoSession, List<String> serviceTypes,
\r
429 String refPropName,
\r
430 Map<String, ServiceBindingType> queriedServiceBindings,
\r
431 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
432 String whereClauseAdditions,
\r
433 String orderByClause,
\r
436 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
438 // Get the service bindings for this tenant
\r
439 TenantBindingConfigReaderImpl tReader =
\r
440 ServiceMain.getInstance().getTenantBindingConfigReader();
\r
441 // We need to get all the procedures, authorities, and objects.
\r
442 List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);
\r
443 if (servicebindings == null || servicebindings.isEmpty()) {
\r
444 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");
\r
447 // Filter the list for current user rights
\r
448 servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);
\r
450 ArrayList<String> docTypes = new ArrayList<String>();
\r
452 String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings,
\r
453 queriedServiceBindings, authRefFieldsByService);
\r
454 if (query == null) { // found no authRef fields - nothing to query
\r
457 // Additional qualifications, like workflow state
\r
458 if (Tools.notBlank(whereClauseAdditions)) {
\r
459 query += " AND " + whereClauseAdditions;
\r
461 // Now we have to issue the search
\r
462 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
463 DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,
\r
464 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);
\r
465 // Now we gather the info for each document into the list and return
\r
466 DocumentModelList docList = docListWrapper.getWrappedObject();
\r
469 private static final boolean READY_FOR_COMPLEX_QUERY = true;
\r
471 private static String computeWhereClauseForAuthorityRefDocs(
\r
473 String refPropName,
\r
474 ArrayList<String> docTypes,
\r
475 List<ServiceBindingType> servicebindings,
\r
476 Map<String, ServiceBindingType> queriedServiceBindings,
\r
477 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {
\r
479 boolean fFirst = true;
\r
480 List<String> authRefFieldPaths;
\r
481 for (ServiceBindingType sb : servicebindings) {
\r
482 // Gets the property names for each part, qualified with the part label (which
\r
483 // is also the table name, the way that the repository works).
\r
484 authRefFieldPaths =
\r
485 ServiceBindingUtils.getAllPartsPropertyValues(sb,
\r
486 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
487 if (authRefFieldPaths.isEmpty()) {
\r
490 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();
\r
491 for (String spec : authRefFieldPaths) {
\r
492 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
493 authRefsInfo.add(arci);
\r
496 String docType = sb.getObject().getName();
\r
497 queriedServiceBindings.put(docType, sb);
\r
498 authRefFieldsByService.put(docType, authRefsInfo);
\r
499 docTypes.add(docType);
\r
502 if (fFirst) { // found no authRef fields - nothing to query
\r
505 // We used to build a complete matches query, but that was too complex.
\r
506 // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID
\r
507 // Note that this will also match the Item itself, but that will get filtered out when
\r
508 // we compute actual matches.
\r
509 AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);
\r
511 String keywords = RefNameUtils.URN_PREFIX
\r
512 + " AND " + (authTermInfo.inAuthority.name != null
\r
513 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)
\r
514 + " AND " + (authTermInfo.name != null
\r
515 ? authTermInfo.name : authTermInfo.csid);
\r
517 String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);
\r
519 if (logger.isTraceEnabled()) {
\r
520 logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);
\r
523 return whereClauseStr;
\r
527 * Runs through the list of found docs, processing them. If list is
\r
528 * non-null, then processing means gather the info for items. If list is
\r
529 * null, and newRefName is non-null, then processing means replacing and
\r
530 * updating. If processing/updating, this must be called in teh context of
\r
531 * an open session, and caller must release Session after calling this.
\r
534 private static int processRefObjsDocList(
\r
535 DocumentModelList docList,
\r
537 Map<String, ServiceBindingType> queriedServiceBindings,
\r
538 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
539 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
540 String newAuthorityRefName) {
\r
541 Iterator<DocumentModel> iter = docList.iterator();
\r
542 int nRefsFoundTotal = 0;
\r
543 while (iter.hasNext()) {
\r
544 DocumentModel docModel = iter.next();
\r
545 AuthorityRefDocList.AuthorityRefDocItem ilistItem;
\r
547 String docType = docModel.getDocumentType().getName();
\r
548 docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);
\r
549 ServiceBindingType sb = queriedServiceBindings.get(docType);
\r
551 throw new RuntimeException(
\r
552 "getAuthorityRefDocs: No Service Binding for docType: " + docType);
\r
554 String serviceContextPath = "/" + sb.getName().toLowerCase() + "/";
\r
556 if (list == null) { // no list - should be update refName case.
\r
557 if (newAuthorityRefName == null) {
\r
558 throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");
\r
561 } else { // Have a list - refObjs case
\r
562 if (newAuthorityRefName != null) {
\r
563 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");
\r
565 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
566 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
\r
567 ilistItem.setDocId(csid);
\r
568 ilistItem.setUri(serviceContextPath + csid);
\r
570 ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());
\r
571 ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));
\r
572 } catch (Exception e) {
\r
573 logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());
\r
575 // The id and URI are the same on all doctypes
\r
576 ilistItem.setDocType(docType);
\r
577 ilistItem.setDocNumber(
\r
578 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));
\r
579 ilistItem.setDocName(
\r
580 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));
\r
582 // Now, we have to loop over the authRefFieldsByService to figure
\r
583 // out which field(s) matched this.
\r
584 List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);
\r
585 if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {
\r
586 throw new RuntimeException(
\r
587 "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");
\r
589 //String authRefAncestorField = "";
\r
590 //String authRefDescendantField = "";
\r
591 //String sourceField = "";
\r
592 int nRefsFoundInDoc = 0;
\r
594 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
\r
596 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, foundProps);
\r
597 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {
\r
598 if (ilistItem != null) {
\r
599 if (nRefsFoundInDoc == 0) { // First one?
\r
600 ilistItem.setSourceField(ari.getQualifiedDisplayName());
\r
601 } else { // duplicates from one object
\r
602 ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());
\r
604 list.add(ilistItem);
\r
605 } else { // update refName case
\r
606 Property propToUpdate = ari.getProperty();
\r
607 propToUpdate.setValue(newAuthorityRefName);
\r
611 } catch (ClientException ce) {
\r
612 throw new RuntimeException(
\r
613 "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());
\r
615 if (nRefsFoundInDoc == 0) {
\r
617 "getAuthorityRefDocs: Result: "
\r
618 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
619 + "] does not reference ["
\r
622 nRefsFoundTotal += nRefsFoundInDoc;
\r
624 return nRefsFoundTotal;
\r
627 private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(
\r
628 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {
\r
629 AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
630 newlistItem.setDocId(ilistItem.getDocId());
\r
631 newlistItem.setDocName(ilistItem.getDocName());
\r
632 newlistItem.setDocNumber(ilistItem.getDocNumber());
\r
633 newlistItem.setDocType(ilistItem.getDocType());
\r
634 newlistItem.setUri(ilistItem.getUri());
\r
635 newlistItem.setSourceField(sourceField);
\r
636 return newlistItem;
\r
639 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
640 DocumentModel docModel,
\r
641 List<AuthRefConfigInfo> authRefFieldInfo,
\r
642 String refNameToMatch,
\r
643 List<AuthRefInfo> foundProps) {
\r
644 // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)
\r
645 // and the values are elPaths to the field, where intervening group structures in
\r
646 // lists of complex structures are replaced with "*". Thus, valid paths include
\r
647 // the following (note that the ServiceBindingUtils prepend schema names to configured values):
\r
648 // "schemaname:fieldname"
\r
649 // "schemaname:scalarlistname"
\r
650 // "schemaname:complexfieldname/fieldname"
\r
651 // "schemaname:complexlistname/*/fieldname"
\r
652 // "schemaname:complexlistname/*/scalarlistname"
\r
653 // "schemaname:complexlistname/*/complexfieldname/fieldname"
\r
654 // "schemaname:complexlistname/*/complexlistname/*/fieldname"
\r
656 for (AuthRefConfigInfo arci : authRefFieldInfo) {
\r
658 // Get first property and work down as needed.
\r
659 Property prop = docModel.getProperty(arci.pathEls[0]);
\r
660 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch);
\r
661 } catch (Exception e) {
\r
662 logger.error("Problem fetching property: " + arci.pathEls[0]);
\r
668 public static List<AuthRefInfo> findAuthRefPropertiesInProperty(
\r
669 List<AuthRefInfo> foundProps,
\r
671 AuthRefConfigInfo arci,
\r
672 int pathStartIndex, // Supports recursion and we work down the path
\r
673 String refNameToMatch) {
\r
674 if (pathStartIndex >= arci.pathEls.length) {
\r
675 throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "
\r
676 + arci.pathEls.toString());
\r
678 AuthRefInfo ari = null;
\r
679 if (prop == null) {
\r
683 if (prop instanceof StringProperty) { // scalar string
\r
684 addARIifMatches(refNameToMatch, arci, prop, foundProps);
\r
685 } else if (prop instanceof List) {
\r
686 List<Property> propList = (List<Property>) prop;
\r
687 // run through list. Must either be list of Strings, or Complex
\r
688 for (Property listItemProp : propList) {
\r
689 if (listItemProp instanceof StringProperty) {
\r
690 if (arci.pathEls.length - pathStartIndex != 1) {
\r
691 logger.error("Configuration for authRefs does not match schema structure: "
\r
692 + arci.pathEls.toString());
\r
695 addARIifMatches(refNameToMatch, arci, listItemProp, foundProps);
\r
697 } else if (listItemProp.isComplex()) {
\r
698 // Just recurse to handle this. Note that since this is a list of complex,
\r
699 // which should look like listName/*/... we add 2 to the path start index
\r
700 findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,
\r
701 pathStartIndex + 2, refNameToMatch);
\r
703 logger.error("Configuration for authRefs does not match schema structure: "
\r
704 + arci.pathEls.toString());
\r
708 } else if (prop.isComplex()) {
\r
709 String localPropName = arci.pathEls[pathStartIndex];
\r
711 Property localProp = prop.get(localPropName);
\r
712 // Now just recurse, pushing down the path 1 step
\r
713 findAuthRefPropertiesInProperty(foundProps, localProp, arci,
\r
714 pathStartIndex, refNameToMatch);
\r
715 } catch (PropertyNotFoundException pnfe) {
\r
716 logger.error("Could not find property: [" + localPropName + "] in path: "
\r
717 + arci.getFullPath());
\r
718 // Fall through - ari will be null and we will continue...
\r
721 logger.error("Configuration for authRefs does not match schema structure: "
\r
722 + arci.pathEls.toString());
\r
726 foundProps.add(ari); //FIXME: REM - This is dead code. 'ari' is never touched after being initalized to null. Why?
\r
732 private static void addARIifMatches(
\r
733 String refNameToMatch,
\r
734 AuthRefConfigInfo arci,
\r
736 List<AuthRefInfo> foundProps) {
\r
737 // Need to either match a passed refName
\r
738 // OR have no refName to match but be non-empty
\r
740 String value = (String) prop.getValue();
\r
741 if (((refNameToMatch != null) && refNameToMatch.equals(value))
\r
742 || ((refNameToMatch == null) && Tools.notBlank(value))) {
\r
744 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");
\r
745 AuthRefInfo ari = new AuthRefInfo(arci, prop);
\r
746 foundProps.add(ari);
\r
748 } catch (PropertyException pe) {
\r
749 logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());
\r
754 * Identifies whether the refName was found in the supplied field. If passed
\r
755 * a new RefName, will set that into fields in which the old one was found.
\r
757 * Only works for: * Scalar fields * Repeatable scalar fields (aka
\r
758 * multi-valued fields)
\r
760 * Does not work for: * Structured fields (complexTypes) * Repeatable
\r
761 * structured fields (repeatable complexTypes) private static int
\r
762 * refNameFoundInField(String oldRefName, Property fieldValue, String
\r
763 * newRefName) { int nFound = 0; if (fieldValue instanceof List) {
\r
764 * List<Property> fieldValueList = (List) fieldValue; for (Property
\r
765 * listItemValue : fieldValueList) { try { if ((listItemValue instanceof
\r
766 * StringProperty) &&
\r
767 * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {
\r
768 * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else
\r
769 * { // We cannot quit after the first, if we are replacing values. // If we
\r
770 * are just looking (not replacing), finding one is enough. break; } } }
\r
771 * catch( PropertyException pe ) {} } } else { try { if ((fieldValue
\r
772 * instanceof StringProperty) &&
\r
773 * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;
\r
774 * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(
\r
775 * PropertyException pe ) {} } return nFound; }
\r