2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.common.vocabulary.nuxeo;
26 import org.collectionspace.services.client.AuthorityClient;
27 import org.collectionspace.services.client.IQueryManager;
28 import org.collectionspace.services.client.PayloadInputPart;
29 import org.collectionspace.services.client.PayloadOutputPart;
30 import org.collectionspace.services.client.PoxPayloadIn;
31 import org.collectionspace.services.client.PoxPayloadOut;
32 import org.collectionspace.services.client.RelationClient;
33 import org.collectionspace.services.common.ResourceBase;
34 import org.collectionspace.services.common.api.CommonAPI;
35 import org.collectionspace.services.common.api.RefName;
36 import org.collectionspace.services.common.api.Tools;
37 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
38 import org.collectionspace.services.common.context.MultipartServiceContext;
39 import org.collectionspace.services.common.context.ServiceBindingUtils;
40 import org.collectionspace.services.common.context.ServiceContext;
41 import org.collectionspace.services.common.document.DocumentException;
42 import org.collectionspace.services.common.document.DocumentFilter;
43 import org.collectionspace.services.common.document.DocumentWrapper;
44 import org.collectionspace.services.common.relation.IRelationsManager;
45 import org.collectionspace.services.common.repository.RepositoryClient;
46 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
47 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
48 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
49 import org.collectionspace.services.config.service.ListResultField;
50 import org.collectionspace.services.config.service.ObjectPartType;
51 import org.collectionspace.services.jaxb.AbstractCommonList;
52 import org.collectionspace.services.nuxeo.client.java.CommonList; //FIXME: REM - 5/15/2012 - CommonList should be moved out of this package and into a more "common" package
53 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
54 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
55 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
56 import org.collectionspace.services.relation.RelationResource;
57 import org.collectionspace.services.relation.RelationsCommon;
58 import org.collectionspace.services.relation.RelationsCommonList;
59 import org.collectionspace.services.relation.RelationsDocListItem;
60 import org.collectionspace.services.relation.RelationshipType;
61 import org.collectionspace.services.vocabulary.VocabularyItemJAXBSchema;
63 import org.nuxeo.ecm.core.api.ClientException;
64 import org.nuxeo.ecm.core.api.DocumentModel;
65 import org.nuxeo.ecm.core.api.DocumentModelList;
66 import org.nuxeo.ecm.core.api.model.Property;
67 import org.nuxeo.ecm.core.api.model.PropertyException;
68 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
73 import javax.ws.rs.core.MultivaluedMap;
74 import javax.ws.rs.core.UriInfo;
76 import java.util.ArrayList;
77 import java.util.HashMap;
78 import java.util.List;
80 import java.util.regex.Matcher;
81 import java.util.regex.Pattern;
83 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
85 * AuthorityItemDocumentModelHandler
87 * $LastChangedRevision: $
90 public abstract class AuthorityItemDocumentModelHandler<AICommon>
91 extends DocHandlerBase<AICommon> {
93 private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
94 private String authorityItemCommonSchemaName;
95 private String authorityItemTermGroupXPathBase;
97 * inVocabulary is the parent Authority for this context
99 protected String inAuthority = null;
100 protected String authorityRefNameBase = null;
101 // Used to determine when the displayName changes as part of the update.
102 protected String oldDisplayNameOnUpdate = null;
103 protected String oldRefNameOnUpdate = null;
104 protected String newRefNameOnUpdate = null;
106 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
107 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
110 public void setInAuthority(String inAuthority) {
111 this.inAuthority = inAuthority;
114 /** Subclasses may override this to customize the URI segment. */
115 public String getAuthorityServicePath() {
116 return getServiceContext().getServiceName().toLowerCase(); // Laramie20110510 CSPACE-3932
120 public String getUri(DocumentModel docModel) {
121 // Laramie20110510 CSPACE-3932
122 String authorityServicePath = getAuthorityServicePath();
123 if(inAuthority==null) { // Only happens on queries to wildcarded authorities
125 inAuthority = (String) docModel.getProperty(authorityItemCommonSchemaName,
126 AuthorityItemJAXBSchema.IN_AUTHORITY);
127 } catch (ClientException pe) {
128 throw new RuntimeException("Could not get parent specifier for item!");
131 return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
134 protected String getAuthorityRefNameBase() {
135 return this.authorityRefNameBase;
138 public void setAuthorityRefNameBase(String value) {
139 this.authorityRefNameBase = value;
143 * Note: the Vocabulary service's VocabularyItemDocumentModelHandler class overrides this method.
145 protected ListResultField getListResultsDisplayNameField() {
146 ListResultField result = new ListResultField();
147 // Per CSPACE-5132, the name of this element remains 'displayName'
148 // for backwards compatibility, although its value is obtained
149 // from the termDisplayName field.
151 // Update: this name is now being changed to 'termDisplayName', both
152 // because this is the actual field name and because the app layer
153 // work to convert over to this field is underway. Per Patrick, the
154 // app layer treats lists, in at least some context(s), as sparse record
155 // payloads, and thus fields in list results must all be present in
156 // (i.e. represent a strict subset of the fields in) record schemas.
160 // In CSPACE-5134, these list results will change substantially
161 // to return display names for both the preferred term and for
162 // each non-preferred term (if any).
163 result.setElement(AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
164 result.setXpath(NuxeoUtils.getPrimaryXPathPropertyName(
165 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_DISPLAY_NAME));
171 * Note: the Vocabulary service's VocabularyItemDocumentModelHandler class overrides this method.
173 protected ListResultField getListResultsTermStatusField() {
174 ListResultField result = new ListResultField();
176 result.setElement(AuthorityItemJAXBSchema.TERM_STATUS);
177 result.setXpath(NuxeoUtils.getPrimaryXPathPropertyName(
178 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_STATUS));
183 private boolean isTermDisplayName(String elName) {
184 return AuthorityItemJAXBSchema.TERM_DISPLAY_NAME.equals(elName) || VocabularyItemJAXBSchema.DISPLAY_NAME.equals(elName);
188 public List<ListResultField> getListItemsArray() throws DocumentException {
189 List<ListResultField> list = super.getListItemsArray();
190 int nFields = list.size();
191 // Ensure that each item in a list of Authority items includes
192 // a set of common fields, so we do not depend upon configuration
193 // for general logic.
194 boolean hasDisplayName = false;
195 boolean hasShortId = false;
196 boolean hasRefName = false;
197 boolean hasTermStatus = false;
198 for (int i = 0; i < nFields; i++) {
199 ListResultField field = list.get(i);
200 String elName = field.getElement();
201 if (isTermDisplayName(elName) == true) {
202 hasDisplayName = true;
203 } else if (AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName)) {
205 } else if (AuthorityItemJAXBSchema.REF_NAME.equals(elName)) {
207 } else if (AuthorityItemJAXBSchema.TERM_STATUS.equals(elName)) {
208 hasTermStatus = true;
211 ListResultField field;
212 if (!hasDisplayName) {
213 field = getListResultsDisplayNameField();
214 list.add(field); //Note: We're updating the "global" service and tenant bindings instance here -the list instance here is a reference to the tenant bindings instance in the singleton ServiceMain.
217 field = new ListResultField();
218 field.setElement(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
219 field.setXpath(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
223 field = new ListResultField();
224 field.setElement(AuthorityItemJAXBSchema.REF_NAME);
225 field.setXpath(AuthorityItemJAXBSchema.REF_NAME);
228 if (!hasTermStatus) {
229 field = getListResultsTermStatusField();
237 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
240 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
241 // first fill all the parts of the document
242 super.handleCreate(wrapDoc);
243 // Ensure we have required fields set properly
244 handleInAuthority(wrapDoc.getWrappedObject());
246 // FIXME: This call to synthesize a shortIdentifier from the termDisplayName
247 // of the preferred term may have been commented out, in the course of
248 // adding support for preferred / non-preferred terms, in CSPACE-4813
249 // and linked issues. Revisit this to determine whether we want to
253 handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
254 // refName includes displayName, so we force a correct value here.
255 updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
259 * Note that the Vocabulary service's document-model for items overrides this method.
261 protected String getPrimaryDisplayName(DocumentModel docModel, String schema,
262 String complexPropertyName, String fieldName) {
263 String result = null;
265 result = getStringValueInPrimaryRepeatingComplexProperty(docModel, schema, complexPropertyName, fieldName);
271 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
274 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
275 // First, get a copy of the old displayName
276 // oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
277 // AuthorityItemJAXBSchema.DISPLAY_NAME);
278 oldDisplayNameOnUpdate = getPrimaryDisplayName(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName,
279 getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
280 oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
281 AuthorityItemJAXBSchema.REF_NAME);
282 super.handleUpdate(wrapDoc);
284 // Now, check the new display and handle the refname update.
285 String newDisplayName = (String) getPrimaryDisplayName(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName,
286 authorityItemTermGroupXPathBase,
287 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
288 if (newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
289 // Need to update the refName, and then fix all references.
290 newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
292 // Mark as not needing attention in completeUpdate phase.
293 newRefNameOnUpdate = null;
294 oldRefNameOnUpdate = null;
299 * Handle display name.
301 * @param docModel the doc model
302 * @throws Exception the exception
304 // protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
305 // // Do nothing by default.
309 * Handle refName updates for changes to display name.
310 * Assumes refName is already correct. Just ensures it is right.
312 * @param docModel the doc model
313 * @param newDisplayName the new display name
314 * @throws Exception the exception
316 protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
317 String newDisplayName) throws Exception {
318 RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
319 if (authItem == null) {
320 String err = "Authority Item has illegal refName: " + oldRefNameOnUpdate;
322 throw new IllegalArgumentException(err);
324 authItem.displayName = newDisplayName;
325 String updatedRefName = authItem.toString();
326 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
327 return updatedRefName;
331 * Note: The Vocabulary document handler overrides this method.
333 protected String getRefPropName() {
334 return ServiceBindingUtils.AUTH_REF_PROP;
338 * Checks to see if the refName has changed, and if so,
339 * uses utilities to find all references and update them.
342 protected void handleItemRefNameReferenceUpdate() throws Exception {
343 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
344 // We have work to do.
345 if (logger.isDebugEnabled()) {
346 String eol = System.getProperty("line.separator");
347 logger.debug("Need to find and update references to Item." + eol
348 + " Old refName" + oldRefNameOnUpdate + eol
349 + " New refName" + newRefNameOnUpdate);
351 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
352 RepositoryClient repoClient = getRepositoryClient(ctx);
353 String refNameProp = getRefPropName();
355 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
356 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
357 if (logger.isDebugEnabled()) {
358 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
364 * If no short identifier was provided in the input payload, generate a
365 * short identifier from the preferred term display name or term name.
367 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel,
368 String schemaName) throws Exception {
369 String shortIdentifier = (String) docModel.getProperty(schemaName,
370 AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
372 if (Tools.isEmpty(shortIdentifier)) {
373 String termDisplayName = getPrimaryDisplayName(
374 docModel, authorityItemCommonSchemaName,
375 getItemTermInfoGroupXPathBase(),
376 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
378 String termName = getPrimaryDisplayName(
379 docModel, authorityItemCommonSchemaName,
380 getItemTermInfoGroupXPathBase(),
381 AuthorityItemJAXBSchema.TERM_NAME);
383 String generatedShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(termDisplayName,
385 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER,
386 generatedShortIdentifier);
391 * Generate a refName for the authority item from the short identifier
394 * All refNames for authority items are generated. If a client supplies
395 * a refName, it will be overwritten during create (per this method)
396 * or discarded during update (per filterReadOnlyPropertiesForPart).
398 * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
401 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
403 String authorityRefBaseName) throws Exception {
404 DocumentModel docModel = wrapDoc.getWrappedObject();
405 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
406 String displayName = getPrimaryDisplayName(docModel, authorityItemCommonSchemaName,
407 getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
409 if (Tools.isEmpty(authorityRefBaseName)) {
410 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
413 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
414 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
415 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
419 * Check the logic around the parent pointer. Note that we only need do this on
420 * create, since we have logic to make this read-only on update.
424 * @throws Exception the exception
426 private void handleInAuthority(DocumentModel docModel) throws Exception {
427 if(inAuthority==null) { // Only happens on queries to wildcarded authorities
428 throw new IllegalStateException("Trying to Create an object with no inAuthority value!");
430 docModel.setProperty(authorityItemCommonSchemaName,
431 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
435 public AuthorityRefDocList getReferencingObjects(
436 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
437 List<String> serviceTypes,
439 String itemcsid) throws Exception {
440 AuthorityRefDocList authRefDocList = null;
441 RepositoryInstance repoSession = null;
442 boolean releaseRepoSession = false;
445 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
446 repoSession = this.getRepositorySession();
447 if (repoSession == null) {
448 repoSession = repoClient.getRepositorySession();
449 releaseRepoSession = true;
451 DocumentFilter myFilter = getDocumentFilter();
454 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, itemcsid);
455 DocumentModel docModel = wrapper.getWrappedObject();
456 String refName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
457 authRefDocList = RefNameServiceUtils.getAuthorityRefDocs(
458 repoSession, ctx, repoClient,
462 myFilter.getPageSize(), myFilter.getStartPage(), true /*computeTotal*/);
463 } catch (PropertyException pe) {
465 } catch (DocumentException de) {
467 } catch (Exception e) {
468 if (logger.isDebugEnabled()) {
469 logger.debug("Caught exception ", e);
471 throw new DocumentException(e);
473 if (releaseRepoSession && repoSession != null) {
474 repoClient.releaseRepositorySession(repoSession);
477 } catch (Exception e) {
478 if (logger.isDebugEnabled()) {
479 logger.debug("Caught exception ", e);
481 throw new DocumentException(e);
483 return authRefDocList;
488 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
491 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
493 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
495 // Add the CSID to the common part, since they may have fetched via the shortId.
496 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
497 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
498 unQObjectProperties.put("csid", csid);
501 return unQObjectProperties;
505 * Filters out selected values supplied in an update request.
507 * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
508 * that the link to the item's parent remains untouched.
510 * @param objectProps the properties filtered out from the update payload
511 * @param partMeta metadata for the object to fill
514 public void filterReadOnlyPropertiesForPart(
515 Map<String, Object> objectProps, ObjectPartType partMeta) {
516 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
517 String commonPartLabel = getServiceContext().getCommonPartLabel();
518 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
519 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
520 objectProps.remove(AuthorityItemJAXBSchema.CSID);
521 objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
522 objectProps.remove(AuthorityItemJAXBSchema.REF_NAME);
526 protected List<String> getPartialTermDisplayNameMatches(List<String> termDisplayNameList, String partialTerm) {
527 List<String> result = new ArrayList<String>();
529 for (String termDisplayName : termDisplayNameList) {
530 if (termDisplayName.contains(partialTerm) == true) {
531 result.add(termDisplayName);
538 @SuppressWarnings("unchecked")
539 private List<String> getPartialTermDisplayNameMatches(DocumentModel docModel, // REM - CSPACE-5133
540 String schema, ListResultField field, String partialTerm) {
541 List<String> result = null;
543 String xpath = field.getXpath(); // results in something like "persons_common:personTermGroupList/[0]/termDisplayName"
544 int endOfTermGroup = xpath.lastIndexOf("/[0]/");
545 String propertyName = endOfTermGroup != -1 ? xpath.substring(0, endOfTermGroup) : xpath; // it may not be multivalued so the xpath passed in would be the property name
549 value = docModel.getProperty(schema, propertyName);
550 } catch (Exception e) {
551 logger.error("Could not extract term display name with property = "
555 if (value != null && value instanceof ArrayList) {
556 ArrayList<HashMap<String, Object>> termGroupList = (ArrayList<HashMap<String, Object>>)value;
557 int arrayListSize = termGroupList.size();
558 if (arrayListSize > 1) { // if there's only 1 element in the list then we've already matched the primary term's display name
559 List<String> displayNameList = new ArrayList<String>();
560 for (int i = 1; i < arrayListSize; i++) { // start at 1, skip the primary term's displayName since we will always return it
561 HashMap<String, Object> map = (HashMap<String, Object>)termGroupList.get(i);
562 String termDisplayName = (String) map.get(AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
563 displayNameList.add(i - 1, termDisplayName);
566 result = getPartialTermDisplayNameMatches(displayNameList, partialTerm);
574 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
575 String schema, ListResultField field) {
576 Object result = null;
578 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
579 String elName = field.getElement();
581 // If the list result value is the termDisplayName element, we need to check to see if a partial term query was made.
583 if (isTermDisplayName(elName) == true) {
584 MultivaluedMap<String, String> queryParams = this.getServiceContext().getQueryParams();
585 String partialTerm = queryParams != null ? queryParams.getFirst(IQueryManager.SEARCH_TYPE_PARTIALTERM) : null;
586 if (partialTerm != null && partialTerm.trim().isEmpty() == false) {
587 String primaryTermDisplayName = (String)result;
588 List<String> matches = getPartialTermDisplayNameMatches(docModel, schema, field, partialTerm);
589 if (matches != null && matches.isEmpty() == false) {
590 matches.add(0, primaryTermDisplayName); // insert the primary term's display name at the beginning of the list
591 result = matches; // set the result to a list of matching term display names with the primary term's display name at the beginning
600 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
601 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
602 super.extractAllParts(wrapDoc);
604 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
605 if (Tools.isTrue(showSiblings)) {
606 showSiblings(wrapDoc, ctx);
607 return; // actual result is returned on ctx.addOutputPart();
610 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
611 if (Tools.isTrue(showRelations)) {
612 showRelations(wrapDoc, ctx);
613 return; // actual result is returned on ctx.addOutputPart();
616 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
617 if (Tools.isTrue(showAllRelations)) {
618 showAllRelations(wrapDoc, ctx);
619 return; // actual result is returned on ctx.addOutputPart();
623 /** @return null on parent not found
625 protected String getParentCSID(String thisCSID) throws Exception {
626 String parentCSID = null;
628 String predicate = RelationshipType.HAS_BROADER.value();
629 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
630 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
631 if (parentList != null) {
632 if (parentList.size() == 0) {
635 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
636 parentCSID = relationListItem.getObjectCsid();
639 } catch (Exception e) {
640 logger.error("Could not find parent for this: " + thisCSID, e);
645 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
646 MultipartServiceContext ctx) throws Exception {
647 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
649 String predicate = RelationshipType.HAS_BROADER.value();
650 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
651 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
653 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
654 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
656 if(logger.isTraceEnabled()) {
657 String dump = dumpLists(thisCSID, parentList, childrenList, null);
658 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
661 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
662 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
663 //Not optimal, but that's the current design spec.
665 for (RelationsCommonList.RelationListItem parent : parentList) {
666 childrenList.add(parent);
669 long childrenSize = childrenList.size();
670 childrenListOuter.setTotalItems(childrenSize);
671 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
673 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
674 ctx.addOutputPart(relationsPart);
677 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
678 MultipartServiceContext ctx) throws Exception {
679 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
680 String parentCSID = getParentCSID(thisCSID);
681 if (parentCSID == null) {
682 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
686 String predicate = RelationshipType.HAS_BROADER.value();
687 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
688 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
690 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
693 RelationsCommonList.RelationListItem item = null;
694 for (RelationsCommonList.RelationListItem sibling : siblingList) {
695 if (thisCSID.equals(sibling.getSubjectCsid())) {
696 toRemoveList.add(sibling); //IS_A copy of the main item, i.e. I have a parent that is my parent, so I'm in the list from the above query.
699 //rather than create an immutable iterator, I'm just putting the items to remove on a separate list, then looping over that list and removing.
700 for (RelationsCommonList.RelationListItem self : toRemoveList) {
701 removeFromList(siblingList, self);
704 long siblingSize = siblingList.size();
705 siblingListOuter.setTotalItems(siblingSize);
706 siblingListOuter.setItemsInPage(siblingSize);
707 if(logger.isTraceEnabled()) {
708 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
709 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
712 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
713 ctx.addOutputPart(relationsPart);
716 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
717 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
719 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
720 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
722 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
723 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
725 if(logger.isTraceEnabled()) {
726 String dump = dumpLists(thisCSID, subjectList, objectList, null);
727 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
730 subjectList.addAll(objectList);
732 //now subjectList actually has records BOTH where thisCSID is subject and object.
733 long relatedSize = subjectList.size();
734 subjectListOuter.setTotalItems(relatedSize);
735 subjectListOuter.setItemsInPage(relatedSize);
737 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
738 ctx.addOutputPart(relationsPart);
742 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
744 // We currently don't override this method with any AuthorityItemDocumentModelHandler specific functionality, so
745 // we could remove this method.
747 super.fillAllParts(wrapDoc, action);
751 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
752 super.completeCreate(wrapDoc);
753 handleRelationsPayload(wrapDoc, false);
757 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
758 super.completeUpdate(wrapDoc);
759 handleRelationsPayload(wrapDoc, true);
760 handleItemRefNameReferenceUpdate();
763 // Note that we must do this after we have completed the Update, so that the repository has the
764 // info for the item itself. The relations code must call into the repo to get info for each end.
765 // This could be optimized to pass in the parent docModel, since it will often be one end.
766 // Nevertheless, we should complete the item save before we do work on the relations, especially
767 // since a save on Create might fail, and we would not want to create relations for something
768 // that may not be created...
769 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
770 ServiceContext ctx = getServiceContext();
771 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
772 DocumentModel documentModel = (wrapDoc.getWrappedObject());
773 String itemCsid = documentModel.getName();
775 //Updates relations part
776 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
778 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList); //FIXME: REM - We should check for a null relationsCommonList and not create the new common list payload
779 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
781 //now we add part for relations list
782 //ServiceContext ctx = getServiceContext();
783 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
784 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
787 /** updateRelations strategy:
789 go through inboundList, remove anything from childList that matches from childList
790 go through inboundList, remove anything from parentList that matches from parentList
791 go through parentList, delete all remaining
792 go through childList, delete all remaining
793 go through actionList, add all remaining.
794 check for duplicate children
795 check for more than one parent.
797 inboundList parentList childList actionList
798 ---------------- --------------- ---------------- ----------------
799 child-a parent-c child-a child-b
800 child-b parent-d child-c
803 private RelationsCommonList updateRelations(
804 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
806 if (logger.isTraceEnabled()) {
807 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
809 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
811 return null; //nothing to do--they didn't send a list of relations.
813 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
814 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
815 List<RelationsCommonList.RelationListItem> actionList = newList();
816 List<RelationsCommonList.RelationListItem> childList = null;
817 List<RelationsCommonList.RelationListItem> parentList = null;
818 DocumentModel docModel = wrapDoc.getWrappedObject();
819 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
821 ServiceContext ctx = getServiceContext();
822 //Do magic replacement of ${itemCSID} and fix URI's.
823 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
825 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
826 UriInfo uriInfo = ctx.getUriInfo();
827 MultivaluedMap queryParams = uriInfo.getQueryParameters();
830 //Run getList() once as sent to get childListOuter:
831 String predicate = RelationshipType.HAS_BROADER.value();
832 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
833 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
834 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
835 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
836 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
837 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
839 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
840 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
841 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
842 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
843 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
846 childList = childListOuter.getRelationListItem();
847 parentList = parentListOuter.getRelationListItem();
849 if (parentList.size() > 1) {
850 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
853 if (logger.isTraceEnabled()) {
854 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
859 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
860 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
861 // and so the CSID for those may be null
862 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
863 // Look for parents and children
864 if(itemCSID.equals(inboundItem.getObject().getCsid())
865 || itemRefName.equals(inboundItem.getObject().getRefName())) {
866 //then this is an item that says we have a child. That child is inboundItem
867 RelationsCommonList.RelationListItem childItem =
868 (childList == null) ? null : findInList(childList, inboundItem);
869 if (childItem != null) {
870 if (logger.isTraceEnabled()) {
871 StringBuilder sb = new StringBuilder();
872 itemToString(sb, "== Child: ", childItem);
873 logger.trace("Found inboundChild in current child list: " + sb.toString());
875 removeFromList(childList, childItem); //exists, just take it off delete list
877 if (logger.isTraceEnabled()) {
878 StringBuilder sb = new StringBuilder();
879 itemToString(sb, "== Child: ", inboundItem);
880 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
882 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
883 String newChildCsid = inboundItem.getSubject().getCsid();
884 if(newChildCsid == null) {
885 String newChildRefName = inboundItem.getSubject().getRefName();
886 if(newChildRefName==null) {
887 throw new RuntimeException("Child with no CSID or refName!");
889 if (logger.isTraceEnabled()) {
890 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
892 DocumentModel newChildDocModel =
893 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
894 newChildRefName, getServiceContext().getResourceMap());
895 newChildCsid = getCsid(newChildDocModel);
897 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
900 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
901 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
902 //then this is an item that says we have a parent. inboundItem is that parent.
903 RelationsCommonList.RelationListItem parentItem =
904 (parentList == null) ? null : findInList(parentList, inboundItem);
905 if (parentItem != null) {
906 removeFromList(parentList, parentItem); //exists, just take it off delete list
908 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
911 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
914 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
917 if (logger.isTraceEnabled()) {
918 String dump = dumpLists(itemCSID, parentList, childList, actionList);
919 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
922 if (logger.isTraceEnabled()) {
923 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
924 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
926 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
927 deleteRelations(childList, ctx, "childList");
929 if (logger.isTraceEnabled()) {
930 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
931 + actionList.size() + " new parents and children.");
933 createRelations(actionList, ctx);
934 if (logger.isTraceEnabled()) {
935 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
937 //We return all elements on the inbound list, since we have just worked to make them exist in the system
938 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
939 return relationsCommonListBody;
942 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
943 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
944 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
945 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
946 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
947 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
948 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
949 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
950 deleteRelations(parentList, ctx, "parentList-delete");
954 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
956 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
958 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
960 sb.append(item.getPredicate());
962 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
966 private String dumpLists(String itemCSID,
967 List<RelationsCommonList.RelationListItem> parentList,
968 List<RelationsCommonList.RelationListItem> childList,
969 List<RelationsCommonList.RelationListItem> actionList) {
970 StringBuilder sb = new StringBuilder();
971 sb.append("itemCSID: " + itemCSID + CR);
972 if(parentList!=null) {
973 sb.append(dumpList(parentList, "parentList"));
975 if(childList!=null) {
976 sb.append(dumpList(childList, "childList"));
978 if(actionList!=null) {
979 sb.append(dumpList(actionList, "actionList"));
981 return sb.toString();
983 private final static String CR = "\r\n";
984 private final static String T = " ";
986 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
987 StringBuilder sb = new StringBuilder();
989 if (list.size() > 0) {
990 sb.append("=========== " + label + " ==========" + CR);
992 for (RelationsCommonList.RelationListItem item : list) {
993 itemToString(sb, "== ", item);
996 return sb.toString();
999 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1000 * and sets URI correctly for related items.
1001 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1003 protected void fixupInboundListItems(ServiceContext ctx,
1004 List<RelationsCommonList.RelationListItem> inboundList,
1005 DocumentModel docModel,
1006 String itemCSID) throws Exception {
1007 String thisURI = this.getUri(docModel);
1008 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1009 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1010 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1011 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1012 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1014 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1015 inboundItem.setObjectCsid(itemCSID);
1016 inboundItemObject.setCsid(itemCSID);
1017 //inboundItemObject.setUri(getUri(docModel));
1020 String objectCsid = inboundItemObject.getCsid();
1021 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1022 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1023 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1024 inboundItemObject.setUri(uri); //CSPACE-4037
1027 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1029 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1030 inboundItem.setSubjectCsid(itemCSID);
1031 inboundItemSubject.setCsid(itemCSID);
1032 //inboundItemSubject.setUri(getUri(docModel));
1035 String subjectCsid = inboundItemSubject.getCsid();
1036 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1037 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1038 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1039 inboundItemSubject.setUri(uri); //CSPACE-4037
1042 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1047 // this method calls the RelationResource to have it create the relations and persist them.
1048 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
1049 for (RelationsCommonList.RelationListItem item : inboundList) {
1050 RelationsCommon rc = new RelationsCommon();
1051 //rc.setCsid(item.getCsid());
1052 //todo: assignTo(item, rc);
1053 RelationsDocListItem itemSubject = item.getSubject();
1054 RelationsDocListItem itemObject = item.getObject();
1056 // Set at least one of CSID and refName for Subject and Object
1057 // Either value might be null for for each of Subject and Object
1058 String subjectCsid = itemSubject.getCsid();
1059 rc.setSubjectCsid(subjectCsid);
1061 String objCsid = itemObject.getCsid();
1062 rc.setObjectCsid(objCsid);
1064 rc.setSubjectRefName(itemSubject.getRefName());
1065 rc.setObjectRefName(itemObject.getRefName());
1067 rc.setRelationshipType(item.getPredicate());
1068 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1069 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1071 // This is superfluous, since it will be fetched by the Relations Create logic.
1072 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1073 rc.setObjectDocumentType(itemObject.getDocumentType());
1075 // This is superfluous, since it will be fetched by the Relations Create logic.
1076 rc.setSubjectUri(itemSubject.getUri());
1077 rc.setObjectUri(itemObject.getUri());
1078 // May not have the info here. Only really require CSID or refName.
1079 // Rest is handled in the Relation create mechanism
1080 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1082 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1083 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1084 payloadOut.addPart(outputPart);
1085 RelationResource relationResource = new RelationResource();
1086 Object res = relationResource.create(ctx.getResourceMap(),
1087 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1091 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
1093 for (RelationsCommonList.RelationListItem item : list) {
1094 RelationResource relationResource = new RelationResource();
1095 if(logger.isTraceEnabled()) {
1096 StringBuilder sb = new StringBuilder();
1097 itemToString(sb, "==== TO DELETE: ", item);
1098 logger.trace(sb.toString());
1100 Object res = relationResource.delete(item.getCsid());
1102 } catch (Throwable t) {
1103 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1108 private List<RelationsCommonList.RelationListItem> newList() {
1109 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
1113 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
1114 List<RelationsCommonList.RelationListItem> result = newList();
1115 for (RelationsCommonList.RelationListItem item : inboundList) {
1121 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1122 // But the list items must not be sparse
1123 private RelationsCommonList.RelationListItem findInList(
1124 List<RelationsCommonList.RelationListItem> list,
1125 RelationsCommonList.RelationListItem item) {
1126 RelationsCommonList.RelationListItem foundItem = null;
1127 for (RelationsCommonList.RelationListItem listItem : list) {
1128 if (itemsEqual(listItem, item)) { //equals must be defined, else
1129 foundItem = listItem;
1136 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1137 // But item1 must not be sparse
1138 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1139 if (item1 == null || item2 == null) {
1142 RelationsDocListItem subj1 = item1.getSubject();
1143 RelationsDocListItem subj2 = item2.getSubject();
1144 RelationsDocListItem obj1 = item1.getObject();
1145 RelationsDocListItem obj2 = item2.getObject();
1146 String subj1Csid = subj1.getCsid();
1147 String subj2Csid = subj2.getCsid();
1148 String subj1RefName = subj1.getRefName();
1149 String subj2RefName = subj2.getRefName();
1151 String obj1Csid = obj1.getCsid();
1152 String obj2Csid = obj2.getCsid();
1153 String obj1RefName = obj1.getRefName();
1154 String obj2RefName = obj2.getRefName();
1157 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1158 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1159 // predicate is proper, but still allow relationshipType
1160 && (item1.getPredicate().equals(item2.getPredicate())
1161 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1162 // Allow missing docTypes, so long as they do not conflict
1163 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1164 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1168 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1172 /* don't even THINK of re-using this method.
1173 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
1175 private String extractInAuthorityCSID(String uri) {
1176 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
1177 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
1178 Matcher m = p.matcher(uri);
1180 if (m.groupCount() < 3) {
1181 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
1184 //String service = m.group(1);
1185 String inauth = m.group(2);
1186 //String theRest = m.group(3);
1188 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
1191 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
1196 //ensures CSPACE-4042
1197 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
1198 String authorityCSID = extractInAuthorityCSID(thisURI);
1199 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
1200 if (Tools.isBlank(authorityCSID)
1201 || Tools.isBlank(authorityCSIDForInbound)
1202 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
1203 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
1207 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1208 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1209 ServiceContext ctx = getServiceContext();
1210 MultivaluedMap queryParams = ctx.getQueryParams();
1211 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1212 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1213 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1215 RelationResource relationResource = new RelationResource();
1216 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
1217 return relationsCommonList;
1219 //============================= END TODO refactor ==========================
1221 public String getItemTermInfoGroupXPathBase() {
1222 return authorityItemTermGroupXPathBase;
1225 public void setItemTermInfoGroupXPathBase(String itemTermInfoGroupXPathBase) {
1226 authorityItemTermGroupXPathBase = itemTermInfoGroupXPathBase;
1229 protected String getAuthorityItemCommonSchemaName() {
1230 return authorityItemCommonSchemaName;