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 = list.subList(startIndex, endIndex);
\r
326 wrapperList.getAuthorityRefDocItem().clear();
\r
327 wrapperList.getAuthorityRefDocItem().addAll(currentPageList);
\r
328 commonList.setItemsInPage(currentPageList.size());
\r
330 if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {
\r
331 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.
\r
333 } catch (Exception e) {
\r
334 logger.error("Could not retrieve a list of documents referring to the specified authority item", e);
\r
335 wrapperList = null;
\r
338 return wrapperList;
\r
341 private static ArrayList<String> getRefNameServiceTypes() {
\r
342 if (refNameServiceTypes == null) {
\r
343 refNameServiceTypes = new ArrayList<String>();
\r
344 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);
\r
345 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);
\r
346 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);
\r
348 return refNameServiceTypes;
\r
350 // Seems like a good value - no real data to set this well.
\r
351 // Note: can set this value lower; e.g. to 3 during debugging; - ADR 2012-07-10
\r
352 private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;
\r
354 public static int updateAuthorityRefDocs(
\r
355 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
356 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
357 RepositoryInstance repoSession,
\r
360 String refPropName) throws Exception {
\r
361 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
362 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
364 int docsScanned = 0;
\r
365 int nRefsFound = 0;
\r
366 int currentPage = 0;
\r
367 int docsInCurrentPage = 0;
\r
368 final String WHERE_CLAUSE_ADDITIONS_VALUE = null;
\r
369 final String ORDER_BY_VALUE = "collectionspace_core:createdAt";
\r
371 if (!(repoClient instanceof RepositoryJavaClientImpl)) {
\r
372 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");
\r
374 try { // REM - How can we deal with transaction and timeout issues here.
\r
375 final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;
\r
376 DocumentModelList docList;
\r
377 boolean morePages = true;
\r
378 while (morePages) {
\r
380 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
381 getRefNameServiceTypes(), oldRefName, refPropName,
\r
382 queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);
\r
384 if (docList == null) {
\r
385 logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");
\r
388 docsInCurrentPage = docList.size();
\r
389 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);
\r
390 if (docsInCurrentPage == 0) {
\r
391 logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");
\r
394 if (docsInCurrentPage < pageSize) {
\r
395 logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");
\r
399 int nRefsFoundThisPage = processRefObjsDocList(docList, oldRefName, queriedServiceBindings, authRefFieldsByService,
\r
401 if (nRefsFoundThisPage > 0) {
\r
402 ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true);
\r
403 nRefsFound += nRefsFoundThisPage;
\r
406 // FIXME: Per REM, set a limit of num objects - something like
\r
407 // 1000K objects - and also add a log Warning after some threshold
\r
408 docsScanned += docsInCurrentPage;
\r
414 } catch (Exception e) {
\r
415 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());
\r
416 logger.debug(Tools.errorToString(e, true));
\r
419 logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");
\r
423 private static DocumentModelList findAuthorityRefDocs(
\r
424 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
425 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
426 RepositoryInstance repoSession, List<String> serviceTypes,
\r
428 String refPropName,
\r
429 Map<String, ServiceBindingType> queriedServiceBindings,
\r
430 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
431 String whereClauseAdditions,
\r
432 String orderByClause,
\r
435 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
437 // Get the service bindings for this tenant
\r
438 TenantBindingConfigReaderImpl tReader =
\r
439 ServiceMain.getInstance().getTenantBindingConfigReader();
\r
440 // We need to get all the procedures, authorities, and objects.
\r
441 List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);
\r
442 if (servicebindings == null || servicebindings.isEmpty()) {
\r
443 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");
\r
446 // Filter the list for current user rights
\r
447 servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);
\r
449 ArrayList<String> docTypes = new ArrayList<String>();
\r
451 String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings,
\r
452 queriedServiceBindings, authRefFieldsByService);
\r
453 if (query == null) { // found no authRef fields - nothing to query
\r
456 // Additional qualifications, like workflow state
\r
457 if (Tools.notBlank(whereClauseAdditions)) {
\r
458 query += " AND " + whereClauseAdditions;
\r
460 // Now we have to issue the search
\r
461 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
462 DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,
\r
463 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);
\r
464 // Now we gather the info for each document into the list and return
\r
465 DocumentModelList docList = docListWrapper.getWrappedObject();
\r
468 private static final boolean READY_FOR_COMPLEX_QUERY = true;
\r
470 private static String computeWhereClauseForAuthorityRefDocs(
\r
472 String refPropName,
\r
473 ArrayList<String> docTypes,
\r
474 List<ServiceBindingType> servicebindings,
\r
475 Map<String, ServiceBindingType> queriedServiceBindings,
\r
476 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {
\r
478 boolean fFirst = true;
\r
479 List<String> authRefFieldPaths;
\r
480 for (ServiceBindingType sb : servicebindings) {
\r
481 // Gets the property names for each part, qualified with the part label (which
\r
482 // is also the table name, the way that the repository works).
\r
483 authRefFieldPaths =
\r
484 ServiceBindingUtils.getAllPartsPropertyValues(sb,
\r
485 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
486 if (authRefFieldPaths.isEmpty()) {
\r
489 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();
\r
490 for (String spec : authRefFieldPaths) {
\r
491 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
492 authRefsInfo.add(arci);
\r
495 String docType = sb.getObject().getName();
\r
496 queriedServiceBindings.put(docType, sb);
\r
497 authRefFieldsByService.put(docType, authRefsInfo);
\r
498 docTypes.add(docType);
\r
501 if (fFirst) { // found no authRef fields - nothing to query
\r
504 // We used to build a complete matches query, but that was too complex.
\r
505 // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID
\r
506 // Note that this will also match the Item itself, but that will get filtered out when
\r
507 // we compute actual matches.
\r
508 AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);
\r
510 String keywords = RefNameUtils.URN_PREFIX
\r
511 + " AND " + (authTermInfo.inAuthority.name != null
\r
512 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)
\r
513 + " AND " + (authTermInfo.name != null
\r
514 ? authTermInfo.name : authTermInfo.csid);
\r
516 String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);
\r
518 if (logger.isTraceEnabled()) {
\r
519 logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);
\r
522 return whereClauseStr;
\r
526 * Runs through the list of found docs, processing them. If list is
\r
527 * non-null, then processing means gather the info for items. If list is
\r
528 * null, and newRefName is non-null, then processing means replacing and
\r
529 * updating. If processing/updating, this must be called in teh context of
\r
530 * an open session, and caller must release Session after calling this.
\r
533 private static int processRefObjsDocList(
\r
534 DocumentModelList docList,
\r
536 Map<String, ServiceBindingType> queriedServiceBindings,
\r
537 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
538 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
539 String newAuthorityRefName) {
\r
540 Iterator<DocumentModel> iter = docList.iterator();
\r
541 int nRefsFoundTotal = 0;
\r
542 while (iter.hasNext()) {
\r
543 DocumentModel docModel = iter.next();
\r
544 AuthorityRefDocList.AuthorityRefDocItem ilistItem;
\r
546 String docType = docModel.getDocumentType().getName();
\r
547 docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);
\r
548 ServiceBindingType sb = queriedServiceBindings.get(docType);
\r
550 throw new RuntimeException(
\r
551 "getAuthorityRefDocs: No Service Binding for docType: " + docType);
\r
553 String serviceContextPath = "/" + sb.getName().toLowerCase() + "/";
\r
555 if (list == null) { // no list - should be update refName case.
\r
556 if (newAuthorityRefName == null) {
\r
557 throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");
\r
560 } else { // Have a list - refObjs case
\r
561 if (newAuthorityRefName != null) {
\r
562 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");
\r
564 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
565 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
\r
566 ilistItem.setDocId(csid);
\r
567 ilistItem.setUri(serviceContextPath + csid);
\r
569 ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());
\r
570 ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));
\r
571 } catch (Exception e) {
\r
572 logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());
\r
574 // The id and URI are the same on all doctypes
\r
575 ilistItem.setDocType(docType);
\r
576 ilistItem.setDocNumber(
\r
577 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));
\r
578 ilistItem.setDocName(
\r
579 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));
\r
581 // Now, we have to loop over the authRefFieldsByService to figure
\r
582 // out which field(s) matched this.
\r
583 List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);
\r
584 if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {
\r
585 throw new RuntimeException(
\r
586 "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");
\r
588 //String authRefAncestorField = "";
\r
589 //String authRefDescendantField = "";
\r
590 //String sourceField = "";
\r
591 int nRefsFoundInDoc = 0;
\r
593 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
\r
595 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, foundProps);
\r
596 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {
\r
597 if (ilistItem != null) {
\r
598 if (nRefsFoundInDoc == 0) { // First one?
\r
599 ilistItem.setSourceField(ari.getQualifiedDisplayName());
\r
600 } else { // duplicates from one object
\r
601 ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());
\r
603 list.add(ilistItem);
\r
604 } else { // update refName case
\r
605 Property propToUpdate = ari.getProperty();
\r
606 propToUpdate.setValue(newAuthorityRefName);
\r
610 } catch (ClientException ce) {
\r
611 throw new RuntimeException(
\r
612 "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());
\r
614 if (nRefsFoundInDoc == 0) {
\r
616 "getAuthorityRefDocs: Result: "
\r
617 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
618 + "] does not reference ["
\r
621 nRefsFoundTotal += nRefsFoundInDoc;
\r
623 return nRefsFoundTotal;
\r
626 private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(
\r
627 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {
\r
628 AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
629 newlistItem.setDocId(ilistItem.getDocId());
\r
630 newlistItem.setDocName(ilistItem.getDocName());
\r
631 newlistItem.setDocNumber(ilistItem.getDocNumber());
\r
632 newlistItem.setDocType(ilistItem.getDocType());
\r
633 newlistItem.setUri(ilistItem.getUri());
\r
634 newlistItem.setSourceField(sourceField);
\r
635 return newlistItem;
\r
638 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
639 DocumentModel docModel,
\r
640 List<AuthRefConfigInfo> authRefFieldInfo,
\r
641 String refNameToMatch,
\r
642 List<AuthRefInfo> foundProps) {
\r
643 // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)
\r
644 // and the values are elPaths to the field, where intervening group structures in
\r
645 // lists of complex structures are replaced with "*". Thus, valid paths include
\r
646 // the following (note that the ServiceBindingUtils prepend schema names to configured values):
\r
647 // "schemaname:fieldname"
\r
648 // "schemaname:scalarlistname"
\r
649 // "schemaname:complexfieldname/fieldname"
\r
650 // "schemaname:complexlistname/*/fieldname"
\r
651 // "schemaname:complexlistname/*/scalarlistname"
\r
652 // "schemaname:complexlistname/*/complexfieldname/fieldname"
\r
653 // "schemaname:complexlistname/*/complexlistname/*/fieldname"
\r
655 for (AuthRefConfigInfo arci : authRefFieldInfo) {
\r
657 // Get first property and work down as needed.
\r
658 Property prop = docModel.getProperty(arci.pathEls[0]);
\r
659 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch);
\r
660 } catch (Exception e) {
\r
661 logger.error("Problem fetching property: " + arci.pathEls[0]);
\r
667 public static List<AuthRefInfo> findAuthRefPropertiesInProperty(
\r
668 List<AuthRefInfo> foundProps,
\r
670 AuthRefConfigInfo arci,
\r
671 int pathStartIndex, // Supports recursion and we work down the path
\r
672 String refNameToMatch) {
\r
673 if (pathStartIndex >= arci.pathEls.length) {
\r
674 throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "
\r
675 + arci.pathEls.toString());
\r
677 AuthRefInfo ari = null;
\r
678 if (prop == null) {
\r
682 if (prop instanceof StringProperty) { // scalar string
\r
683 addARIifMatches(refNameToMatch, arci, prop, foundProps);
\r
684 } else if (prop instanceof List) {
\r
685 List<Property> propList = (List<Property>) prop;
\r
686 // run through list. Must either be list of Strings, or Complex
\r
687 for (Property listItemProp : propList) {
\r
688 if (listItemProp instanceof StringProperty) {
\r
689 if (arci.pathEls.length - pathStartIndex != 1) {
\r
690 logger.error("Configuration for authRefs does not match schema structure: "
\r
691 + arci.pathEls.toString());
\r
694 addARIifMatches(refNameToMatch, arci, listItemProp, foundProps);
\r
696 } else if (listItemProp.isComplex()) {
\r
697 // Just recurse to handle this. Note that since this is a list of complex,
\r
698 // which should look like listName/*/... we add 2 to the path start index
\r
699 findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,
\r
700 pathStartIndex + 2, refNameToMatch);
\r
702 logger.error("Configuration for authRefs does not match schema structure: "
\r
703 + arci.pathEls.toString());
\r
707 } else if (prop.isComplex()) {
\r
708 String localPropName = arci.pathEls[pathStartIndex];
\r
710 Property localProp = prop.get(localPropName);
\r
711 // Now just recurse, pushing down the path 1 step
\r
712 findAuthRefPropertiesInProperty(foundProps, localProp, arci,
\r
713 pathStartIndex, refNameToMatch);
\r
714 } catch (PropertyNotFoundException pnfe) {
\r
715 logger.error("Could not find property: [" + localPropName + "] in path: "
\r
716 + arci.getFullPath());
\r
717 // Fall through - ari will be null and we will continue...
\r
720 logger.error("Configuration for authRefs does not match schema structure: "
\r
721 + arci.pathEls.toString());
\r
725 foundProps.add(ari); //FIXME: REM - This is dead code. 'ari' is never touched after being initalized to null. Why?
\r
731 private static void addARIifMatches(
\r
732 String refNameToMatch,
\r
733 AuthRefConfigInfo arci,
\r
735 List<AuthRefInfo> foundProps) {
\r
736 // Need to either match a passed refName
\r
737 // OR have no refName to match but be non-empty
\r
739 String value = (String) prop.getValue();
\r
740 if (((refNameToMatch != null) && refNameToMatch.equals(value))
\r
741 || ((refNameToMatch == null) && Tools.notBlank(value))) {
\r
743 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");
\r
744 AuthRefInfo ari = new AuthRefInfo(arci, prop);
\r
745 foundProps.add(ari);
\r
747 } catch (PropertyException pe) {
\r
748 logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());
\r
753 * Identifies whether the refName was found in the supplied field. If passed
\r
754 * a new RefName, will set that into fields in which the old one was found.
\r
756 * Only works for: * Scalar fields * Repeatable scalar fields (aka
\r
757 * multi-valued fields)
\r
759 * Does not work for: * Structured fields (complexTypes) * Repeatable
\r
760 * structured fields (repeatable complexTypes) private static int
\r
761 * refNameFoundInField(String oldRefName, Property fieldValue, String
\r
762 * newRefName) { int nFound = 0; if (fieldValue instanceof List) {
\r
763 * List<Property> fieldValueList = (List) fieldValue; for (Property
\r
764 * listItemValue : fieldValueList) { try { if ((listItemValue instanceof
\r
765 * StringProperty) &&
\r
766 * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {
\r
767 * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else
\r
768 * { // We cannot quit after the first, if we are replacing values. // If we
\r
769 * are just looking (not replacing), finding one is enough. break; } } }
\r
770 * catch( PropertyException pe ) {} } } else { try { if ((fieldValue
\r
771 * instanceof StringProperty) &&
\r
772 * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;
\r
773 * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(
\r
774 * PropertyException pe ) {} } return nFound; }
\r