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.vocabulary.AuthorityJAXBSchema;
49 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
50 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
51 import org.collectionspace.services.config.service.ListResultField;
52 import org.collectionspace.services.config.service.ObjectPartType;
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.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.nuxeo.runtime.transaction.TransactionHelper;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
69 import javax.ws.rs.PathParam;
70 import javax.ws.rs.WebApplicationException;
71 import javax.ws.rs.core.Context;
72 import javax.ws.rs.core.MultivaluedMap;
73 import javax.ws.rs.core.Response;
74 import javax.ws.rs.core.UriInfo;
75 import java.util.ArrayList;
76 import java.util.List;
78 import java.util.regex.Matcher;
79 import java.util.regex.Pattern;
81 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
83 * AuthorityItemDocumentModelHandler
85 * $LastChangedRevision: $
88 public abstract class AuthorityItemDocumentModelHandler<AICommon>
89 extends DocHandlerBase<AICommon> {
91 private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
92 private String authorityItemCommonSchemaName;
94 * inVocabulary is the parent Authority for this context
96 protected String inAuthority;
97 protected String authorityRefNameBase;
98 // Used to determine when the displayName changes as part of the update.
99 protected String oldDisplayNameOnUpdate = null;
100 protected String oldRefNameOnUpdate = null;
101 protected String newRefNameOnUpdate = null;
103 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
104 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
107 public String getInAuthority() {
111 public void setInAuthority(String inAuthority) {
112 this.inAuthority = inAuthority;
115 /** Subclasses may override this to customize the URI segment. */
116 public String getAuthorityServicePath() {
117 return getServiceContext().getServiceName().toLowerCase(); // Laramie20110510 CSPACE-3932
121 public String getUri(DocumentModel docModel) {
122 // Laramie20110510 CSPACE-3932
123 String authorityServicePath = getAuthorityServicePath();
124 return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
127 public String getAuthorityRefNameBase() {
128 return this.authorityRefNameBase;
131 public void setAuthorityRefNameBase(String value) {
132 this.authorityRefNameBase = value;
136 public List<ListResultField> getListItemsArray() throws DocumentException {
137 List<ListResultField> list = super.getListItemsArray();
138 int nFields = list.size();
139 // Ensure some common fields so do not depend upon config for general logic
140 boolean hasDisplayName = false;
141 boolean hasShortId = false;
142 boolean hasRefName = false;
143 boolean hasTermStatus = false;
144 for (int i = 0; i < nFields; i++) {
145 ListResultField field = list.get(i);
146 String elName = field.getElement();
147 if (AuthorityItemJAXBSchema.DISPLAY_NAME.equals(elName)) {
148 hasDisplayName = true;
149 } else if (AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName)) {
151 } else if (AuthorityItemJAXBSchema.REF_NAME.equals(elName)) {
153 } else if (AuthorityItemJAXBSchema.TERM_STATUS.equals(elName)) {
154 hasTermStatus = true;
157 ListResultField field;
158 if (!hasDisplayName) {
159 field = new ListResultField();
160 field.setElement(AuthorityItemJAXBSchema.DISPLAY_NAME);
161 field.setXpath(AuthorityItemJAXBSchema.DISPLAY_NAME);
165 field = new ListResultField();
166 field.setElement(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
167 field.setXpath(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
171 field = new ListResultField();
172 field.setElement(AuthorityItemJAXBSchema.REF_NAME);
173 field.setXpath(AuthorityItemJAXBSchema.REF_NAME);
176 if (!hasTermStatus) {
177 field = new ListResultField();
178 field.setElement(AuthorityItemJAXBSchema.TERM_STATUS);
179 field.setXpath(AuthorityItemJAXBSchema.TERM_STATUS);
188 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
191 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
192 // first fill all the parts of the document
193 super.handleCreate(wrapDoc);
194 // Ensure we have required fields set properly
195 handleInAuthority(wrapDoc.getWrappedObject());
197 handleComputedDisplayNames(wrapDoc.getWrappedObject());
198 String displayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
199 AuthorityItemJAXBSchema.DISPLAY_NAME);
200 if (Tools.isEmpty(displayName)) {
201 logger.warn("Creating Authority Item with no displayName!");
204 handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
205 // refName includes displayName, so we force a correct value here.
206 updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
210 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
213 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
214 // First, get a copy of the old displayName
215 oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
216 AuthorityItemJAXBSchema.DISPLAY_NAME);
217 oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
218 AuthorityItemJAXBSchema.REF_NAME);
219 super.handleUpdate(wrapDoc);
220 handleComputedDisplayNames(wrapDoc.getWrappedObject());
221 String newDisplayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
222 AuthorityItemJAXBSchema.DISPLAY_NAME);
223 if (newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
224 // Need to update the refName, and then fix all references.
225 newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
227 // Mark as not needing attention in completeUpdate phase.
228 newRefNameOnUpdate = null;
229 oldRefNameOnUpdate = null;
234 * Handle display name.
236 * @param docModel the doc model
237 * @throws Exception the exception
239 protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
240 // Do nothing by default.
244 * Handle refName updates for changes to display name.
245 * Assumes refName is already correct. Just ensures it is right.
247 * @param docModel the doc model
248 * @param newDisplayName the new display name
249 * @throws Exception the exception
251 protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
252 String newDisplayName) throws Exception {
253 RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
254 if (authItem == null) {
255 String err = "Authority Item has illegal refName: " + oldRefNameOnUpdate;
257 throw new IllegalArgumentException(err);
259 authItem.displayName = newDisplayName;
260 String updatedRefName = authItem.toString();
261 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
262 return updatedRefName;
265 protected String getRefPropName() {
266 return ServiceBindingUtils.AUTH_REF_PROP;
270 * Checks to see if the refName has changed, and if so,
271 * uses utilities to find all references and update them.
274 protected void handleItemRefNameReferenceUpdate() throws Exception {
275 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
276 // We have work to do.
277 if (logger.isDebugEnabled()) {
278 String eol = System.getProperty("line.separator");
279 logger.debug("Need to find and update references to Item." + eol
280 + " Old refName" + oldRefNameOnUpdate + eol
281 + " New refName" + newRefNameOnUpdate);
283 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
284 RepositoryClient repoClient = getRepositoryClient(ctx);
285 String refNameProp = getRefPropName();
287 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
288 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
289 if (logger.isDebugEnabled()) {
290 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
296 * If no short identifier was provided in the input payload,
297 * generate a short identifier from the display name.
299 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
300 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
301 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
302 String shortDisplayName = "";
304 shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
305 } catch (PropertyNotFoundException pnfe) {
306 // Do nothing on exception. Some vocabulary schemas may not include a short display name.
308 if (Tools.isEmpty(shortIdentifier)) {
309 String generatedShortIdentifier =
310 AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
311 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
316 * Generate a refName for the authority item from the short identifier
319 * All refNames for authority items are generated. If a client supplies
320 * a refName, it will be overwritten during create (per this method)
321 * or discarded during update (per filterReadOnlyPropertiesForPart).
323 * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
326 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
328 String authorityRefBaseName) throws Exception {
329 DocumentModel docModel = wrapDoc.getWrappedObject();
330 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
331 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
332 if (Tools.isEmpty(authorityRefBaseName)) {
333 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
335 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
336 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
337 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
341 * Check the logic around the parent pointer. Note that we only need do this on
342 * create, since we have logic to make this read-only on update.
346 * @throws Exception the exception
348 private void handleInAuthority(DocumentModel docModel) throws Exception {
349 docModel.setProperty(authorityItemCommonSchemaName,
350 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
354 public AuthorityRefDocList getReferencingObjects(
355 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
356 List<String> serviceTypes,
358 String itemcsid) throws Exception {
359 AuthorityRefDocList authRefDocList = null;
360 RepositoryInstance repoSession = null;
361 boolean releaseRepoSession = false;
364 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
365 repoSession = this.getRepositorySession();
366 if (repoSession == null) {
367 repoSession = repoClient.getRepositorySession();
368 releaseRepoSession = true;
370 DocumentFilter myFilter = getDocumentFilter();
373 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, itemcsid);
374 DocumentModel docModel = wrapper.getWrappedObject();
375 String refName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
376 authRefDocList = RefNameServiceUtils.getAuthorityRefDocs(
377 repoSession, ctx, repoClient,
381 myFilter.getPageSize(), myFilter.getStartPage(), true /*computeTotal*/);
382 } catch (PropertyException pe) {
384 } catch (DocumentException de) {
386 } catch (Exception e) {
387 if (logger.isDebugEnabled()) {
388 logger.debug("Caught exception ", e);
390 throw new DocumentException(e);
392 if (releaseRepoSession && repoSession != null) {
393 repoClient.releaseRepositorySession(repoSession);
396 } catch (Exception e) {
397 if (logger.isDebugEnabled()) {
398 logger.debug("Caught exception ", e);
400 throw new DocumentException(e);
402 return authRefDocList;
409 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
412 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
414 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
416 // Add the CSID to the common part, since they may have fetched via the shortId.
417 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
418 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
419 unQObjectProperties.put("csid", csid);
422 return unQObjectProperties;
426 * Filters out selected values supplied in an update request.
428 * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
429 * that the link to the item's parent remains untouched.
431 * @param objectProps the properties filtered out from the update payload
432 * @param partMeta metadata for the object to fill
435 public void filterReadOnlyPropertiesForPart(
436 Map<String, Object> objectProps, ObjectPartType partMeta) {
437 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
438 String commonPartLabel = getServiceContext().getCommonPartLabel();
439 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
440 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
441 objectProps.remove(AuthorityItemJAXBSchema.CSID);
442 objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
443 objectProps.remove(AuthorityItemJAXBSchema.REF_NAME);
448 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
449 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
450 super.extractAllParts(wrapDoc);
452 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
453 if (Tools.isTrue(showSiblings)) {
454 showSiblings(wrapDoc, ctx);
455 return; // actual result is returned on ctx.addOutputPart();
458 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
459 if (Tools.isTrue(showRelations)) {
460 showRelations(wrapDoc, ctx);
461 return; // actual result is returned on ctx.addOutputPart();
464 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
465 if (Tools.isTrue(showAllRelations)) {
466 showAllRelations(wrapDoc, ctx);
467 return; // actual result is returned on ctx.addOutputPart();
471 /** @return null on parent not found
473 protected String getParentCSID(String thisCSID) throws Exception {
474 String parentCSID = null;
476 String predicate = RelationshipType.HAS_BROADER.value();
477 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
478 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
479 if (parentList != null) {
480 if (parentList.size() == 0) {
483 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
484 parentCSID = relationListItem.getObjectCsid();
487 } catch (Exception e) {
488 logger.error("Could not find parent for this: " + thisCSID, e);
493 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
494 MultipartServiceContext ctx) throws Exception {
495 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
497 String predicate = RelationshipType.HAS_BROADER.value();
498 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
499 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
501 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
502 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
504 if(logger.isTraceEnabled()) {
505 String dump = dumpLists(thisCSID, parentList, childrenList, null);
506 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
509 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
510 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
511 //Not optimal, but that's the current design spec.
513 for (RelationsCommonList.RelationListItem parent : parentList) {
514 childrenList.add(parent);
517 long childrenSize = childrenList.size();
518 childrenListOuter.setTotalItems(childrenSize);
519 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
521 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
522 ctx.addOutputPart(relationsPart);
525 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
526 MultipartServiceContext ctx) throws Exception {
527 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
528 String parentCSID = getParentCSID(thisCSID);
529 if (parentCSID == null) {
530 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
534 String predicate = RelationshipType.HAS_BROADER.value();
535 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
536 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
538 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
541 RelationsCommonList.RelationListItem item = null;
542 for (RelationsCommonList.RelationListItem sibling : siblingList) {
543 if (thisCSID.equals(sibling.getSubjectCsid())) {
544 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.
547 //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.
548 for (RelationsCommonList.RelationListItem self : toRemoveList) {
549 removeFromList(siblingList, self);
552 long siblingSize = siblingList.size();
553 siblingListOuter.setTotalItems(siblingSize);
554 siblingListOuter.setItemsInPage(siblingSize);
555 if(logger.isTraceEnabled()) {
556 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
557 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
560 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
561 ctx.addOutputPart(relationsPart);
564 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
565 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
567 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
568 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
570 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
571 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
573 if(logger.isTraceEnabled()) {
574 String dump = dumpLists(thisCSID, subjectList, objectList, null);
575 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
578 subjectList.addAll(objectList);
580 //now subjectList actually has records BOTH where thisCSID is subject and object.
581 long relatedSize = subjectList.size();
582 subjectListOuter.setTotalItems(relatedSize);
583 subjectListOuter.setItemsInPage(relatedSize);
585 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
586 ctx.addOutputPart(relationsPart);
589 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
590 super.fillAllParts(wrapDoc, action);
592 ServiceContext ctx = getServiceContext();
593 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
594 DocumentModel documentModel = (wrapDoc.getWrappedObject());
595 String itemCsid = documentModel.getName();
597 //UPDATE and CREATE will call. Updates relations part
598 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
600 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
601 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
605 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
606 super.completeCreate(wrapDoc);
607 handleRelationsPayload(wrapDoc, false);
610 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
611 super.completeUpdate(wrapDoc);
612 handleRelationsPayload(wrapDoc, true);
613 handleItemRefNameReferenceUpdate();
616 // Note that we must do this after we have completed the Update, so that the repository has the
617 // info for the item itself. The relations code must call into the repo to get info for each end.
618 // This could be optimized to pass in the parent docModel, since it will often be one end.
619 // Nevertheless, we should complete the item save before we do work on the relations, especially
620 // since a save on Create might fail, and we would not want to create relations for something
621 // that may not be created...
622 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
623 ServiceContext ctx = getServiceContext();
624 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
625 DocumentModel documentModel = (wrapDoc.getWrappedObject());
626 String itemCsid = documentModel.getName();
628 //Updates relations part
629 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
631 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
632 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
634 //now we add part for relations list
635 //ServiceContext ctx = getServiceContext();
636 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
637 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
640 /** updateRelations strategy:
642 go through inboundList, remove anything from childList that matches from childList
643 go through inboundList, remove anything from parentList that matches from parentList
644 go through parentList, delete all remaining
645 go through childList, delete all remaining
646 go through actionList, add all remaining.
647 check for duplicate children
648 check for more than one parent.
650 inboundList parentList childList actionList
651 ---------------- --------------- ---------------- ----------------
652 child-a parent-c child-a child-b
653 child-b parent-d child-c
656 private RelationsCommonList updateRelations(
657 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
659 if (logger.isTraceEnabled()) {
660 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
662 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
664 return null; //nothing to do--they didn't send a list of relations.
666 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
667 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
668 List<RelationsCommonList.RelationListItem> actionList = newList();
669 List<RelationsCommonList.RelationListItem> childList = null;
670 List<RelationsCommonList.RelationListItem> parentList = null;
671 DocumentModel docModel = wrapDoc.getWrappedObject();
672 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
674 ServiceContext ctx = getServiceContext();
675 //Do magic replacement of ${itemCSID} and fix URI's.
676 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
678 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
679 UriInfo uriInfo = ctx.getUriInfo();
680 MultivaluedMap queryParams = uriInfo.getQueryParameters();
683 //Run getList() once as sent to get childListOuter:
684 String predicate = RelationshipType.HAS_BROADER.value();
685 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
686 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
687 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
688 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
689 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
690 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
692 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
693 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
694 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
695 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
696 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
699 childList = childListOuter.getRelationListItem();
700 parentList = parentListOuter.getRelationListItem();
702 if (parentList.size() > 1) {
703 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
706 if (logger.isTraceEnabled()) {
707 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
712 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
713 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
714 // and so the CSID for those may be null
715 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
716 // Look for parents and children
717 if(itemCSID.equals(inboundItem.getObject().getCsid())
718 || itemRefName.equals(inboundItem.getObject().getRefName())) {
719 //then this is an item that says we have a child. That child is inboundItem
720 RelationsCommonList.RelationListItem childItem =
721 (childList == null) ? null : findInList(childList, inboundItem);
722 if (childItem != null) {
723 if (logger.isTraceEnabled()) {
724 StringBuilder sb = new StringBuilder();
725 itemToString(sb, "== Child: ", childItem);
726 logger.trace("Found inboundChild in current child list: " + sb.toString());
728 removeFromList(childList, childItem); //exists, just take it off delete list
730 if (logger.isTraceEnabled()) {
731 StringBuilder sb = new StringBuilder();
732 itemToString(sb, "== Child: ", inboundItem);
733 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
735 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
736 String newChildCsid = inboundItem.getSubject().getCsid();
737 if(newChildCsid == null) {
738 String newChildRefName = inboundItem.getSubject().getRefName();
739 if(newChildRefName==null) {
740 throw new RuntimeException("Child with no CSID or refName!");
742 if (logger.isTraceEnabled()) {
743 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
745 DocumentModel newChildDocModel =
746 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
747 newChildRefName, getServiceContext().getResourceMap());
748 newChildCsid = getCsid(newChildDocModel);
750 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
753 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
754 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
755 //then this is an item that says we have a parent. inboundItem is that parent.
756 RelationsCommonList.RelationListItem parentItem =
757 (parentList == null) ? null : findInList(parentList, inboundItem);
758 if (parentItem != null) {
759 removeFromList(parentList, parentItem); //exists, just take it off delete list
761 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
764 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
767 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
770 if (logger.isTraceEnabled()) {
771 String dump = dumpLists(itemCSID, parentList, childList, actionList);
772 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
775 if (logger.isTraceEnabled()) {
776 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
777 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
779 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
780 deleteRelations(childList, ctx, "childList");
782 if (logger.isTraceEnabled()) {
783 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
784 + actionList.size() + " new parents and children.");
786 createRelations(actionList, ctx);
787 if (logger.isTraceEnabled()) {
788 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
790 //We return all elements on the inbound list, since we have just worked to make them exist in the system
791 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
792 return relationsCommonListBody;
795 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
796 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
797 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
798 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
799 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
800 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
801 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
802 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
803 deleteRelations(parentList, ctx, "parentList-delete");
807 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
809 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
811 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
813 sb.append(item.getPredicate());
815 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
819 private String dumpLists(String itemCSID,
820 List<RelationsCommonList.RelationListItem> parentList,
821 List<RelationsCommonList.RelationListItem> childList,
822 List<RelationsCommonList.RelationListItem> actionList) {
823 StringBuilder sb = new StringBuilder();
824 sb.append("itemCSID: " + itemCSID + CR);
825 if(parentList!=null) {
826 sb.append(dumpList(parentList, "parentList"));
828 if(childList!=null) {
829 sb.append(dumpList(childList, "childList"));
831 if(actionList!=null) {
832 sb.append(dumpList(actionList, "actionList"));
834 return sb.toString();
836 private final static String CR = "\r\n";
837 private final static String T = " ";
839 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
840 StringBuilder sb = new StringBuilder();
842 if (list.size() > 0) {
843 sb.append("=========== " + label + " ==========" + CR);
845 for (RelationsCommonList.RelationListItem item : list) {
846 itemToString(sb, "== ", item);
849 return sb.toString();
852 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
853 * and sets URI correctly for related items.
854 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
856 protected void fixupInboundListItems(ServiceContext ctx,
857 List<RelationsCommonList.RelationListItem> inboundList,
858 DocumentModel docModel,
859 String itemCSID) throws Exception {
860 String thisURI = this.getUri(docModel);
861 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
862 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
863 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
864 RelationsDocListItem inboundItemObject = inboundItem.getObject();
865 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
867 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
868 inboundItem.setObjectCsid(itemCSID);
869 inboundItemObject.setCsid(itemCSID);
870 //inboundItemObject.setUri(getUri(docModel));
873 String objectCsid = inboundItemObject.getCsid();
874 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
875 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
876 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
877 inboundItemObject.setUri(uri); //CSPACE-4037
880 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
882 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
883 inboundItem.setSubjectCsid(itemCSID);
884 inboundItemSubject.setCsid(itemCSID);
885 //inboundItemSubject.setUri(getUri(docModel));
888 String subjectCsid = inboundItemSubject.getCsid();
889 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
890 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
891 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
892 inboundItemSubject.setUri(uri); //CSPACE-4037
895 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
900 // this method calls the RelationResource to have it create the relations and persist them.
901 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
902 for (RelationsCommonList.RelationListItem item : inboundList) {
903 RelationsCommon rc = new RelationsCommon();
904 //rc.setCsid(item.getCsid());
905 //todo: assignTo(item, rc);
906 RelationsDocListItem itemSubject = item.getSubject();
907 RelationsDocListItem itemObject = item.getObject();
909 // Set at least one of CSID and refName for Subject and Object
910 // Either value might be null for for each of Subject and Object
911 String subjectCsid = itemSubject.getCsid();
912 rc.setSubjectCsid(subjectCsid);
914 String objCsid = itemObject.getCsid();
915 rc.setObjectCsid(objCsid);
917 rc.setSubjectRefName(itemSubject.getRefName());
918 rc.setObjectRefName(itemObject.getRefName());
920 rc.setRelationshipType(item.getPredicate());
921 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
922 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
924 // This is superfluous, since it will be fetched by the Relations Create logic.
925 rc.setSubjectDocumentType(itemSubject.getDocumentType());
926 rc.setObjectDocumentType(itemObject.getDocumentType());
928 // This is superfluous, since it will be fetched by the Relations Create logic.
929 rc.setSubjectUri(itemSubject.getUri());
930 rc.setObjectUri(itemObject.getUri());
931 // May not have the info here. Only really require CSID or refName.
932 // Rest is handled in the Relation create mechanism
933 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
935 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
936 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
937 payloadOut.addPart(outputPart);
938 RelationResource relationResource = new RelationResource();
939 Object res = relationResource.create(ctx.getResourceMap(),
940 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
944 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
946 for (RelationsCommonList.RelationListItem item : list) {
947 RelationResource relationResource = new RelationResource();
948 if(logger.isTraceEnabled()) {
949 StringBuilder sb = new StringBuilder();
950 itemToString(sb, "==== TO DELETE: ", item);
951 logger.trace(sb.toString());
953 Object res = relationResource.delete(item.getCsid());
955 } catch (Throwable t) {
956 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
961 private List<RelationsCommonList.RelationListItem> newList() {
962 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
966 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
967 List<RelationsCommonList.RelationListItem> result = newList();
968 for (RelationsCommonList.RelationListItem item : inboundList) {
974 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
975 // But the list items must not be sparse
976 private RelationsCommonList.RelationListItem findInList(
977 List<RelationsCommonList.RelationListItem> list,
978 RelationsCommonList.RelationListItem item) {
979 RelationsCommonList.RelationListItem foundItem = null;
980 for (RelationsCommonList.RelationListItem listItem : list) {
981 if (itemsEqual(listItem, item)) { //equals must be defined, else
982 foundItem = listItem;
989 // Note that item2 may be sparse (only refName, no CSID for subject or object)
990 // But item1 must not be sparse
991 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
992 if (item1 == null || item2 == null) {
995 RelationsDocListItem subj1 = item1.getSubject();
996 RelationsDocListItem subj2 = item2.getSubject();
997 RelationsDocListItem obj1 = item1.getObject();
998 RelationsDocListItem obj2 = item2.getObject();
999 String subj1Csid = subj1.getCsid();
1000 String subj2Csid = subj2.getCsid();
1001 String subj1RefName = subj1.getRefName();
1002 String subj2RefName = subj2.getRefName();
1004 String obj1Csid = obj1.getCsid();
1005 String obj2Csid = obj2.getCsid();
1006 String obj1RefName = obj1.getRefName();
1007 String obj2RefName = obj2.getRefName();
1010 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1011 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1012 // predicate is proper, but still allow relationshipType
1013 && (item1.getPredicate().equals(item2.getPredicate())
1014 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1015 // Allow missing docTypes, so long as they do not conflict
1016 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1017 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1021 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1025 /* don't even THINK of re-using this method.
1026 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
1028 private String extractInAuthorityCSID(String uri) {
1029 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
1030 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
1031 Matcher m = p.matcher(uri);
1033 if (m.groupCount() < 3) {
1034 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
1037 //String service = m.group(1);
1038 String inauth = m.group(2);
1039 //String theRest = m.group(3);
1041 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
1044 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
1049 //ensures CSPACE-4042
1050 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
1051 String authorityCSID = extractInAuthorityCSID(thisURI);
1052 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
1053 if (Tools.isBlank(authorityCSID)
1054 || Tools.isBlank(authorityCSIDForInbound)
1055 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
1056 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
1060 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1061 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1062 ServiceContext ctx = getServiceContext();
1063 MultivaluedMap queryParams = ctx.getQueryParams();
1064 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1065 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1066 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1068 RelationResource relationResource = new RelationResource();
1069 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
1070 return relationsCommonList;
1072 //============================= END TODO refactor ==========================