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, and if we can eliminate much of the
\r
296 // non-DRY code below, that would be desirable.
\r
298 int startIndex = 0;
\r
301 // Return all results if pageSize is 0.
\r
302 if (pageSize == 0) {
\r
304 endIndex = list.size();
\r
306 startIndex = pageNum * pageSize;
\r
309 // Return an empty list when the start of the requested page is
\r
310 // beyond the last item in the list.
\r
311 if (startIndex > list.size()) {
\r
312 wrapperList.getAuthorityRefDocItem().clear();
\r
313 commonList.setItemsInPage(wrapperList.getAuthorityRefDocItem().size());
\r
314 return wrapperList;
\r
317 // Otherwise, return a list of items from the start of the specified
\r
318 // page through the last item on that page, or otherwise through the
\r
319 // last item in the entire list, if that occurs earlier than the end
\r
320 // of the specified page.
\r
321 if (endIndex == 0) {
\r
322 int pageEndIndex = ((startIndex + pageSize));
\r
323 endIndex = (pageEndIndex > list.size()) ? list.size() : pageEndIndex;
\r
326 // Slice the list to return only the specified page of results.
\r
327 // Note: the second argument to List.subList(), endIndex, is
\r
328 // exclusive of the item at its index position, reflecting the
\r
329 // zero-index nature of the list.
\r
330 List<AuthorityRefDocList.AuthorityRefDocItem> currentPageList =
\r
331 new ArrayList<AuthorityRefDocList.AuthorityRefDocItem>(list.subList(startIndex, endIndex));
\r
332 wrapperList.getAuthorityRefDocItem().clear();
\r
333 wrapperList.getAuthorityRefDocItem().addAll(currentPageList);
\r
334 commonList.setItemsInPage(currentPageList.size());
\r
336 if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {
\r
337 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.
\r
339 } catch (Exception e) {
\r
340 logger.error("Could not retrieve a list of documents referring to the specified authority item", e);
\r
341 wrapperList = null;
\r
344 return wrapperList;
\r
347 private static ArrayList<String> getRefNameServiceTypes() {
\r
348 if (refNameServiceTypes == null) {
\r
349 refNameServiceTypes = new ArrayList<String>();
\r
350 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);
\r
351 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);
\r
352 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);
\r
354 return refNameServiceTypes;
\r
356 // Seems like a good value - no real data to set this well.
\r
357 // Note: can set this value lower; e.g. to 3 during debugging; - ADR 2012-07-10
\r
358 private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;
\r
360 public static int updateAuthorityRefDocs(
\r
361 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
362 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
363 RepositoryInstance repoSession,
\r
366 String refPropName) throws Exception {
\r
367 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
368 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
370 int docsScanned = 0;
\r
371 int nRefsFound = 0;
\r
372 int currentPage = 0;
\r
373 int docsInCurrentPage = 0;
\r
374 final String WHERE_CLAUSE_ADDITIONS_VALUE = null;
\r
375 final String ORDER_BY_VALUE = "collectionspace_core:createdAt";
\r
377 if (!(repoClient instanceof RepositoryJavaClientImpl)) {
\r
378 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");
\r
380 try { // REM - How can we deal with transaction and timeout issues here.
\r
381 final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;
\r
382 DocumentModelList docList;
\r
383 boolean morePages = true;
\r
384 while (morePages) {
\r
386 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
387 getRefNameServiceTypes(), oldRefName, refPropName,
\r
388 queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);
\r
390 if (docList == null) {
\r
391 logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");
\r
394 docsInCurrentPage = docList.size();
\r
395 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);
\r
396 if (docsInCurrentPage == 0) {
\r
397 logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");
\r
400 if (docsInCurrentPage < pageSize) {
\r
401 logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");
\r
405 int nRefsFoundThisPage = processRefObjsDocList(docList, oldRefName, queriedServiceBindings, authRefFieldsByService,
\r
407 if (nRefsFoundThisPage > 0) {
\r
408 ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true);
\r
409 nRefsFound += nRefsFoundThisPage;
\r
412 // FIXME: Per REM, set a limit of num objects - something like
\r
413 // 1000K objects - and also add a log Warning after some threshold
\r
414 docsScanned += docsInCurrentPage;
\r
420 } catch (Exception e) {
\r
421 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());
\r
422 logger.debug(Tools.errorToString(e, true));
\r
425 logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");
\r
429 private static DocumentModelList findAuthorityRefDocs(
\r
430 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
431 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
432 RepositoryInstance repoSession, List<String> serviceTypes,
\r
434 String refPropName,
\r
435 Map<String, ServiceBindingType> queriedServiceBindings,
\r
436 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
437 String whereClauseAdditions,
\r
438 String orderByClause,
\r
441 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
443 // Get the service bindings for this tenant
\r
444 TenantBindingConfigReaderImpl tReader =
\r
445 ServiceMain.getInstance().getTenantBindingConfigReader();
\r
446 // We need to get all the procedures, authorities, and objects.
\r
447 List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);
\r
448 if (servicebindings == null || servicebindings.isEmpty()) {
\r
449 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");
\r
452 // Filter the list for current user rights
\r
453 servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);
\r
455 ArrayList<String> docTypes = new ArrayList<String>();
\r
457 String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings,
\r
458 queriedServiceBindings, authRefFieldsByService);
\r
459 if (query == null) { // found no authRef fields - nothing to query
\r
462 // Additional qualifications, like workflow state
\r
463 if (Tools.notBlank(whereClauseAdditions)) {
\r
464 query += " AND " + whereClauseAdditions;
\r
466 // Now we have to issue the search
\r
467 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
468 DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,
\r
469 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);
\r
470 // Now we gather the info for each document into the list and return
\r
471 DocumentModelList docList = docListWrapper.getWrappedObject();
\r
474 private static final boolean READY_FOR_COMPLEX_QUERY = true;
\r
476 private static String computeWhereClauseForAuthorityRefDocs(
\r
478 String refPropName,
\r
479 ArrayList<String> docTypes,
\r
480 List<ServiceBindingType> servicebindings,
\r
481 Map<String, ServiceBindingType> queriedServiceBindings,
\r
482 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {
\r
484 boolean fFirst = true;
\r
485 List<String> authRefFieldPaths;
\r
486 for (ServiceBindingType sb : servicebindings) {
\r
487 // Gets the property names for each part, qualified with the part label (which
\r
488 // is also the table name, the way that the repository works).
\r
489 authRefFieldPaths =
\r
490 ServiceBindingUtils.getAllPartsPropertyValues(sb,
\r
491 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
492 if (authRefFieldPaths.isEmpty()) {
\r
495 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();
\r
496 for (String spec : authRefFieldPaths) {
\r
497 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
498 authRefsInfo.add(arci);
\r
501 String docType = sb.getObject().getName();
\r
502 queriedServiceBindings.put(docType, sb);
\r
503 authRefFieldsByService.put(docType, authRefsInfo);
\r
504 docTypes.add(docType);
\r
507 if (fFirst) { // found no authRef fields - nothing to query
\r
510 // We used to build a complete matches query, but that was too complex.
\r
511 // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID
\r
512 // Note that this will also match the Item itself, but that will get filtered out when
\r
513 // we compute actual matches.
\r
514 AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);
\r
516 String keywords = RefNameUtils.URN_PREFIX
\r
517 + " AND " + (authTermInfo.inAuthority.name != null
\r
518 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)
\r
519 + " AND " + (authTermInfo.name != null
\r
520 ? authTermInfo.name : authTermInfo.csid);
\r
522 String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);
\r
524 if (logger.isTraceEnabled()) {
\r
525 logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);
\r
528 return whereClauseStr;
\r
532 * Runs through the list of found docs, processing them. If list is
\r
533 * non-null, then processing means gather the info for items. If list is
\r
534 * null, and newRefName is non-null, then processing means replacing and
\r
535 * updating. If processing/updating, this must be called in teh context of
\r
536 * an open session, and caller must release Session after calling this.
\r
539 private static int processRefObjsDocList(
\r
540 DocumentModelList docList,
\r
542 Map<String, ServiceBindingType> queriedServiceBindings,
\r
543 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
544 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
545 String newAuthorityRefName) {
\r
546 Iterator<DocumentModel> iter = docList.iterator();
\r
547 int nRefsFoundTotal = 0;
\r
548 while (iter.hasNext()) {
\r
549 DocumentModel docModel = iter.next();
\r
550 AuthorityRefDocList.AuthorityRefDocItem ilistItem;
\r
552 String docType = docModel.getDocumentType().getName();
\r
553 docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);
\r
554 ServiceBindingType sb = queriedServiceBindings.get(docType);
\r
556 throw new RuntimeException(
\r
557 "getAuthorityRefDocs: No Service Binding for docType: " + docType);
\r
559 String serviceContextPath = "/" + sb.getName().toLowerCase() + "/";
\r
561 if (list == null) { // no list - should be update refName case.
\r
562 if (newAuthorityRefName == null) {
\r
563 throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");
\r
566 } else { // Have a list - refObjs case
\r
567 if (newAuthorityRefName != null) {
\r
568 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");
\r
570 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
571 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
\r
572 ilistItem.setDocId(csid);
\r
573 ilistItem.setUri(serviceContextPath + csid);
\r
575 ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());
\r
576 ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));
\r
577 } catch (Exception e) {
\r
578 logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());
\r
580 // The id and URI are the same on all doctypes
\r
581 ilistItem.setDocType(docType);
\r
582 ilistItem.setDocNumber(
\r
583 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));
\r
584 ilistItem.setDocName(
\r
585 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));
\r
587 // Now, we have to loop over the authRefFieldsByService to figure
\r
588 // out which field(s) matched this.
\r
589 List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);
\r
590 if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {
\r
591 throw new RuntimeException(
\r
592 "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");
\r
594 //String authRefAncestorField = "";
\r
595 //String authRefDescendantField = "";
\r
596 //String sourceField = "";
\r
597 int nRefsFoundInDoc = 0;
\r
599 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
\r
601 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, foundProps);
\r
602 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {
\r
603 if (ilistItem != null) {
\r
604 if (nRefsFoundInDoc == 0) { // First one?
\r
605 ilistItem.setSourceField(ari.getQualifiedDisplayName());
\r
606 } else { // duplicates from one object
\r
607 ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());
\r
609 list.add(ilistItem);
\r
610 } else { // update refName case
\r
611 Property propToUpdate = ari.getProperty();
\r
612 propToUpdate.setValue(newAuthorityRefName);
\r
616 } catch (ClientException ce) {
\r
617 throw new RuntimeException(
\r
618 "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());
\r
620 if (nRefsFoundInDoc == 0) {
\r
622 "getAuthorityRefDocs: Result: "
\r
623 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
624 + "] does not reference ["
\r
627 nRefsFoundTotal += nRefsFoundInDoc;
\r
629 return nRefsFoundTotal;
\r
632 private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(
\r
633 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {
\r
634 AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
635 newlistItem.setDocId(ilistItem.getDocId());
\r
636 newlistItem.setDocName(ilistItem.getDocName());
\r
637 newlistItem.setDocNumber(ilistItem.getDocNumber());
\r
638 newlistItem.setDocType(ilistItem.getDocType());
\r
639 newlistItem.setUri(ilistItem.getUri());
\r
640 newlistItem.setSourceField(sourceField);
\r
641 return newlistItem;
\r
644 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
645 DocumentModel docModel,
\r
646 List<AuthRefConfigInfo> authRefFieldInfo,
\r
647 String refNameToMatch,
\r
648 List<AuthRefInfo> foundProps) {
\r
649 // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)
\r
650 // and the values are elPaths to the field, where intervening group structures in
\r
651 // lists of complex structures are replaced with "*". Thus, valid paths include
\r
652 // the following (note that the ServiceBindingUtils prepend schema names to configured values):
\r
653 // "schemaname:fieldname"
\r
654 // "schemaname:scalarlistname"
\r
655 // "schemaname:complexfieldname/fieldname"
\r
656 // "schemaname:complexlistname/*/fieldname"
\r
657 // "schemaname:complexlistname/*/scalarlistname"
\r
658 // "schemaname:complexlistname/*/complexfieldname/fieldname"
\r
659 // "schemaname:complexlistname/*/complexlistname/*/fieldname"
\r
661 for (AuthRefConfigInfo arci : authRefFieldInfo) {
\r
663 // Get first property and work down as needed.
\r
664 Property prop = docModel.getProperty(arci.pathEls[0]);
\r
665 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch);
\r
666 } catch (Exception e) {
\r
667 logger.error("Problem fetching property: " + arci.pathEls[0]);
\r
673 public static List<AuthRefInfo> findAuthRefPropertiesInProperty(
\r
674 List<AuthRefInfo> foundProps,
\r
676 AuthRefConfigInfo arci,
\r
677 int pathStartIndex, // Supports recursion and we work down the path
\r
678 String refNameToMatch) {
\r
679 if (pathStartIndex >= arci.pathEls.length) {
\r
680 throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "
\r
681 + arci.pathEls.toString());
\r
683 AuthRefInfo ari = null;
\r
684 if (prop == null) {
\r
688 if (prop instanceof StringProperty) { // scalar string
\r
689 addARIifMatches(refNameToMatch, arci, prop, foundProps);
\r
690 } else if (prop instanceof List) {
\r
691 List<Property> propList = (List<Property>) prop;
\r
692 // run through list. Must either be list of Strings, or Complex
\r
693 for (Property listItemProp : propList) {
\r
694 if (listItemProp instanceof StringProperty) {
\r
695 if (arci.pathEls.length - pathStartIndex != 1) {
\r
696 logger.error("Configuration for authRefs does not match schema structure: "
\r
697 + arci.pathEls.toString());
\r
700 addARIifMatches(refNameToMatch, arci, listItemProp, foundProps);
\r
702 } else if (listItemProp.isComplex()) {
\r
703 // Just recurse to handle this. Note that since this is a list of complex,
\r
704 // which should look like listName/*/... we add 2 to the path start index
\r
705 findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,
\r
706 pathStartIndex + 2, refNameToMatch);
\r
708 logger.error("Configuration for authRefs does not match schema structure: "
\r
709 + arci.pathEls.toString());
\r
713 } else if (prop.isComplex()) {
\r
714 String localPropName = arci.pathEls[pathStartIndex];
\r
716 Property localProp = prop.get(localPropName);
\r
717 // Now just recurse, pushing down the path 1 step
\r
718 findAuthRefPropertiesInProperty(foundProps, localProp, arci,
\r
719 pathStartIndex, refNameToMatch);
\r
720 } catch (PropertyNotFoundException pnfe) {
\r
721 logger.error("Could not find property: [" + localPropName + "] in path: "
\r
722 + arci.getFullPath());
\r
723 // Fall through - ari will be null and we will continue...
\r
726 logger.error("Configuration for authRefs does not match schema structure: "
\r
727 + arci.pathEls.toString());
\r
731 foundProps.add(ari); //FIXME: REM - This is dead code. 'ari' is never touched after being initalized to null. Why?
\r
737 private static void addARIifMatches(
\r
738 String refNameToMatch,
\r
739 AuthRefConfigInfo arci,
\r
741 List<AuthRefInfo> foundProps) {
\r
742 // Need to either match a passed refName
\r
743 // OR have no refName to match but be non-empty
\r
745 String value = (String) prop.getValue();
\r
746 if (((refNameToMatch != null) && refNameToMatch.equals(value))
\r
747 || ((refNameToMatch == null) && Tools.notBlank(value))) {
\r
749 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");
\r
750 AuthRefInfo ari = new AuthRefInfo(arci, prop);
\r
751 foundProps.add(ari);
\r
753 } catch (PropertyException pe) {
\r
754 logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());
\r
759 * Identifies whether the refName was found in the supplied field. If passed
\r
760 * a new RefName, will set that into fields in which the old one was found.
\r
762 * Only works for: * Scalar fields * Repeatable scalar fields (aka
\r
763 * multi-valued fields)
\r
765 * Does not work for: * Structured fields (complexTypes) * Repeatable
\r
766 * structured fields (repeatable complexTypes) private static int
\r
767 * refNameFoundInField(String oldRefName, Property fieldValue, String
\r
768 * newRefName) { int nFound = 0; if (fieldValue instanceof List) {
\r
769 * List<Property> fieldValueList = (List) fieldValue; for (Property
\r
770 * listItemValue : fieldValueList) { try { if ((listItemValue instanceof
\r
771 * StringProperty) &&
\r
772 * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {
\r
773 * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else
\r
774 * { // We cannot quit after the first, if we are replacing values. // If we
\r
775 * are just looking (not replacing), finding one is enough. break; } } }
\r
776 * catch( PropertyException pe ) {} } } else { try { if ((fieldValue
\r
777 * instanceof StringProperty) &&
\r
778 * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;
\r
779 * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(
\r
780 * PropertyException pe ) {} } return nFound; }
\r