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 DocumentModelList docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
244 serviceTypes, refName, refPropName, queriedServiceBindings, authRefFieldsByService,
\r
245 filter.getWhereClause(), null, pageSize, pageNum, computeTotal);
\r
247 if (docList == null) { // found no authRef fields - nothing to process
\r
248 return wrapperList;
\r
251 // set the fieldsReturned list. Even though this is a fixed schema, app layer treats
\r
252 // this like other abstract common lists
\r
254 * <xs:element name="docType" type="xs:string" minOccurs="1" />
\r
255 * <xs:element name="docId" type="xs:string" minOccurs="1" />
\r
256 * <xs:element name="docNumber" type="xs:string" minOccurs="0" />
\r
257 * <xs:element name="docName" type="xs:string" minOccurs="0" />
\r
258 * <xs:element name="sourceField" type="xs:string" minOccurs="1" />
\r
259 * <xs:element name="uri" type="xs:anyURI" minOccurs="1" />
\r
260 * <xs:element name="updatedAt" type="xs:string" minOccurs="1" />
\r
261 * <xs:element name="workflowState" type="xs:string" minOccurs="1"
\r
264 String fieldList = "docType|docId|docNumber|docName|sourceField|uri|updatedAt|workflowState";
\r
265 commonList.setFieldsReturned(fieldList);
\r
267 // As a side-effect, the method called below modifies the value of
\r
268 // the 'list' variable, which holds the list of references to
\r
269 // an authority item.
\r
271 // There can be more than one reference to a particular authority
\r
272 // item within any individual document scanned, so the number of
\r
273 // authority references may potentially exceed the total number
\r
274 // of documents scanned.
\r
275 int nRefsFound = processRefObjsDocList(docList, refName, queriedServiceBindings, authRefFieldsByService, // the actual list size needs to be updated to the size of "list"
\r
278 commonList.setPageSize(pageSize);
\r
280 // Values returned in the pagination block above the list items
\r
281 // need to reflect the number of references to authority items
\r
282 // returned, rather than the number of documents originally scanned
\r
283 // to find such references.
\r
284 commonList.setPageNum(pageNum);
\r
285 commonList.setTotalItems(list.size());
\r
287 // Slice the list to return only the specified page of items
\r
288 // in the list results.
\r
290 // FIXME: There may well be a pattern-based way to do this in our framework.
\r
291 int startIndex = 0;
\r
293 // Return all results if pageSize is 0
\r
294 if (pageSize == 0) {
\r
296 endIndex = list.size();
\r
298 startIndex = pageNum * pageSize;
\r
299 int pageEndIndex = ((startIndex + pageSize) - 1);
\r
300 endIndex = (pageEndIndex > list.size()) ? list.size() : pageEndIndex;
\r
302 // Adjust for the second argument to List.subList() being exclusive
\r
303 // of the last item in the slice
\r
305 list = new ArrayList<AuthorityRefDocList.AuthorityRefDocItem>
\r
306 (list.subList(startIndex, endIndex));
\r
307 commonList.setItemsInPage(list.size());
\r
309 if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {
\r
310 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.
\r
312 } catch (Exception e) {
\r
313 logger.error("Could not retrieve the Nuxeo repository", e);
\r
314 wrapperList = null;
\r
317 return wrapperList;
\r
320 private static ArrayList<String> getRefNameServiceTypes() {
\r
321 if (refNameServiceTypes == null) {
\r
322 refNameServiceTypes = new ArrayList<String>();
\r
323 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);
\r
324 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);
\r
325 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);
\r
327 return refNameServiceTypes;
\r
329 // Seems like a good value - no real data to set this well.
\r
330 // Note: can set this value lower; e.g. to 3 during debugging; - ADR 2012-07-10
\r
331 private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;
\r
333 public static int updateAuthorityRefDocs(
\r
334 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
335 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
336 RepositoryInstance repoSession,
\r
339 String refPropName) throws Exception {
\r
340 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
341 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
343 int docsScanned = 0;
\r
344 int nRefsFound = 0;
\r
345 int currentPage = 0;
\r
346 int docsInCurrentPage = 0;
\r
347 final String WHERE_CLAUSE_ADDITIONS_VALUE = null;
\r
348 final String ORDER_BY_VALUE = "collectionspace_core:createdAt";
\r
350 if (!(repoClient instanceof RepositoryJavaClientImpl)) {
\r
351 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");
\r
353 try { // REM - How can we deal with transaction and timeout issues here.
\r
354 final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;
\r
355 DocumentModelList docList;
\r
356 boolean morePages = true;
\r
357 while (morePages) {
\r
359 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
360 getRefNameServiceTypes(), oldRefName, refPropName,
\r
361 queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);
\r
363 if (docList == null) {
\r
364 logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");
\r
367 docsInCurrentPage = docList.size();
\r
368 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);
\r
369 if (docsInCurrentPage == 0) {
\r
370 logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");
\r
373 if (docsInCurrentPage < pageSize) {
\r
374 logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");
\r
378 int nRefsFoundThisPage = processRefObjsDocList(docList, oldRefName, queriedServiceBindings, authRefFieldsByService,
\r
380 if (nRefsFoundThisPage > 0) {
\r
381 ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true);
\r
382 nRefsFound += nRefsFoundThisPage;
\r
385 // FIXME: Per REM, set a limit of num objects - something like
\r
386 // 1000K objects - and also add a log Warning after some threshold
\r
387 docsScanned += docsInCurrentPage;
\r
393 } catch (Exception e) {
\r
394 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());
\r
395 logger.debug(Tools.errorToString(e, true));
\r
398 logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");
\r
402 private static DocumentModelList findAuthorityRefDocs(
\r
403 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
404 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
405 RepositoryInstance repoSession, List<String> serviceTypes,
\r
407 String refPropName,
\r
408 Map<String, ServiceBindingType> queriedServiceBindings,
\r
409 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
410 String whereClauseAdditions,
\r
411 String orderByClause,
\r
414 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
416 // Get the service bindings for this tenant
\r
417 TenantBindingConfigReaderImpl tReader =
\r
418 ServiceMain.getInstance().getTenantBindingConfigReader();
\r
419 // We need to get all the procedures, authorities, and objects.
\r
420 List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);
\r
421 if (servicebindings == null || servicebindings.isEmpty()) {
\r
422 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");
\r
425 // Filter the list for current user rights
\r
426 servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);
\r
428 ArrayList<String> docTypes = new ArrayList<String>();
\r
430 String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings,
\r
431 queriedServiceBindings, authRefFieldsByService);
\r
432 if (query == null) { // found no authRef fields - nothing to query
\r
435 // Additional qualifications, like workflow state
\r
436 if (Tools.notBlank(whereClauseAdditions)) {
\r
437 query += " AND " + whereClauseAdditions;
\r
439 // Now we have to issue the search
\r
440 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
441 DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,
\r
442 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);
\r
443 // Now we gather the info for each document into the list and return
\r
444 DocumentModelList docList = docListWrapper.getWrappedObject();
\r
447 private static final boolean READY_FOR_COMPLEX_QUERY = true;
\r
449 private static String computeWhereClauseForAuthorityRefDocs(
\r
451 String refPropName,
\r
452 ArrayList<String> docTypes,
\r
453 List<ServiceBindingType> servicebindings,
\r
454 Map<String, ServiceBindingType> queriedServiceBindings,
\r
455 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {
\r
457 boolean fFirst = true;
\r
458 List<String> authRefFieldPaths;
\r
459 for (ServiceBindingType sb : servicebindings) {
\r
460 // Gets the property names for each part, qualified with the part label (which
\r
461 // is also the table name, the way that the repository works).
\r
462 authRefFieldPaths =
\r
463 ServiceBindingUtils.getAllPartsPropertyValues(sb,
\r
464 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
465 if (authRefFieldPaths.isEmpty()) {
\r
468 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();
\r
469 for (String spec : authRefFieldPaths) {
\r
470 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
471 authRefsInfo.add(arci);
\r
474 String docType = sb.getObject().getName();
\r
475 queriedServiceBindings.put(docType, sb);
\r
476 authRefFieldsByService.put(docType, authRefsInfo);
\r
477 docTypes.add(docType);
\r
480 if (fFirst) { // found no authRef fields - nothing to query
\r
483 // We used to build a complete matches query, but that was too complex.
\r
484 // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID
\r
485 // Note that this will also match the Item itself, but that will get filtered out when
\r
486 // we compute actual matches.
\r
487 AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);
\r
489 String keywords = RefNameUtils.URN_PREFIX
\r
490 + " AND " + (authTermInfo.inAuthority.name != null
\r
491 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)
\r
492 + " AND " + (authTermInfo.name != null
\r
493 ? authTermInfo.name : authTermInfo.csid);
\r
495 String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);
\r
497 if (logger.isTraceEnabled()) {
\r
498 logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);
\r
501 return whereClauseStr;
\r
505 * Runs through the list of found docs, processing them. If list is
\r
506 * non-null, then processing means gather the info for items. If list is
\r
507 * null, and newRefName is non-null, then processing means replacing and
\r
508 * updating. If processing/updating, this must be called in teh context of
\r
509 * an open session, and caller must release Session after calling this.
\r
512 private static int processRefObjsDocList(
\r
513 DocumentModelList docList,
\r
515 Map<String, ServiceBindingType> queriedServiceBindings,
\r
516 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
517 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
518 String newAuthorityRefName) {
\r
519 Iterator<DocumentModel> iter = docList.iterator();
\r
520 int nRefsFoundTotal = 0;
\r
521 while (iter.hasNext()) {
\r
522 DocumentModel docModel = iter.next();
\r
523 AuthorityRefDocList.AuthorityRefDocItem ilistItem;
\r
525 String docType = docModel.getDocumentType().getName();
\r
526 docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);
\r
527 ServiceBindingType sb = queriedServiceBindings.get(docType);
\r
529 throw new RuntimeException(
\r
530 "getAuthorityRefDocs: No Service Binding for docType: " + docType);
\r
532 String serviceContextPath = "/" + sb.getName().toLowerCase() + "/";
\r
534 if (list == null) { // no list - should be update refName case.
\r
535 if (newAuthorityRefName == null) {
\r
536 throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");
\r
539 } else { // Have a list - refObjs case
\r
540 if (newAuthorityRefName != null) {
\r
541 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");
\r
543 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
544 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
\r
545 ilistItem.setDocId(csid);
\r
546 ilistItem.setUri(serviceContextPath + csid);
\r
548 ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());
\r
549 ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));
\r
550 } catch (Exception e) {
\r
551 logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());
\r
553 // The id and URI are the same on all doctypes
\r
554 ilistItem.setDocType(docType);
\r
555 ilistItem.setDocNumber(
\r
556 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));
\r
557 ilistItem.setDocName(
\r
558 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));
\r
560 // Now, we have to loop over the authRefFieldsByService to figure
\r
561 // out which field(s) matched this.
\r
562 List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);
\r
563 if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {
\r
564 throw new RuntimeException(
\r
565 "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");
\r
567 //String authRefAncestorField = "";
\r
568 //String authRefDescendantField = "";
\r
569 //String sourceField = "";
\r
570 int nRefsFoundInDoc = 0;
\r
572 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
\r
574 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, foundProps);
\r
575 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {
\r
576 if (ilistItem != null) {
\r
577 if (nRefsFoundInDoc == 0) { // First one?
\r
578 ilistItem.setSourceField(ari.getQualifiedDisplayName());
\r
579 } else { // duplicates from one object
\r
580 ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());
\r
582 list.add(ilistItem);
\r
583 } else { // update refName case
\r
584 Property propToUpdate = ari.getProperty();
\r
585 propToUpdate.setValue(newAuthorityRefName);
\r
589 } catch (ClientException ce) {
\r
590 throw new RuntimeException(
\r
591 "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());
\r
593 if (nRefsFoundInDoc == 0) {
\r
595 "getAuthorityRefDocs: Result: "
\r
596 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
597 + "] does not reference ["
\r
600 nRefsFoundTotal += nRefsFoundInDoc;
\r
602 return nRefsFoundTotal;
\r
605 private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(
\r
606 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {
\r
607 AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
608 newlistItem.setDocId(ilistItem.getDocId());
\r
609 newlistItem.setDocName(ilistItem.getDocName());
\r
610 newlistItem.setDocNumber(ilistItem.getDocNumber());
\r
611 newlistItem.setDocType(ilistItem.getDocType());
\r
612 newlistItem.setUri(ilistItem.getUri());
\r
613 newlistItem.setSourceField(sourceField);
\r
614 return newlistItem;
\r
617 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
618 DocumentModel docModel,
\r
619 List<AuthRefConfigInfo> authRefFieldInfo,
\r
620 String refNameToMatch,
\r
621 List<AuthRefInfo> foundProps) {
\r
622 // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)
\r
623 // and the values are elPaths to the field, where intervening group structures in
\r
624 // lists of complex structures are replaced with "*". Thus, valid paths include
\r
625 // the following (note that the ServiceBindingUtils prepend schema names to configured values):
\r
626 // "schemaname:fieldname"
\r
627 // "schemaname:scalarlistname"
\r
628 // "schemaname:complexfieldname/fieldname"
\r
629 // "schemaname:complexlistname/*/fieldname"
\r
630 // "schemaname:complexlistname/*/scalarlistname"
\r
631 // "schemaname:complexlistname/*/complexfieldname/fieldname"
\r
632 // "schemaname:complexlistname/*/complexlistname/*/fieldname"
\r
634 for (AuthRefConfigInfo arci : authRefFieldInfo) {
\r
636 // Get first property and work down as needed.
\r
637 Property prop = docModel.getProperty(arci.pathEls[0]);
\r
638 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch);
\r
639 } catch (Exception e) {
\r
640 logger.error("Problem fetching property: " + arci.pathEls[0]);
\r
646 public static List<AuthRefInfo> findAuthRefPropertiesInProperty(
\r
647 List<AuthRefInfo> foundProps,
\r
649 AuthRefConfigInfo arci,
\r
650 int pathStartIndex, // Supports recursion and we work down the path
\r
651 String refNameToMatch) {
\r
652 if (pathStartIndex >= arci.pathEls.length) {
\r
653 throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "
\r
654 + arci.pathEls.toString());
\r
656 AuthRefInfo ari = null;
\r
657 if (prop == null) {
\r
661 if (prop instanceof StringProperty) { // scalar string
\r
662 addARIifMatches(refNameToMatch, arci, prop, foundProps);
\r
663 } else if (prop instanceof List) {
\r
664 List<Property> propList = (List<Property>) prop;
\r
665 // run through list. Must either be list of Strings, or Complex
\r
666 for (Property listItemProp : propList) {
\r
667 if (listItemProp instanceof StringProperty) {
\r
668 if (arci.pathEls.length - pathStartIndex != 1) {
\r
669 logger.error("Configuration for authRefs does not match schema structure: "
\r
670 + arci.pathEls.toString());
\r
673 addARIifMatches(refNameToMatch, arci, listItemProp, foundProps);
\r
675 } else if (listItemProp.isComplex()) {
\r
676 // Just recurse to handle this. Note that since this is a list of complex,
\r
677 // which should look like listName/*/... we add 2 to the path start index
\r
678 findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,
\r
679 pathStartIndex + 2, refNameToMatch);
\r
681 logger.error("Configuration for authRefs does not match schema structure: "
\r
682 + arci.pathEls.toString());
\r
686 } else if (prop.isComplex()) {
\r
687 String localPropName = arci.pathEls[pathStartIndex];
\r
689 Property localProp = prop.get(localPropName);
\r
690 // Now just recurse, pushing down the path 1 step
\r
691 findAuthRefPropertiesInProperty(foundProps, localProp, arci,
\r
692 pathStartIndex, refNameToMatch);
\r
693 } catch (PropertyNotFoundException pnfe) {
\r
694 logger.error("Could not find property: [" + localPropName + "] in path: "
\r
695 + arci.getFullPath());
\r
696 // Fall through - ari will be null and we will continue...
\r
699 logger.error("Configuration for authRefs does not match schema structure: "
\r
700 + arci.pathEls.toString());
\r
704 foundProps.add(ari); //FIXME: REM - This is dead code. 'ari' is never touched after being initalized to null. Why?
\r
710 private static void addARIifMatches(
\r
711 String refNameToMatch,
\r
712 AuthRefConfigInfo arci,
\r
714 List<AuthRefInfo> foundProps) {
\r
715 // Need to either match a passed refName
\r
716 // OR have no refName to match but be non-empty
\r
718 String value = (String) prop.getValue();
\r
719 if (((refNameToMatch != null) && refNameToMatch.equals(value))
\r
720 || ((refNameToMatch == null) && Tools.notBlank(value))) {
\r
722 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");
\r
723 AuthRefInfo ari = new AuthRefInfo(arci, prop);
\r
724 foundProps.add(ari);
\r
726 } catch (PropertyException pe) {
\r
727 logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());
\r
732 * Identifies whether the refName was found in the supplied field. If passed
\r
733 * a new RefName, will set that into fields in which the old one was found.
\r
735 * Only works for: * Scalar fields * Repeatable scalar fields (aka
\r
736 * multi-valued fields)
\r
738 * Does not work for: * Structured fields (complexTypes) * Repeatable
\r
739 * structured fields (repeatable complexTypes) private static int
\r
740 * refNameFoundInField(String oldRefName, Property fieldValue, String
\r
741 * newRefName) { int nFound = 0; if (fieldValue instanceof List) {
\r
742 * List<Property> fieldValueList = (List) fieldValue; for (Property
\r
743 * listItemValue : fieldValueList) { try { if ((listItemValue instanceof
\r
744 * StringProperty) &&
\r
745 * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {
\r
746 * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else
\r
747 * { // We cannot quit after the first, if we are replacing values. // If we
\r
748 * are just looking (not replacing), finding one is enough. break; } } }
\r
749 * catch( PropertyException pe ) {} } } else { try { if ((fieldValue
\r
750 * instanceof StringProperty) &&
\r
751 * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;
\r
752 * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(
\r
753 * PropertyException pe ) {} } return nFound; }
\r