2 * This document is a part of the source code and related artifacts for
\r
3 * CollectionSpace, an open source collections management system for museums and
\r
4 * related institutions:
\r
6 * http://www.collectionspace.org http://wiki.collectionspace.org
\r
8 * Copyright 2009 University of California at Berkeley
\r
10 * Licensed under the Educational Community License (ECL), Version 2.0. You may
\r
11 * not use this file except in compliance with this License.
\r
13 * You may obtain a copy of the ECL 2.0 License at
\r
15 * https://source.collectionspace.org/collection-space/LICENSE.txt
\r
17 * Unless required by applicable law or agreed to in writing, software
\r
18 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
\r
19 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
\r
20 * License for the specific language governing permissions and limitations under
\r
23 package org.collectionspace.services.common.vocabulary;
\r
25 import java.util.ArrayList;
\r
26 import java.util.HashMap;
\r
27 import java.util.Iterator;
\r
28 import java.util.List;
\r
29 import java.util.Map;
\r
31 import org.nuxeo.ecm.core.api.ClientException;
\r
32 import org.nuxeo.ecm.core.api.DocumentModel;
\r
33 import org.nuxeo.ecm.core.api.DocumentModelList;
\r
34 import org.nuxeo.ecm.core.api.model.Property;
\r
35 import org.nuxeo.ecm.core.api.model.PropertyException;
\r
36 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
\r
37 import org.nuxeo.ecm.core.api.model.impl.primitives.StringProperty;
\r
38 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
\r
39 import org.slf4j.Logger;
\r
40 import org.slf4j.LoggerFactory;
\r
42 import org.collectionspace.services.client.PoxPayloadIn;
\r
43 import org.collectionspace.services.client.PoxPayloadOut;
\r
44 import org.collectionspace.services.common.ServiceMain;
\r
45 import org.collectionspace.services.common.StoredValuesUriTemplate;
\r
46 import org.collectionspace.services.common.UriTemplateFactory;
\r
47 import org.collectionspace.services.common.UriTemplateRegistry;
\r
48 import org.collectionspace.services.common.UriTemplateRegistryKey;
\r
49 import org.collectionspace.services.common.context.ServiceContext;
\r
50 import org.collectionspace.services.common.context.AbstractServiceContextImpl;
\r
51 import org.collectionspace.services.common.api.RefNameUtils;
\r
52 import org.collectionspace.services.common.api.Tools;
\r
53 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
\r
54 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
\r
55 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
\r
56 import org.collectionspace.services.common.context.ServiceBindingUtils;
\r
57 import org.collectionspace.services.common.document.DocumentException;
\r
58 import org.collectionspace.services.common.document.DocumentFilter;
\r
59 import org.collectionspace.services.common.document.DocumentNotFoundException;
\r
60 import org.collectionspace.services.common.document.DocumentUtils;
\r
61 import org.collectionspace.services.common.document.DocumentWrapper;
\r
62 import org.collectionspace.services.common.query.QueryManager;
\r
63 import org.collectionspace.services.common.repository.RepositoryClient;
\r
64 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
\r
65 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
\r
66 import org.collectionspace.services.common.security.SecurityUtils;
\r
67 import org.collectionspace.services.config.service.ServiceBindingType;
\r
68 import org.collectionspace.services.jaxb.AbstractCommonList;
\r
69 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
\r
70 import org.jboss.resteasy.spi.ResteasyProviderFactory;
\r
73 * RefNameServiceUtils is a collection of services utilities related to refName
\r
76 * $LastChangedRevision: $ $LastChangedDate: $
\r
78 public class RefNameServiceUtils {
\r
80 public static class AuthRefConfigInfo {
\r
82 public String getQualifiedDisplayName() {
\r
83 return (Tools.isBlank(schema))
\r
84 ? displayName : DocumentUtils.appendSchemaName(schema, displayName);
\r
87 public String getDisplayName() {
\r
91 public void setDisplayName(String displayName) {
\r
92 this.displayName = displayName;
\r
97 public String getSchema() {
\r
101 public void setSchema(String schema) {
\r
102 this.schema = schema;
\r
105 public String getFullPath() {
\r
109 public void setFullPath(String fullPath) {
\r
110 this.fullPath = fullPath;
\r
113 protected String[] pathEls;
\r
115 public AuthRefConfigInfo(AuthRefConfigInfo arci) {
\r
116 this.displayName = arci.displayName;
\r
117 this.schema = arci.schema;
\r
118 this.fullPath = arci.fullPath;
\r
119 this.pathEls = arci.pathEls;
\r
120 // Skip the pathElse check, since we are creatign from another (presumably valid) arci.
\r
123 public AuthRefConfigInfo(String displayName, String schema, String fullPath, String[] pathEls) {
\r
124 this.displayName = displayName;
\r
125 this.schema = schema;
\r
126 this.fullPath = fullPath;
\r
127 this.pathEls = pathEls;
\r
131 // Split a config value string like "intakes_common:collector", or
\r
132 // "collectionobjects_common:contentPeoples|contentPeople"
\r
133 // "collectionobjects_common:assocEventGroupList/*/assocEventPlace"
\r
134 // If has a pipe ('|') second part is a displayLabel, and first is path
\r
135 // Otherwise, entry is a path, and can use the last pathElement as displayName
\r
136 // Should be schema qualified.
\r
137 public AuthRefConfigInfo(String configString) {
\r
138 String[] pair = configString.split("\\|", 2);
\r
140 String displayName, fullPath;
\r
141 if (pair.length == 1) {
\r
142 // no label specifier, so we'll defer getting label
\r
143 fullPath = pair[0];
\r
144 pathEls = pair[0].split("/");
\r
145 displayName = pathEls[pathEls.length - 1];
\r
147 fullPath = pair[0];
\r
148 pathEls = pair[0].split("/");
\r
149 displayName = pair[1];
\r
151 String[] schemaSplit = pathEls[0].split(":", 2);
\r
153 if (schemaSplit.length == 1) { // schema not specified
\r
156 schema = schemaSplit[0];
\r
157 if (pair.length == 1 && pathEls.length == 1) { // simplest case of field in top level schema, no labelll
\r
158 displayName = schemaSplit[1]; // Have to fix up displayName to have no schema
\r
161 this.displayName = displayName;
\r
162 this.schema = schema;
\r
163 this.fullPath = fullPath;
\r
164 this.pathEls = pathEls;
\r
168 protected void checkPathEls() {
\r
169 int len = pathEls.length;
\r
171 throw new InternalError("Bad values in authRef info - caller screwed up:" + fullPath);
\r
173 // Handle case of them putting a leading slash on the path
\r
174 if (len > 1 && pathEls[0].endsWith(":")) {
\r
176 String[] newArray = new String[len];
\r
177 newArray[0] = pathEls[0] + pathEls[1];
\r
179 System.arraycopy(pathEls, 2, newArray, 1, len - 1);
\r
181 pathEls = newArray;
\r
186 public static class AuthRefInfo extends AuthRefConfigInfo {
\r
188 public Property getProperty() {
\r
192 public void setProperty(Property property) {
\r
193 this.property = property;
\r
197 public AuthRefInfo(String displayName, String schema, String fullPath, String[] pathEls, Property prop) {
\r
198 super(displayName, schema, fullPath, pathEls);
\r
199 this.property = prop;
\r
202 public AuthRefInfo(AuthRefConfigInfo arci, Property prop) {
\r
204 this.property = prop;
\r
207 private static final Logger logger = LoggerFactory.getLogger(RefNameServiceUtils.class);
\r
208 private static ArrayList<String> refNameServiceTypes = null;
\r
210 public static List<AuthRefConfigInfo> getConfiguredAuthorityRefs(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
\r
211 List<String> authRefFields =
\r
212 ((AbstractServiceContextImpl) ctx).getAllPartsPropertyValues(
\r
213 ServiceBindingUtils.AUTH_REF_PROP, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
214 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>(authRefFields.size());
\r
215 for (String spec : authRefFields) {
\r
216 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
217 authRefsInfo.add(arci);
\r
219 return authRefsInfo;
\r
222 public static AuthorityRefDocList getAuthorityRefDocs(
\r
223 RepositoryInstance repoSession,
\r
224 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
225 UriTemplateRegistry uriTemplateRegistry,
\r
226 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
227 List<String> serviceTypes,
\r
229 String refPropName,
\r
230 DocumentFilter filter, boolean computeTotal)
\r
231 throws DocumentException, DocumentNotFoundException {
\r
232 AuthorityRefDocList wrapperList = new AuthorityRefDocList();
\r
233 AbstractCommonList commonList = (AbstractCommonList) wrapperList;
\r
234 int pageNum = filter.getStartPage();
\r
235 int pageSize = filter.getPageSize();
\r
237 List<AuthorityRefDocList.AuthorityRefDocItem> list =
\r
238 wrapperList.getAuthorityRefDocItem();
\r
240 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
241 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
243 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
245 // Ignore any provided page size and number query parameters in
\r
246 // the following call, as they pertain to the list of authority
\r
247 // references to be returned, not to the list of documents to be
\r
248 // scanned for those references.
\r
249 DocumentModelList docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
250 serviceTypes, refName, refPropName, queriedServiceBindings, authRefFieldsByService,
\r
251 filter.getWhereClause(), null, 0 /* pageSize */, 0 /* pageNum */, computeTotal);
\r
253 if (docList == null) { // found no authRef fields - nothing to process
\r
254 return wrapperList;
\r
257 // set the fieldsReturned list. Even though this is a fixed schema, app layer treats
\r
258 // this like other abstract common lists
\r
260 * <xs:element name="docType" type="xs:string" minOccurs="1" />
\r
261 * <xs:element name="docId" type="xs:string" minOccurs="1" />
\r
262 * <xs:element name="docNumber" type="xs:string" minOccurs="0" />
\r
263 * <xs:element name="docName" type="xs:string" minOccurs="0" />
\r
264 * <xs:element name="sourceField" type="xs:string" minOccurs="1" />
\r
265 * <xs:element name="uri" type="xs:anyURI" minOccurs="1" />
\r
266 * <xs:element name="updatedAt" type="xs:string" minOccurs="1" />
\r
267 * <xs:element name="workflowState" type="xs:string" minOccurs="1"
\r
270 String fieldList = "docType|docId|docNumber|docName|sourceField|uri|updatedAt|workflowState";
\r
271 commonList.setFieldsReturned(fieldList);
\r
273 // As a side-effect, the method called below modifies the value of
\r
274 // the 'list' variable, which holds the list of references to
\r
275 // an authority item.
\r
277 // There can be more than one reference to a particular authority
\r
278 // item within any individual document scanned, so the number of
\r
279 // authority references may potentially exceed the total number
\r
280 // of documents scanned.
\r
281 int nRefsFound = processRefObjsDocList(docList, ctx.getTenantId(), refName, queriedServiceBindings, authRefFieldsByService, // the actual list size needs to be updated to the size of "list"
\r
284 commonList.setPageSize(pageSize);
\r
286 // Values returned in the pagination block above the list items
\r
287 // need to reflect the number of references to authority items
\r
288 // returned, rather than the number of documents originally scanned
\r
289 // to find such references.
\r
290 commonList.setPageNum(pageNum);
\r
291 commonList.setTotalItems(list.size());
\r
293 // Slice the list to return only the specified page of items
\r
294 // in the list results.
\r
296 // FIXME: There may well be a pattern-based way to do this
\r
297 // in our framework, and if we can eliminate much of the
\r
298 // non-DRY code below, that would be desirable.
\r
300 int startIndex = 0;
\r
303 // Return all results if pageSize is 0.
\r
304 if (pageSize == 0) {
\r
306 endIndex = list.size();
\r
308 startIndex = pageNum * pageSize;
\r
311 // Return an empty list when the start of the requested page is
\r
312 // beyond the last item in the list.
\r
313 if (startIndex > list.size()) {
\r
314 wrapperList.getAuthorityRefDocItem().clear();
\r
315 commonList.setItemsInPage(wrapperList.getAuthorityRefDocItem().size());
\r
316 return wrapperList;
\r
319 // Otherwise, return a list of items from the start of the specified
\r
320 // page through the last item on that page, or otherwise through the
\r
321 // last item in the entire list, if that occurs earlier than the end
\r
322 // of the specified page.
\r
323 if (endIndex == 0) {
\r
324 int pageEndIndex = ((startIndex + pageSize));
\r
325 endIndex = (pageEndIndex > list.size()) ? list.size() : pageEndIndex;
\r
328 // Slice the list to return only the specified page of results.
\r
329 // Note: the second argument to List.subList(), endIndex, is
\r
330 // exclusive of the item at its index position, reflecting the
\r
331 // zero-index nature of the list.
\r
332 List<AuthorityRefDocList.AuthorityRefDocItem> currentPageList =
\r
333 new ArrayList<AuthorityRefDocList.AuthorityRefDocItem>(list.subList(startIndex, endIndex));
\r
334 wrapperList.getAuthorityRefDocItem().clear();
\r
335 wrapperList.getAuthorityRefDocItem().addAll(currentPageList);
\r
336 commonList.setItemsInPage(currentPageList.size());
\r
338 if (logger.isDebugEnabled() && (nRefsFound < docList.size())) {
\r
339 logger.debug("Internal curiosity: got fewer matches of refs than # docs matched..."); // We found a ref to ourself and have excluded it.
\r
341 } catch (Exception e) {
\r
342 logger.error("Could not retrieve a list of documents referring to the specified authority item", e);
\r
343 wrapperList = null;
\r
346 return wrapperList;
\r
349 private static ArrayList<String> getRefNameServiceTypes() {
\r
350 if (refNameServiceTypes == null) {
\r
351 refNameServiceTypes = new ArrayList<String>();
\r
352 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_AUTHORITY);
\r
353 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_OBJECT);
\r
354 refNameServiceTypes.add(ServiceBindingUtils.SERVICE_TYPE_PROCEDURE);
\r
356 return refNameServiceTypes;
\r
358 // Seems like a good value - no real data to set this well.
\r
359 // Note: can set this value lower during debugging; e.g. to 3 - ADR 2012-07-10
\r
360 private static final int N_OBJS_TO_UPDATE_PER_LOOP = 100;
\r
362 public static int updateAuthorityRefDocs(
\r
363 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
364 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
365 RepositoryInstance repoSession,
\r
368 String refPropName) throws Exception {
\r
369 Map<String, ServiceBindingType> queriedServiceBindings = new HashMap<String, ServiceBindingType>();
\r
370 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService = new HashMap<String, List<AuthRefConfigInfo>>();
\r
372 int docsScanned = 0;
\r
373 int nRefsFound = 0;
\r
374 int currentPage = 0;
\r
375 int docsInCurrentPage = 0;
\r
376 final String WHERE_CLAUSE_ADDITIONS_VALUE = null;
\r
377 final String ORDER_BY_VALUE = "collectionspace_core:createdAt";
\r
379 if (!(repoClient instanceof RepositoryJavaClientImpl)) {
\r
380 throw new InternalError("updateAuthorityRefDocs() called with unknown repoClient type!");
\r
382 try { // REM - How can we deal with transaction and timeout issues here.
\r
383 final int pageSize = N_OBJS_TO_UPDATE_PER_LOOP;
\r
384 DocumentModelList docList;
\r
385 boolean morePages = true;
\r
386 while (morePages) {
\r
388 docList = findAuthorityRefDocs(ctx, repoClient, repoSession,
\r
389 getRefNameServiceTypes(), oldRefName, refPropName,
\r
390 queriedServiceBindings, authRefFieldsByService, WHERE_CLAUSE_ADDITIONS_VALUE, ORDER_BY_VALUE, pageSize, currentPage, false);
\r
392 if (docList == null) {
\r
393 logger.debug("updateAuthorityRefDocs: no documents could be found that referenced the old refName");
\r
396 docsInCurrentPage = docList.size();
\r
397 logger.debug("updateAuthorityRefDocs: current page=" + currentPage + " documents included in page=" + docsInCurrentPage);
\r
398 if (docsInCurrentPage == 0) {
\r
399 logger.debug("updateAuthorityRefDocs: no more documents requiring refName updates could be found");
\r
402 if (docsInCurrentPage < pageSize) {
\r
403 logger.debug("updateAuthorityRefDocs: assuming no more documents requiring refName updates will be found, as docsInCurrentPage < pageSize");
\r
407 int nRefsFoundThisPage = processRefObjsDocList(docList, ctx.getTenantId(), oldRefName, queriedServiceBindings, authRefFieldsByService,
\r
409 if (nRefsFoundThisPage > 0) {
\r
410 ((RepositoryJavaClientImpl) repoClient).saveDocListWithoutHandlerProcessing(ctx, repoSession, docList, true);
\r
411 nRefsFound += nRefsFoundThisPage;
\r
414 // FIXME: Per REM, set a limit of num objects - something like
\r
415 // 1000K objects - and also add a log Warning after some threshold
\r
416 docsScanned += docsInCurrentPage;
\r
422 } catch (Exception e) {
\r
423 logger.error("Internal error updating the AuthorityRefDocs: " + e.getLocalizedMessage());
\r
424 logger.debug(Tools.errorToString(e, true));
\r
427 logger.debug("updateAuthorityRefDocs replaced a total of " + nRefsFound + " authority references, within as many as " + docsScanned + " scanned document(s)");
\r
431 private static DocumentModelList findAuthorityRefDocs(
\r
432 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
\r
433 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient,
\r
434 RepositoryInstance repoSession, List<String> serviceTypes,
\r
436 String refPropName,
\r
437 Map<String, ServiceBindingType> queriedServiceBindings,
\r
438 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
439 String whereClauseAdditions,
\r
440 String orderByClause,
\r
443 boolean computeTotal) throws DocumentException, DocumentNotFoundException {
\r
445 // Get the service bindings for this tenant
\r
446 TenantBindingConfigReaderImpl tReader =
\r
447 ServiceMain.getInstance().getTenantBindingConfigReader();
\r
448 // We need to get all the procedures, authorities, and objects.
\r
449 List<ServiceBindingType> servicebindings = tReader.getServiceBindingsByType(ctx.getTenantId(), serviceTypes);
\r
450 if (servicebindings == null || servicebindings.isEmpty()) {
\r
451 logger.error("RefNameServiceUtils.getAuthorityRefDocs: No services bindings found, cannot proceed!");
\r
454 // Filter the list for current user rights
\r
455 servicebindings = SecurityUtils.getReadableServiceBindingsForCurrentUser(servicebindings);
\r
457 ArrayList<String> docTypes = new ArrayList<String>();
\r
459 String query = computeWhereClauseForAuthorityRefDocs(refName, refPropName, docTypes, servicebindings,
\r
460 queriedServiceBindings, authRefFieldsByService);
\r
461 if (query == null) { // found no authRef fields - nothing to query
\r
464 // Additional qualifications, like workflow state
\r
465 if (Tools.notBlank(whereClauseAdditions)) {
\r
466 query += " AND " + whereClauseAdditions;
\r
468 // Now we have to issue the search
\r
469 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl) repoClient;
\r
470 DocumentWrapper<DocumentModelList> docListWrapper = nuxeoRepoClient.findDocs(ctx, repoSession,
\r
471 docTypes, query, orderByClause, pageSize, pageNum, computeTotal);
\r
472 // Now we gather the info for each document into the list and return
\r
473 DocumentModelList docList = docListWrapper.getWrappedObject();
\r
476 private static final boolean READY_FOR_COMPLEX_QUERY = true;
\r
478 private static String computeWhereClauseForAuthorityRefDocs(
\r
480 String refPropName,
\r
481 ArrayList<String> docTypes,
\r
482 List<ServiceBindingType> servicebindings,
\r
483 Map<String, ServiceBindingType> queriedServiceBindings,
\r
484 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService) {
\r
486 boolean fFirst = true;
\r
487 List<String> authRefFieldPaths;
\r
488 for (ServiceBindingType sb : servicebindings) {
\r
489 // Gets the property names for each part, qualified with the part label (which
\r
490 // is also the table name, the way that the repository works).
\r
491 authRefFieldPaths =
\r
492 ServiceBindingUtils.getAllPartsPropertyValues(sb,
\r
493 refPropName, ServiceBindingUtils.QUALIFIED_PROP_NAMES);
\r
494 if (authRefFieldPaths.isEmpty()) {
\r
497 ArrayList<AuthRefConfigInfo> authRefsInfo = new ArrayList<AuthRefConfigInfo>();
\r
498 for (String spec : authRefFieldPaths) {
\r
499 AuthRefConfigInfo arci = new AuthRefConfigInfo(spec);
\r
500 authRefsInfo.add(arci);
\r
503 String docType = sb.getObject().getName();
\r
504 queriedServiceBindings.put(docType, sb);
\r
505 authRefFieldsByService.put(docType, authRefsInfo);
\r
506 docTypes.add(docType);
\r
509 if (fFirst) { // found no authRef fields - nothing to query
\r
512 // We used to build a complete matches query, but that was too complex.
\r
513 // Just build a keyword query based upon some key pieces - the urn syntax elements and the shortID
\r
514 // Note that this will also match the Item itself, but that will get filtered out when
\r
515 // we compute actual matches.
\r
516 AuthorityTermInfo authTermInfo = RefNameUtils.parseAuthorityTermInfo(refName);
\r
518 String keywords = RefNameUtils.URN_PREFIX
\r
519 + " AND " + (authTermInfo.inAuthority.name != null
\r
520 ? authTermInfo.inAuthority.name : authTermInfo.inAuthority.csid)
\r
521 + " AND " + (authTermInfo.name != null
\r
522 ? authTermInfo.name : authTermInfo.csid);
\r
524 String whereClauseStr = QueryManager.createWhereClauseFromKeywords(keywords);
\r
526 if (logger.isTraceEnabled()) {
\r
527 logger.trace("The 'where' clause to find refObjs is: ", whereClauseStr);
\r
530 return whereClauseStr;
\r
534 * Runs through the list of found docs, processing them. If list is
\r
535 * non-null, then processing means gather the info for items. If list is
\r
536 * null, and newRefName is non-null, then processing means replacing and
\r
537 * updating. If processing/updating, this must be called in teh context of
\r
538 * an open session, and caller must release Session after calling this.
\r
541 private static int processRefObjsDocList(
\r
542 DocumentModelList docList,
\r
545 Map<String, ServiceBindingType> queriedServiceBindings,
\r
546 Map<String, List<AuthRefConfigInfo>> authRefFieldsByService,
\r
547 List<AuthorityRefDocList.AuthorityRefDocItem> list,
\r
548 String newAuthorityRefName) {
\r
549 Iterator<DocumentModel> iter = docList.iterator();
\r
550 int nRefsFoundTotal = 0;
\r
551 while (iter.hasNext()) {
\r
552 DocumentModel docModel = iter.next();
\r
553 AuthorityRefDocList.AuthorityRefDocItem ilistItem;
\r
555 String docType = docModel.getDocumentType().getName(); // REM - This will be a tentant qualified document type
\r
556 docType = ServiceBindingUtils.getUnqualifiedTenantDocType(docType);
\r
557 ServiceBindingType sb = queriedServiceBindings.get(docType);
\r
559 throw new RuntimeException(
\r
560 "getAuthorityRefDocs: No Service Binding for docType: " + docType);
\r
563 if (list == null) { // no list - should be update refName case.
\r
564 if (newAuthorityRefName == null) {
\r
565 throw new InternalError("processRefObjsDocList() called with neither an itemList nor a new RefName!");
\r
568 } else { // Have a list - refObjs case
\r
569 if (newAuthorityRefName != null) {
\r
570 throw new InternalError("processRefObjsDocList() called with both an itemList and a new RefName!");
\r
572 ilistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
573 String csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
\r
574 ilistItem.setDocId(csid);
\r
576 UriTemplateRegistry registry = ServiceMain.getInstance().getUriTemplateRegistry();
\r
577 UriTemplateRegistryKey key = new UriTemplateRegistryKey(tenantId, docType);
\r
578 StoredValuesUriTemplate template = registry.get(key);
\r
579 if (template != null) {
\r
580 Map<String, String> additionalValues = new HashMap<String, String>();
\r
581 if (template.getUriTemplateType() == UriTemplateFactory.RESOURCE) {
\r
582 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, csid);
\r
583 uri = template.buildUri(additionalValues);
\r
584 } else if (template.getUriTemplateType() == UriTemplateFactory.ITEM) {
\r
586 String inAuthorityCsid = (String) docModel.getPropertyValue("inAuthority"); // AuthorityItemJAXBSchema.IN_AUTHORITY
\r
587 additionalValues.put(UriTemplateFactory.IDENTIFIER_VAR, inAuthorityCsid);
\r
588 additionalValues.put(UriTemplateFactory.ITEM_IDENTIFIER_VAR, csid);
\r
589 uri = template.buildUri(additionalValues);
\r
590 } catch (Exception e) {
\r
591 logger.warn("Could not extract inAuthority property from authority item record: " + e.getMessage());
\r
593 } else if (template.getUriTemplateType() == UriTemplateFactory.CONTACT) {
\r
594 // FIXME: Generating contact sub-resource URIs requires additional work,
\r
595 // as a follow-on to CSPACE-5271 - ADR 2012-08-16
\r
596 // Sets the default (empty string) value for uri, for now
\r
598 logger.warn("Unrecognized URI template type = " + template.getUriTemplateType());
\r
599 // Sets the default (empty string) value for uri
\r
601 } else { // (if template == null)
\r
602 logger.warn("Could not retrieve URI template from registry via tenant ID "
\r
603 + tenantId + " and docType " + docType);
\r
604 // Sets the default (empty string) value for uri
\r
606 ilistItem.setUri(uri);
\r
608 ilistItem.setWorkflowState(docModel.getCurrentLifeCycleState());
\r
609 ilistItem.setUpdatedAt(DocHandlerBase.getUpdatedAtAsString(docModel));
\r
610 } catch (Exception e) {
\r
611 logger.error("Error getting core values for doc [" + csid + "]: " + e.getLocalizedMessage());
\r
613 ilistItem.setDocType(docType);
\r
614 ilistItem.setDocNumber(
\r
615 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel));
\r
616 ilistItem.setDocName(
\r
617 ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NAME_PROP, docModel));
\r
619 // Now, we have to loop over the authRefFieldsByService to figure
\r
620 // out which field(s) matched this.
\r
621 List<AuthRefConfigInfo> matchingAuthRefFields = authRefFieldsByService.get(docType);
\r
622 if (matchingAuthRefFields == null || matchingAuthRefFields.isEmpty()) {
\r
623 throw new RuntimeException(
\r
624 "getAuthorityRefDocs: internal logic error: can't fetch authRefFields for DocType.");
\r
626 //String authRefAncestorField = "";
\r
627 //String authRefDescendantField = "";
\r
628 //String sourceField = "";
\r
629 int nRefsFoundInDoc = 0;
\r
631 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
\r
633 findAuthRefPropertiesInDoc(docModel, matchingAuthRefFields, refName, foundProps);
\r
634 for (RefNameServiceUtils.AuthRefInfo ari : foundProps) {
\r
635 if (ilistItem != null) {
\r
636 if (nRefsFoundInDoc == 0) { // First one?
\r
637 ilistItem.setSourceField(ari.getQualifiedDisplayName());
\r
638 } else { // duplicates from one object
\r
639 ilistItem = cloneAuthRefDocItem(ilistItem, ari.getQualifiedDisplayName());
\r
641 list.add(ilistItem);
\r
642 } else { // update refName case
\r
643 Property propToUpdate = ari.getProperty();
\r
644 propToUpdate.setValue(newAuthorityRefName);
\r
648 } catch (ClientException ce) {
\r
649 throw new RuntimeException(
\r
650 "getAuthorityRefDocs: Problem fetching values from repo: " + ce.getLocalizedMessage());
\r
652 if (nRefsFoundInDoc == 0) {
\r
654 "getAuthorityRefDocs: Result: "
\r
655 + docType + " [" + NuxeoUtils.getCsid(docModel)
\r
656 + "] does not reference ["
\r
659 nRefsFoundTotal += nRefsFoundInDoc;
\r
661 return nRefsFoundTotal;
\r
664 private static AuthorityRefDocList.AuthorityRefDocItem cloneAuthRefDocItem(
\r
665 AuthorityRefDocList.AuthorityRefDocItem ilistItem, String sourceField) {
\r
666 AuthorityRefDocList.AuthorityRefDocItem newlistItem = new AuthorityRefDocList.AuthorityRefDocItem();
\r
667 newlistItem.setDocId(ilistItem.getDocId());
\r
668 newlistItem.setDocName(ilistItem.getDocName());
\r
669 newlistItem.setDocNumber(ilistItem.getDocNumber());
\r
670 newlistItem.setDocType(ilistItem.getDocType());
\r
671 newlistItem.setUri(ilistItem.getUri());
\r
672 newlistItem.setSourceField(sourceField);
\r
673 return newlistItem;
\r
676 public static List<AuthRefInfo> findAuthRefPropertiesInDoc(
\r
677 DocumentModel docModel,
\r
678 List<AuthRefConfigInfo> authRefFieldInfo,
\r
679 String refNameToMatch,
\r
680 List<AuthRefInfo> foundProps) {
\r
681 // Assume that authRefFieldInfo is keyed by the field name (possibly mapped for UI)
\r
682 // and the values are elPaths to the field, where intervening group structures in
\r
683 // lists of complex structures are replaced with "*". Thus, valid paths include
\r
684 // the following (note that the ServiceBindingUtils prepend schema names to configured values):
\r
685 // "schemaname:fieldname"
\r
686 // "schemaname:scalarlistname"
\r
687 // "schemaname:complexfieldname/fieldname"
\r
688 // "schemaname:complexlistname/*/fieldname"
\r
689 // "schemaname:complexlistname/*/scalarlistname"
\r
690 // "schemaname:complexlistname/*/complexfieldname/fieldname"
\r
691 // "schemaname:complexlistname/*/complexlistname/*/fieldname"
\r
693 for (AuthRefConfigInfo arci : authRefFieldInfo) {
\r
695 // Get first property and work down as needed.
\r
696 Property prop = docModel.getProperty(arci.pathEls[0]);
\r
697 findAuthRefPropertiesInProperty(foundProps, prop, arci, 0, refNameToMatch);
\r
698 } catch (Exception e) {
\r
699 logger.error("Problem fetching property: " + arci.pathEls[0]);
\r
705 public static List<AuthRefInfo> findAuthRefPropertiesInProperty(
\r
706 List<AuthRefInfo> foundProps,
\r
708 AuthRefConfigInfo arci,
\r
709 int pathStartIndex, // Supports recursion and we work down the path
\r
710 String refNameToMatch) {
\r
711 if (pathStartIndex >= arci.pathEls.length) {
\r
712 throw new ArrayIndexOutOfBoundsException("Index = " + pathStartIndex + " for path: "
\r
713 + arci.pathEls.toString());
\r
715 AuthRefInfo ari = null;
\r
716 if (prop == null) {
\r
720 if (prop instanceof StringProperty) { // scalar string
\r
721 addARIifMatches(refNameToMatch, arci, prop, foundProps);
\r
722 } else if (prop instanceof List) {
\r
723 List<Property> propList = (List<Property>) prop;
\r
724 // run through list. Must either be list of Strings, or Complex
\r
725 for (Property listItemProp : propList) {
\r
726 if (listItemProp instanceof StringProperty) {
\r
727 if (arci.pathEls.length - pathStartIndex != 1) {
\r
728 logger.error("Configuration for authRefs does not match schema structure: "
\r
729 + arci.pathEls.toString());
\r
732 addARIifMatches(refNameToMatch, arci, listItemProp, foundProps);
\r
734 } else if (listItemProp.isComplex()) {
\r
735 // Just recurse to handle this. Note that since this is a list of complex,
\r
736 // which should look like listName/*/... we add 2 to the path start index
\r
737 findAuthRefPropertiesInProperty(foundProps, listItemProp, arci,
\r
738 pathStartIndex + 2, refNameToMatch);
\r
740 logger.error("Configuration for authRefs does not match schema structure: "
\r
741 + arci.pathEls.toString());
\r
745 } else if (prop.isComplex()) {
\r
746 String localPropName = arci.pathEls[pathStartIndex];
\r
748 Property localProp = prop.get(localPropName);
\r
749 // Now just recurse, pushing down the path 1 step
\r
750 findAuthRefPropertiesInProperty(foundProps, localProp, arci,
\r
751 pathStartIndex, refNameToMatch);
\r
752 } catch (PropertyNotFoundException pnfe) {
\r
753 logger.error("Could not find property: [" + localPropName + "] in path: "
\r
754 + arci.getFullPath());
\r
755 // Fall through - ari will be null and we will continue...
\r
758 logger.error("Configuration for authRefs does not match schema structure: "
\r
759 + arci.pathEls.toString());
\r
763 foundProps.add(ari); //FIXME: REM - This is dead code. 'ari' is never touched after being initalized to null. Why?
\r
769 private static void addARIifMatches(
\r
770 String refNameToMatch,
\r
771 AuthRefConfigInfo arci,
\r
773 List<AuthRefInfo> foundProps) {
\r
774 // Need to either match a passed refName
\r
775 // OR have no refName to match but be non-empty
\r
777 String value = (String) prop.getValue();
\r
778 if (((refNameToMatch != null) && refNameToMatch.equals(value))
\r
779 || ((refNameToMatch == null) && Tools.notBlank(value))) {
\r
781 logger.debug("Found a match on property: " + prop.getPath() + " with value: [" + value + "]");
\r
782 AuthRefInfo ari = new AuthRefInfo(arci, prop);
\r
783 foundProps.add(ari);
\r
785 } catch (PropertyException pe) {
\r
786 logger.debug("PropertyException on: " + prop.getPath() + pe.getLocalizedMessage());
\r
791 * Identifies whether the refName was found in the supplied field. If passed
\r
792 * a new RefName, will set that into fields in which the old one was found.
\r
794 * Only works for: * Scalar fields * Repeatable scalar fields (aka
\r
795 * multi-valued fields)
\r
797 * Does not work for: * Structured fields (complexTypes) * Repeatable
\r
798 * structured fields (repeatable complexTypes) private static int
\r
799 * refNameFoundInField(String oldRefName, Property fieldValue, String
\r
800 * newRefName) { int nFound = 0; if (fieldValue instanceof List) {
\r
801 * List<Property> fieldValueList = (List) fieldValue; for (Property
\r
802 * listItemValue : fieldValueList) { try { if ((listItemValue instanceof
\r
803 * StringProperty) &&
\r
804 * oldRefName.equalsIgnoreCase((String)listItemValue.getValue())) {
\r
805 * nFound++; if(newRefName!=null) { fieldValue.setValue(newRefName); } else
\r
806 * { // We cannot quit after the first, if we are replacing values. // If we
\r
807 * are just looking (not replacing), finding one is enough. break; } } }
\r
808 * catch( PropertyException pe ) {} } } else { try { if ((fieldValue
\r
809 * instanceof StringProperty) &&
\r
810 * oldRefName.equalsIgnoreCase((String)fieldValue.getValue())) { nFound++;
\r
811 * if(newRefName!=null) { fieldValue.setValue(newRefName); } } } catch(
\r
812 * PropertyException pe ) {} } return nFound; }
\r