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.PayloadInputPart;
28 import org.collectionspace.services.client.PayloadOutputPart;
29 import org.collectionspace.services.client.PoxPayloadIn;
30 import org.collectionspace.services.client.PoxPayloadOut;
31 import org.collectionspace.services.client.RelationClient;
32 import org.collectionspace.services.common.ResourceBase;
33 import org.collectionspace.services.common.ServiceMessages;
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.document.DocumentWrapperImpl;
45 import org.collectionspace.services.common.relation.IRelationsManager;
46 import org.collectionspace.services.common.repository.RepositoryClient;
47 import org.collectionspace.services.common.repository.RepositoryClientFactory;
48 import org.collectionspace.services.common.service.ObjectPartType;
49 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
50 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
51 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
52 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
53 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
54 import org.collectionspace.services.common.service.ListResultField;
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.nuxeo.ecm.core.api.DocumentModel;
62 import org.nuxeo.ecm.core.api.model.PropertyException;
63 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
64 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
68 import javax.ws.rs.PathParam;
69 import javax.ws.rs.WebApplicationException;
70 import javax.ws.rs.core.Context;
71 import javax.ws.rs.core.MultivaluedMap;
72 import javax.ws.rs.core.Response;
73 import javax.ws.rs.core.UriInfo;
74 import java.util.ArrayList;
75 import java.util.List;
77 import java.util.regex.Matcher;
78 import java.util.regex.Pattern;
80 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
82 * AuthorityItemDocumentModelHandler
84 * $LastChangedRevision: $
87 public abstract class AuthorityItemDocumentModelHandler<AICommon>
88 extends DocHandlerBase<AICommon> {
90 private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
91 private String authorityItemCommonSchemaName;
93 * inVocabulary is the parent Authority for this context
95 protected String inAuthority;
96 protected String authorityRefNameBase;
97 // Used to determine when the displayName changes as part of the update.
98 protected String oldDisplayNameOnUpdate = null;
99 protected String oldRefNameOnUpdate = null;
100 protected String newRefNameOnUpdate = null;
102 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
103 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
106 public String getInAuthority() {
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 return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
126 public String getAuthorityRefNameBase() {
127 return this.authorityRefNameBase;
130 public void setAuthorityRefNameBase(String value) {
131 this.authorityRefNameBase = value;
135 public List<ListResultField> getListItemsArray() throws DocumentException {
136 List<ListResultField> list = super.getListItemsArray();
137 int nFields = list.size();
138 // Ensure some common fields so do not depend upon config for general logic
139 boolean hasDisplayName = false;
140 boolean hasShortId = false;
141 boolean hasRefName = false;
142 boolean hasTermStatus = false;
143 for (int i = 0; i < nFields; i++) {
144 ListResultField field = list.get(i);
145 String elName = field.getElement();
146 if (AuthorityItemJAXBSchema.DISPLAY_NAME.equals(elName)) {
147 hasDisplayName = true;
148 } else if (AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName)) {
150 } else if (AuthorityItemJAXBSchema.REF_NAME.equals(elName)) {
152 } else if (AuthorityItemJAXBSchema.TERM_STATUS.equals(elName)) {
153 hasTermStatus = true;
156 ListResultField field;
157 if (!hasDisplayName) {
158 field = new ListResultField();
159 field.setElement(AuthorityItemJAXBSchema.DISPLAY_NAME);
160 field.setXpath(AuthorityItemJAXBSchema.DISPLAY_NAME);
164 field = new ListResultField();
165 field.setElement(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
166 field.setXpath(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
170 field = new ListResultField();
171 field.setElement(AuthorityItemJAXBSchema.REF_NAME);
172 field.setXpath(AuthorityItemJAXBSchema.REF_NAME);
175 if (!hasTermStatus) {
176 field = new ListResultField();
177 field.setElement(AuthorityItemJAXBSchema.TERM_STATUS);
178 field.setXpath(AuthorityItemJAXBSchema.TERM_STATUS);
187 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
190 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
191 // first fill all the parts of the document
192 super.handleCreate(wrapDoc);
193 // Ensure we have required fields set properly
194 handleInAuthority(wrapDoc.getWrappedObject());
196 handleComputedDisplayNames(wrapDoc.getWrappedObject());
197 String displayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
198 AuthorityItemJAXBSchema.DISPLAY_NAME);
199 if (Tools.isEmpty(displayName)) {
200 logger.warn("Creating Authority Item with no displayName!");
203 handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
204 // refName includes displayName, so we force a correct value here.
205 updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
209 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
212 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
213 // First, get a copy of the old displayName
214 oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
215 AuthorityItemJAXBSchema.DISPLAY_NAME);
216 oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
217 AuthorityItemJAXBSchema.REF_NAME);
218 super.handleUpdate(wrapDoc);
219 handleComputedDisplayNames(wrapDoc.getWrappedObject());
220 String newDisplayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
221 AuthorityItemJAXBSchema.DISPLAY_NAME);
222 if (newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
223 // Need to update the refName, and then fix all references.
224 newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
226 // Mark as not needing attention in completeUpdate phase.
227 newRefNameOnUpdate = null;
228 oldRefNameOnUpdate = null;
233 * Handle display name.
235 * @param docModel the doc model
236 * @throws Exception the exception
238 protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
239 // Do nothing by default.
243 * Handle refName updates for changes to display name.
244 * Assumes refName is already correct. Just ensures it is right.
246 * @param docModel the doc model
247 * @param newDisplayName the new display name
248 * @throws Exception the exception
250 protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
251 String newDisplayName) throws Exception {
252 RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
253 if (authItem == null) {
254 String err = "Authority Item has illegal refName: " + oldRefNameOnUpdate;
256 throw new IllegalArgumentException(err);
258 authItem.displayName = newDisplayName;
259 String updatedRefName = authItem.toString();
260 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
261 return updatedRefName;
264 protected String getRefPropName() {
265 return ServiceBindingUtils.AUTH_REF_PROP;
269 * Checks to see if the refName has changed, and if so,
270 * uses utilities to find all references and update them.
272 protected void handleItemRefNameReferenceUpdate() {
273 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
274 // We have work to do.
275 if (logger.isDebugEnabled()) {
276 String eol = System.getProperty("line.separator");
277 logger.debug("Need to find and update references to Item." + eol
278 + " Old refName" + oldRefNameOnUpdate + eol
279 + " New refName" + newRefNameOnUpdate);
281 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
282 RepositoryClient repoClient = getRepositoryClient(ctx);
283 String refNameProp = getRefPropName();
285 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
286 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
287 if (logger.isDebugEnabled()) {
288 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
294 * If no short identifier was provided in the input payload,
295 * generate a short identifier from the display name.
297 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
298 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
299 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
300 String shortDisplayName = "";
302 shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
303 } catch (PropertyNotFoundException pnfe) {
304 // Do nothing on exception. Some vocabulary schemas may not include a short display name.
306 if (Tools.isEmpty(shortIdentifier)) {
307 String generatedShortIdentifier =
308 AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
309 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
314 * Generate a refName for the authority item from the short identifier
317 * All refNames for authority items are generated. If a client supplies
318 * a refName, it will be overwritten during create (per this method)
319 * or discarded during update (per filterReadOnlyPropertiesForPart).
321 * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
324 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
326 String authorityRefBaseName) throws Exception {
327 DocumentModel docModel = wrapDoc.getWrappedObject();
328 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
329 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
330 if (Tools.isEmpty(authorityRefBaseName)) {
331 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
333 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
334 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
335 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
339 * Check the logic around the parent pointer. Note that we only need do this on
340 * create, since we have logic to make this read-only on update.
344 * @throws Exception the exception
346 private void handleInAuthority(DocumentModel docModel) throws Exception {
347 docModel.setProperty(authorityItemCommonSchemaName,
348 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
352 public AuthorityRefDocList getReferencingObjects(
353 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
354 ArrayList<String> serviceTypes,
356 String itemcsid) throws Exception {
357 AuthorityRefDocList authRefDocList = null;
358 RepositoryInstance repoSession = null;
359 boolean releaseRepoSession = false;
362 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
363 repoSession = this.getRepositorySession();
364 if (repoSession == null) {
365 repoSession = repoClient.getRepositorySession();
366 releaseRepoSession = true;
368 DocumentFilter myFilter = getDocumentFilter();
371 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, itemcsid);
372 DocumentModel docModel = wrapper.getWrappedObject();
373 String refName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
374 authRefDocList = RefNameServiceUtils.getAuthorityRefDocs(
375 repoSession, ctx, repoClient,
379 myFilter.getPageSize(), myFilter.getStartPage(), true /*computeTotal*/);
380 } catch (PropertyException pe) {
382 } catch (DocumentException de) {
384 } catch (Exception e) {
385 if (logger.isDebugEnabled()) {
386 logger.debug("Caught exception ", e);
388 throw new DocumentException(e);
390 if (releaseRepoSession && repoSession != null) {
391 repoClient.releaseRepositorySession(repoSession);
394 } catch (Exception e) {
395 if (logger.isDebugEnabled()) {
396 logger.debug("Caught exception ", e);
398 throw new DocumentException(e);
400 return authRefDocList;
407 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
410 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
412 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
414 // Add the CSID to the common part, since they may have fetched via the shortId.
415 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
416 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
417 unQObjectProperties.put("csid", csid);
420 return unQObjectProperties;
424 * Filters out selected values supplied in an update request.
426 * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
427 * that the link to the item's parent remains untouched.
429 * @param objectProps the properties filtered out from the update payload
430 * @param partMeta metadata for the object to fill
433 public void filterReadOnlyPropertiesForPart(
434 Map<String, Object> objectProps, ObjectPartType partMeta) {
435 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
436 String commonPartLabel = getServiceContext().getCommonPartLabel();
437 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
438 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
439 objectProps.remove(AuthorityItemJAXBSchema.CSID);
440 objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
441 objectProps.remove(AuthorityItemJAXBSchema.REF_NAME);
446 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
447 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
448 super.extractAllParts(wrapDoc);
450 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
451 if (Tools.isTrue(showSiblings)) {
452 showSiblings(wrapDoc, ctx);
453 return; // actual result is returned on ctx.addOutputPart();
456 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
457 if (Tools.isTrue(showRelations)) {
458 showRelations(wrapDoc, ctx);
459 return; // actual result is returned on ctx.addOutputPart();
462 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
463 if (Tools.isTrue(showAllRelations)) {
464 showAllRelations(wrapDoc, ctx);
465 return; // actual result is returned on ctx.addOutputPart();
469 /** @return null on parent not found
471 protected String getParentCSID(String thisCSID) throws Exception {
472 String parentCSID = null;
474 String predicate = RelationshipType.HAS_BROADER.value();
475 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
476 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
477 if (parentList != null) {
478 if (parentList.size() == 0) {
481 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
482 parentCSID = relationListItem.getObjectCsid();
485 } catch (Exception e) {
486 logger.error("Could not find parent for this: " + thisCSID, e);
491 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
492 MultipartServiceContext ctx) throws Exception {
493 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
495 String predicate = RelationshipType.HAS_BROADER.value();
496 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
497 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
499 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
500 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
502 if(logger.isTraceEnabled()) {
503 String dump = dumpLists(thisCSID, parentList, childrenList, null);
504 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
507 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
508 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
509 //Not optimal, but that's the current design spec.
511 for (RelationsCommonList.RelationListItem parent : parentList) {
512 childrenList.add(parent);
515 long childrenSize = childrenList.size();
516 childrenListOuter.setTotalItems(childrenSize);
517 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
519 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
520 ctx.addOutputPart(relationsPart);
523 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
524 MultipartServiceContext ctx) throws Exception {
525 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
526 String parentCSID = getParentCSID(thisCSID);
527 if (parentCSID == null) {
528 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
532 String predicate = RelationshipType.HAS_BROADER.value();
533 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
534 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
536 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
539 RelationsCommonList.RelationListItem item = null;
540 for (RelationsCommonList.RelationListItem sibling : siblingList) {
541 if (thisCSID.equals(sibling.getSubjectCsid())) {
542 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.
545 //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.
546 for (RelationsCommonList.RelationListItem self : toRemoveList) {
547 removeFromList(siblingList, self);
550 long siblingSize = siblingList.size();
551 siblingListOuter.setTotalItems(siblingSize);
552 siblingListOuter.setItemsInPage(siblingSize);
553 if(logger.isTraceEnabled()) {
554 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
555 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
558 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
559 ctx.addOutputPart(relationsPart);
562 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
563 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
565 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
566 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
568 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
569 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
571 if(logger.isTraceEnabled()) {
572 String dump = dumpLists(thisCSID, subjectList, objectList, null);
573 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
576 subjectList.addAll(objectList);
578 //now subjectList actually has records BOTH where thisCSID is subject and object.
579 long relatedSize = subjectList.size();
580 subjectListOuter.setTotalItems(relatedSize);
581 subjectListOuter.setItemsInPage(relatedSize);
583 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
584 ctx.addOutputPart(relationsPart);
587 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
588 super.fillAllParts(wrapDoc, action);
590 ServiceContext ctx = getServiceContext();
591 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
592 DocumentModel documentModel = (wrapDoc.getWrappedObject());
593 String itemCsid = documentModel.getName();
595 //UPDATE and CREATE will call. Updates relations part
596 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
598 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
599 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
603 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
604 super.completeCreate(wrapDoc);
605 handleRelationsPayload(wrapDoc, false);
608 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
609 super.completeUpdate(wrapDoc);
610 handleRelationsPayload(wrapDoc, true);
611 handleItemRefNameReferenceUpdate();
614 // Note that we must do this after we have completed the Update, so that the repository has the
615 // info for the item itself. The relations code must call into the repo to get info for each end.
616 // This could be optimized to pass in the parent docModel, since it will often be one end.
617 // Nevertheless, we should complete the item save before we do work on the relations, especially
618 // since a save on Create might fail, and we would not want to create relations for something
619 // that may not be created...
620 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
621 ServiceContext ctx = getServiceContext();
622 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
623 DocumentModel documentModel = (wrapDoc.getWrappedObject());
624 String itemCsid = documentModel.getName();
626 //Updates relations part
627 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
629 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
630 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
632 //now we add part for relations list
633 //ServiceContext ctx = getServiceContext();
634 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
635 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
638 /** updateRelations strategy:
640 go through inboundList, remove anything from childList that matches from childList
641 go through inboundList, remove anything from parentList that matches from parentList
642 go through parentList, delete all remaining
643 go through childList, delete all remaining
644 go through actionList, add all remaining.
645 check for duplicate children
646 check for more than one parent.
648 inboundList parentList childList actionList
649 ---------------- --------------- ---------------- ----------------
650 child-a parent-c child-a child-b
651 child-b parent-d child-c
654 private RelationsCommonList updateRelations(
655 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
657 if (logger.isTraceEnabled()) {
658 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
660 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
662 return null; //nothing to do--they didn't send a list of relations.
664 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
665 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
666 List<RelationsCommonList.RelationListItem> actionList = newList();
667 List<RelationsCommonList.RelationListItem> childList = null;
668 List<RelationsCommonList.RelationListItem> parentList = null;
669 DocumentModel docModel = wrapDoc.getWrappedObject();
670 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
672 ServiceContext ctx = getServiceContext();
673 //Do magic replacement of ${itemCSID} and fix URI's.
674 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
676 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
677 UriInfo uriInfo = ctx.getUriInfo();
678 MultivaluedMap queryParams = uriInfo.getQueryParameters();
681 //Run getList() once as sent to get childListOuter:
682 String predicate = RelationshipType.HAS_BROADER.value();
683 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
684 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
685 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
686 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
687 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
688 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
690 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
691 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
692 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
693 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
694 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
697 childList = childListOuter.getRelationListItem();
698 parentList = parentListOuter.getRelationListItem();
700 if (parentList.size() > 1) {
701 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
704 if (logger.isTraceEnabled()) {
705 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
710 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
711 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
712 // and so the CSID for those may be null
713 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
714 // Look for parents and children
715 if(itemCSID.equals(inboundItem.getObject().getCsid())
716 || itemRefName.equals(inboundItem.getObject().getRefName())) {
717 //then this is an item that says we have a child. That child is inboundItem
718 RelationsCommonList.RelationListItem childItem =
719 (childList == null) ? null : findInList(childList, inboundItem);
720 if (childItem != null) {
721 if (logger.isTraceEnabled()) {
722 StringBuilder sb = new StringBuilder();
723 itemToString(sb, "== Child: ", childItem);
724 logger.trace("Found inboundChild in current child list: " + sb.toString());
726 removeFromList(childList, childItem); //exists, just take it off delete list
728 if (logger.isTraceEnabled()) {
729 StringBuilder sb = new StringBuilder();
730 itemToString(sb, "== Child: ", inboundItem);
731 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
733 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
734 String newChildCsid = inboundItem.getSubject().getCsid();
735 if(newChildCsid == null) {
736 String newChildRefName = inboundItem.getSubject().getRefName();
737 if(newChildRefName==null) {
738 throw new RuntimeException("Child with no CSID or refName!");
740 if (logger.isTraceEnabled()) {
741 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
743 DocumentModel newChildDocModel =
744 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
745 newChildRefName, getServiceContext().getResourceMap());
746 newChildCsid = getCsid(newChildDocModel);
748 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
751 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
752 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
753 //then this is an item that says we have a parent. inboundItem is that parent.
754 RelationsCommonList.RelationListItem parentItem =
755 (parentList == null) ? null : findInList(parentList, inboundItem);
756 if (parentItem != null) {
757 removeFromList(parentList, parentItem); //exists, just take it off delete list
759 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
762 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
765 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
768 if (logger.isTraceEnabled()) {
769 String dump = dumpLists(itemCSID, parentList, childList, actionList);
770 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
773 if (logger.isTraceEnabled()) {
774 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
775 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
777 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
778 deleteRelations(childList, ctx, "childList");
780 if (logger.isTraceEnabled()) {
781 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
782 + actionList.size() + " new parents and children.");
784 createRelations(actionList, ctx);
785 if (logger.isTraceEnabled()) {
786 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
788 //We return all elements on the inbound list, since we have just worked to make them exist in the system
789 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
790 return relationsCommonListBody;
793 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
794 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
795 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
796 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
797 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
798 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
799 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
800 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
801 deleteRelations(parentList, ctx, "parentList-delete");
805 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
807 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
809 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
811 sb.append(item.getPredicate());
813 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
817 private String dumpLists(String itemCSID,
818 List<RelationsCommonList.RelationListItem> parentList,
819 List<RelationsCommonList.RelationListItem> childList,
820 List<RelationsCommonList.RelationListItem> actionList) {
821 StringBuilder sb = new StringBuilder();
822 sb.append("itemCSID: " + itemCSID + CR);
823 if(parentList!=null) {
824 sb.append(dumpList(parentList, "parentList"));
826 if(childList!=null) {
827 sb.append(dumpList(childList, "childList"));
829 if(actionList!=null) {
830 sb.append(dumpList(actionList, "actionList"));
832 return sb.toString();
834 private final static String CR = "\r\n";
835 private final static String T = " ";
837 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
838 StringBuilder sb = new StringBuilder();
840 if (list.size() > 0) {
841 sb.append("=========== " + label + " ==========" + CR);
843 for (RelationsCommonList.RelationListItem item : list) {
844 itemToString(sb, "== ", item);
847 return sb.toString();
850 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
851 * and sets URI correctly for related items.
852 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
854 protected void fixupInboundListItems(ServiceContext ctx,
855 List<RelationsCommonList.RelationListItem> inboundList,
856 DocumentModel docModel,
857 String itemCSID) throws Exception {
858 String thisURI = this.getUri(docModel);
859 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
860 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
861 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
862 RelationsDocListItem inboundItemObject = inboundItem.getObject();
863 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
865 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
866 inboundItem.setObjectCsid(itemCSID);
867 inboundItemObject.setCsid(itemCSID);
868 //inboundItemObject.setUri(getUri(docModel));
871 String objectCsid = inboundItemObject.getCsid();
872 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
873 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
874 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
875 inboundItemObject.setUri(uri); //CSPACE-4037
878 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
880 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
881 inboundItem.setSubjectCsid(itemCSID);
882 inboundItemSubject.setCsid(itemCSID);
883 //inboundItemSubject.setUri(getUri(docModel));
886 String subjectCsid = inboundItemSubject.getCsid();
887 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
888 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
889 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
890 inboundItemSubject.setUri(uri); //CSPACE-4037
893 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
898 // this method calls the RelationResource to have it create the relations and persist them.
899 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
900 for (RelationsCommonList.RelationListItem item : inboundList) {
901 RelationsCommon rc = new RelationsCommon();
902 //rc.setCsid(item.getCsid());
903 //todo: assignTo(item, rc);
904 RelationsDocListItem itemSubject = item.getSubject();
905 RelationsDocListItem itemObject = item.getObject();
907 // Set at least one of CSID and refName for Subject and Object
908 // Either value might be null for for each of Subject and Object
909 String subjectCsid = itemSubject.getCsid();
910 rc.setSubjectCsid(subjectCsid);
912 String objCsid = itemObject.getCsid();
913 rc.setObjectCsid(objCsid);
915 rc.setSubjectRefName(itemSubject.getRefName());
916 rc.setObjectRefName(itemObject.getRefName());
918 rc.setRelationshipType(item.getPredicate());
919 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
920 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
922 // This is superfluous, since it will be fetched by the Relations Create logic.
923 rc.setSubjectDocumentType(itemSubject.getDocumentType());
924 rc.setObjectDocumentType(itemObject.getDocumentType());
926 // This is superfluous, since it will be fetched by the Relations Create logic.
927 rc.setSubjectUri(itemSubject.getUri());
928 rc.setObjectUri(itemObject.getUri());
929 // May not have the info here. Only really require CSID or refName.
930 // Rest is handled in the Relation create mechanism
931 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
933 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
934 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
935 payloadOut.addPart(outputPart);
936 RelationResource relationResource = new RelationResource();
937 Object res = relationResource.create(ctx.getResourceMap(),
938 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
942 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
944 for (RelationsCommonList.RelationListItem item : list) {
945 RelationResource relationResource = new RelationResource();
946 if(logger.isTraceEnabled()) {
947 StringBuilder sb = new StringBuilder();
948 itemToString(sb, "==== TO DELETE: ", item);
949 logger.trace(sb.toString());
951 Object res = relationResource.delete(item.getCsid());
953 } catch (Throwable t) {
954 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
959 private List<RelationsCommonList.RelationListItem> newList() {
960 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
964 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
965 List<RelationsCommonList.RelationListItem> result = newList();
966 for (RelationsCommonList.RelationListItem item : inboundList) {
972 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
973 // But the list items must not be sparse
974 private RelationsCommonList.RelationListItem findInList(
975 List<RelationsCommonList.RelationListItem> list,
976 RelationsCommonList.RelationListItem item) {
977 RelationsCommonList.RelationListItem foundItem = null;
978 for (RelationsCommonList.RelationListItem listItem : list) {
979 if (itemsEqual(listItem, item)) { //equals must be defined, else
980 foundItem = listItem;
987 // Note that item2 may be sparse (only refName, no CSID for subject or object)
988 // But item1 must not be sparse
989 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
990 if (item1 == null || item2 == null) {
993 RelationsDocListItem subj1 = item1.getSubject();
994 RelationsDocListItem subj2 = item2.getSubject();
995 RelationsDocListItem obj1 = item1.getObject();
996 RelationsDocListItem obj2 = item2.getObject();
997 String subj1Csid = subj1.getCsid();
998 String subj2Csid = subj2.getCsid();
999 String subj1RefName = subj1.getRefName();
1000 String subj2RefName = subj2.getRefName();
1002 String obj1Csid = obj1.getCsid();
1003 String obj2Csid = obj2.getCsid();
1004 String obj1RefName = obj1.getRefName();
1005 String obj2RefName = obj2.getRefName();
1008 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1009 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1010 // predicate is proper, but still allow relationshipType
1011 && (item1.getPredicate().equals(item2.getPredicate())
1012 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1013 // Allow missing docTypes, so long as they do not conflict
1014 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1015 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1019 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1023 /* don't even THINK of re-using this method.
1024 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
1026 private String extractInAuthorityCSID(String uri) {
1027 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
1028 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
1029 Matcher m = p.matcher(uri);
1031 if (m.groupCount() < 3) {
1032 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
1035 //String service = m.group(1);
1036 String inauth = m.group(2);
1037 //String theRest = m.group(3);
1039 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
1042 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
1047 //ensures CSPACE-4042
1048 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
1049 String authorityCSID = extractInAuthorityCSID(thisURI);
1050 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
1051 if (Tools.isBlank(authorityCSID)
1052 || Tools.isBlank(authorityCSIDForInbound)
1053 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
1054 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
1058 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1059 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1060 ServiceContext ctx = getServiceContext();
1061 MultivaluedMap queryParams = ctx.getQueryParams();
1062 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1063 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1064 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1066 RelationResource relationResource = new RelationResource();
1067 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
1068 return relationsCommonList;
1070 //============================= END TODO refactor ==========================