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.api.CommonAPI;
33 import org.collectionspace.services.common.api.RefName;
34 import org.collectionspace.services.common.api.Tools;
35 import org.collectionspace.services.common.context.MultipartServiceContext;
36 import org.collectionspace.services.common.context.ServiceContext;
37 import org.collectionspace.services.common.document.DocumentWrapper;
38 import org.collectionspace.services.common.document.DocumentWrapperImpl;
39 import org.collectionspace.services.common.relation.IRelationsManager;
40 import org.collectionspace.services.common.repository.RepositoryClient;
41 import org.collectionspace.services.common.repository.RepositoryClientFactory;
42 import org.collectionspace.services.common.service.ObjectPartType;
43 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
44 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
45 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
46 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
47 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
48 import org.collectionspace.services.relation.RelationResource;
49 import org.collectionspace.services.relation.RelationsCommon;
50 import org.collectionspace.services.relation.RelationsCommonList;
51 import org.collectionspace.services.relation.RelationsDocListItem;
52 import org.collectionspace.services.relation.RelationshipType;
53 import org.nuxeo.ecm.core.api.DocumentModel;
54 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
58 import javax.ws.rs.core.MultivaluedMap;
59 import javax.ws.rs.core.UriInfo;
60 import java.util.ArrayList;
61 import java.util.List;
63 import java.util.regex.Matcher;
64 import java.util.regex.Pattern;
66 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
68 * AuthorityItemDocumentModelHandler
70 * $LastChangedRevision: $
73 public abstract class AuthorityItemDocumentModelHandler<AICommon>
74 extends DocHandlerBase<AICommon> {
76 private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
77 private String authorityItemCommonSchemaName;
79 * inVocabulary is the parent Authority for this context
81 protected String inAuthority;
82 protected String authorityRefNameBase;
84 // Used to determine when the displayName changes as part of the update.
85 protected String oldDisplayNameOnUpdate = null;
86 protected String oldRefNameOnUpdate = null;
87 protected String newRefNameOnUpdate = null;
89 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
90 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
93 public String getInAuthority() {
97 public void setInAuthority(String inAuthority) {
98 this.inAuthority = inAuthority;
101 /** Subclasses may override this to customize the URI segment. */
102 public String getAuthorityServicePath() {
103 return getServiceContext().getServiceName().toLowerCase(); // Laramie20110510 CSPACE-3932
107 public String getUri(DocumentModel docModel) {
108 // Laramie20110510 CSPACE-3932
109 String authorityServicePath = getAuthorityServicePath();
110 return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
113 public String getAuthorityRefNameBase() {
114 return this.authorityRefNameBase;
117 public void setAuthorityRefNameBase(String value) {
118 this.authorityRefNameBase = value;
122 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
125 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
126 // first fill all the parts of the document
127 super.handleCreate(wrapDoc);
128 // Ensure we have required fields set properly
129 handleInAuthority(wrapDoc.getWrappedObject());
131 handleComputedDisplayNames(wrapDoc.getWrappedObject());
132 String displayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
133 AuthorityItemJAXBSchema.DISPLAY_NAME);
134 if(Tools.isEmpty(displayName)) {
135 logger.warn("Creating Authority Item with no displayName!");
138 handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
139 // refName includes displayName, so we force a correct value here.
140 updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
144 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
147 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
148 // First, get a copy of the old displayName
149 oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
150 AuthorityItemJAXBSchema.DISPLAY_NAME);
151 oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
152 AuthorityItemJAXBSchema.REF_NAME);
153 super.handleUpdate(wrapDoc);
154 handleComputedDisplayNames(wrapDoc.getWrappedObject());
155 String newDisplayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
156 AuthorityItemJAXBSchema.DISPLAY_NAME);
157 if(newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
158 // Need to update the refName, and then fix all references.
159 newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
161 // Mark as not needing attention in completeUpdate phase.
162 newRefNameOnUpdate = null;
163 oldRefNameOnUpdate = null;
168 * Handle display name.
170 * @param docModel the doc model
171 * @throws Exception the exception
173 protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
174 // Do nothing by default.
178 * Handle refName updates for changes to display name.
179 * Assumes refName is already correct. Just ensures it is right.
181 * @param docModel the doc model
182 * @throws Exception the exception
184 protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
185 String newDisplayName) throws Exception {
186 //String suppliedRefName = (String) docModel.getProperty(authorityItemCommonSchemaName,
187 // AuthorityItemJAXBSchema.REF_NAME);
188 RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
189 if(authItem == null) {
190 String err = "Authority Item has illegal refName: "+oldRefNameOnUpdate;
192 throw new IllegalArgumentException(err);
194 authItem.displayName = newDisplayName;
195 String updatedRefName = authItem.toString();
196 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
197 return updatedRefName;
202 * Checks to see if the refName has changed, and if so,
203 * uses utilities to find all references and update them.
205 protected void handleItemRefNameReferenceUpdate() {
206 if(newRefNameOnUpdate != null && oldRefNameOnUpdate!= null) {
207 // We have work to do.
208 logger.debug("Need to find and update references to Item.");
209 logger.debug("Old refName" + oldRefNameOnUpdate);
210 logger.debug("New refName" + newRefNameOnUpdate);
215 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
216 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
217 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
218 String shortDisplayName = "";
220 shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
221 } catch (PropertyNotFoundException pnfe) {
222 // Do nothing on exception. Some vocabulary schemas may not include a short display name.
224 if (Tools.isEmpty(shortIdentifier)) {
225 String generatedShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
226 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
230 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
232 String authorityRefBaseName) throws Exception {
233 DocumentModel docModel = wrapDoc.getWrappedObject();
234 String suppliedRefName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME);
236 // Temporarily accept client-supplied refName values, rather than always generating such values.
237 // Remove first block and the surrounding 'if' statement when clients should no longer supply refName values.
238 if(!Tools.isEmpty(suppliedRefName) ) {
239 // Supplied refName must at least be legal
240 RefName.AuthorityItem item = RefName.AuthorityItem.parse(suppliedRefName);
242 logger.error("Passed refName for authority item not legal: "+suppliedRefName);
243 suppliedRefName = null; // Clear this and compute a new one below.
246 // Recheck, in case we cleared it for being illegal
247 if(Tools.isEmpty(suppliedRefName) ) {
248 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
249 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
250 if (Tools.isEmpty(authorityRefBaseName)) {
251 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
253 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
254 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
255 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
260 * Check the logic around the parent pointer. Note that we only need do this on
261 * create, since we have logic to make this read-only on update.
265 * @throws Exception the exception
267 private void handleInAuthority(DocumentModel docModel) throws Exception {
268 docModel.setProperty(authorityItemCommonSchemaName,
269 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
274 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
277 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
279 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
281 // Add the CSID to the common part
282 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
283 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
284 unQObjectProperties.put("csid", csid);
287 return unQObjectProperties;
291 * Filters out selected values supplied in an update request.
293 * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
294 * that the link to the item's parent remains untouched.
296 * @param objectProps the properties filtered out from the update payload
297 * @param partMeta metadata for the object to fill
300 public void filterReadOnlyPropertiesForPart(
301 Map<String, Object> objectProps, ObjectPartType partMeta) {
302 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
303 String commonPartLabel = getServiceContext().getCommonPartLabel();
304 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
305 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
306 objectProps.remove(AuthorityItemJAXBSchema.CSID);
307 objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
308 // Enable when clients should no longer supply refName values
309 // objectProps.remove(AuthorityItemJAXBSchema.REF_NAME); // CSPACE-3178
315 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
316 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
317 super.extractAllParts(wrapDoc);
319 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
320 if (Tools.isTrue(showSiblings)) {
321 showSiblings(wrapDoc, ctx);
322 return; // actual result is returned on ctx.addOutputPart();
325 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
326 if (Tools.isTrue(showRelations)) {
327 showRelations(wrapDoc, ctx);
328 return; // actual result is returned on ctx.addOutputPart();
331 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
332 if (Tools.isTrue(showAllRelations)) {
333 showAllRelations(wrapDoc, ctx);
334 return; // actual result is returned on ctx.addOutputPart();
338 /** @return null on parent not found
340 protected String getParentCSID(String thisCSID) throws Exception {
341 String parentCSID = null;
343 String predicate = RelationshipType.HAS_BROADER.value();
344 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
345 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
346 if (parentList != null) {
347 if (parentList.size() == 0) {
350 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
351 parentCSID = relationListItem.getObjectCsid();
354 } catch (Exception e) {
355 logger.error("Could not find parent for this: " + thisCSID, e);
360 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
361 MultipartServiceContext ctx) throws Exception {
362 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
364 String predicate = RelationshipType.HAS_BROADER.value();
365 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
366 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
368 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
369 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
371 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
372 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
373 //Not optimal, but that's the current design spec.
375 for (RelationsCommonList.RelationListItem parent : parentList) {
376 childrenList.add(parent);
379 long childrenSize = childrenList.size();
380 childrenListOuter.setTotalItems(childrenSize);
381 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
383 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
384 ctx.addOutputPart(relationsPart);
387 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
388 MultipartServiceContext ctx) throws Exception {
389 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
390 String parentCSID = getParentCSID(thisCSID);
391 if (parentCSID == null) {
392 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
396 String predicate = RelationshipType.HAS_BROADER.value();
397 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
398 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
400 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
403 RelationsCommonList.RelationListItem item = null;
404 for (RelationsCommonList.RelationListItem sibling : siblingList) {
405 if (thisCSID.equals(sibling.getSubjectCsid())) {
406 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.
409 //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.
410 for (RelationsCommonList.RelationListItem self : toRemoveList) {
411 removeFromList(siblingList, self);
414 long siblingSize = siblingList.size();
415 siblingListOuter.setTotalItems(siblingSize);
416 siblingListOuter.setItemsInPage(siblingSize);
418 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
419 ctx.addOutputPart(relationsPart);
422 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
423 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
425 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
426 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
428 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
429 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
432 subjectList.addAll(objectList);
434 //now subjectList actually has records BOTH where thisCSID is subject and object.
435 long relatedSize = subjectList.size();
436 subjectListOuter.setTotalItems(relatedSize);
437 subjectListOuter.setItemsInPage(relatedSize);
439 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
440 ctx.addOutputPart(relationsPart);
443 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
444 super.fillAllParts(wrapDoc, action);
446 ServiceContext ctx = getServiceContext();
447 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
448 DocumentModel documentModel = (wrapDoc.getWrappedObject());
449 String itemCsid = documentModel.getName();
451 //UPDATE and CREATE will call. Updates relations part
452 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
454 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
455 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
459 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
460 super.completeCreate(wrapDoc);
461 handleRelationsPayload(wrapDoc, false);
464 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
465 super.completeUpdate(wrapDoc);
466 handleRelationsPayload(wrapDoc, true);
467 handleItemRefNameReferenceUpdate();
470 // Note that we must do this after we have completed the Update, so that the repository has the
471 // info for the item itself. The relations code must call into the repo to get info for each end.
472 // This could be optimized to pass in the parent docModel, since it will often be one end.
473 // Nevertheless, we should complete the item save before we do work on the relations, especially
474 // since a save on Create might fail, and we would not want to create relations for something
475 // that may not be created...
476 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
477 ServiceContext ctx = getServiceContext();
478 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
479 DocumentModel documentModel = (wrapDoc.getWrappedObject());
480 String itemCsid = documentModel.getName();
482 //Updates relations part
483 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
485 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
486 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
488 //now we add part for relations list
489 //ServiceContext ctx = getServiceContext();
490 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
491 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
494 /** updateRelations strategy:
496 go through inboundList, remove anything from childList that matches from childList
497 go through inboundList, remove anything from parentList that matches from parentList
498 go through parentList, delete all remaining
499 go through childList, delete all remaining
500 go through actionList, add all remaining.
501 check for duplicate children
502 check for more than one parent.
504 inboundList parentList childList actionList
505 ---------------- --------------- ---------------- ----------------
506 child-a parent-c child-a child-b
507 child-b parent-d child-c
510 private RelationsCommonList updateRelations(
511 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
513 if(logger.isTraceEnabled()) {
514 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID);
516 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
518 return null; //nothing to do--they didn't send a list of relations.
520 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
521 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
522 List<RelationsCommonList.RelationListItem> actionList = newList();
523 List<RelationsCommonList.RelationListItem> childList = null;
524 List<RelationsCommonList.RelationListItem> parentList = null;
525 DocumentModel docModel = wrapDoc.getWrappedObject();
527 ServiceContext ctx = getServiceContext();
528 //Do magic replacement of ${itemCSID} and fix URI's.
529 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
531 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
532 UriInfo uriInfo = ctx.getUriInfo();
533 MultivaluedMap queryParams = uriInfo.getQueryParameters();
536 //Run getList() once as sent to get childListOuter:
537 String predicate = RelationshipType.HAS_BROADER.value();
538 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
539 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
540 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
541 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
542 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
543 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
545 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
546 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
547 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
548 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
549 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
552 childList = childListOuter.getRelationListItem();
553 parentList = parentListOuter.getRelationListItem();
555 if (parentList.size() > 1) {
556 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
559 if(logger.isTraceEnabled()) {
560 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" got existing relations.");
565 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
566 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
567 // and so the CSID for those may be null
568 if (itemCSID.equals(inboundItem.getObject().getCsid())
569 && inboundItem.getPredicate().equals(HAS_BROADER)) {
570 //then this is an item that says we have a child. That child is inboundItem
571 RelationsCommonList.RelationListItem childItem =
572 (childList==null)?null:findInList(childList, inboundItem);
573 if (childItem != null) {
574 removeFromList(childList, childItem); //exists, just take it off delete list
576 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
578 ensureChildHasNoOtherParents(ctx, queryParams, inboundItem.getSubject().getCsid());
580 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
581 && inboundItem.getPredicate().equals(HAS_BROADER)) {
582 //then this is an item that says we have a parent. inboundItem is that parent.
583 RelationsCommonList.RelationListItem parentItem =
584 (parentList==null)?null:findInList(parentList, inboundItem);
585 if (parentItem != null) {
586 removeFromList(parentList, parentItem); //exists, just take it off delete list
588 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
591 logger.warn("Element didn't match parent or child, but may have partial fields that match. inboundItem: " + inboundItem);
592 //not dealing with: hasNarrower or any other predicate.
595 if(logger.isTraceEnabled()) {
596 String dump = dumpLists(itemCSID, parentList, childList, actionList);
597 //System.out.println("====dump====="+CR+dump);
598 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
601 if(logger.isTraceEnabled()) {
602 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" deleting "
603 +parentList.size()+" existing parents and "+childList.size()+" existing children.");
605 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
606 deleteRelations(childList, ctx, "childList");
608 if(logger.isTraceEnabled()) {
609 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" adding "
610 +actionList.size()+" new parents and children.");
612 createRelations(actionList, ctx);
613 if(logger.isTraceEnabled()) {
614 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" done.");
616 //We return all elements on the inbound list, since we have just worked to make them exist in the system
617 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
618 return relationsCommonListBody;
621 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
622 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
623 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
624 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
625 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
626 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
627 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
628 deleteRelations(parentList, ctx, "parentList-delete");
631 private String dumpLists(String itemCSID,
632 List<RelationsCommonList.RelationListItem> parentList,
633 List<RelationsCommonList.RelationListItem> childList,
634 List<RelationsCommonList.RelationListItem> actionList) {
635 StringBuffer sb = new StringBuffer();
636 sb.append("itemCSID: " + itemCSID + CR);
637 sb.append(dumpList(parentList, "parentList"));
638 sb.append(dumpList(childList, "childList"));
639 sb.append(dumpList(actionList, "actionList"));
640 return sb.toString();
642 private final static String CR = "\r\n";
643 private final static String T = " ";
645 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
646 StringBuffer sb = new StringBuffer();
648 if (list.size() > 0) {
649 sb.append("=========== " + label + " ==========" + CR);
651 for (RelationsCommonList.RelationListItem item : list) {
653 T + item.getSubject().getCsid() //+T4 + item.getSubject().getUri()
654 + T + item.getPredicate()
655 + T + item.getObject().getCsid() //+T4 + item.getObject().getUri()
656 + CR //+"subject:{"+item.getSubject()+"}\r\n object:{"+item.getObject()+"}"
657 //+ CR + "relation-record: {"+item+"}"
662 return sb.toString();
665 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
666 * and sets URI correctly for related items.
667 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
669 protected void fixupInboundListItems(ServiceContext ctx,
670 List<RelationsCommonList.RelationListItem> inboundList,
671 DocumentModel docModel,
672 String itemCSID) throws Exception {
673 String thisURI = this.getUri(docModel);
674 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
675 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
676 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
677 RelationsDocListItem inboundItemObject = inboundItem.getObject();
678 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
680 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
681 inboundItem.setObjectCsid(itemCSID);
682 inboundItemObject.setCsid(itemCSID);
683 //inboundItemObject.setUri(getUri(docModel));
686 String objectCsid = inboundItemObject.getCsid();
687 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
688 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
689 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
690 inboundItemObject.setUri(uri); //CSPACE-4037
693 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
695 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
696 inboundItem.setSubjectCsid(itemCSID);
697 inboundItemSubject.setCsid(itemCSID);
698 //inboundItemSubject.setUri(getUri(docModel));
701 String subjectCsid = inboundItemSubject.getCsid();
702 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
703 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
704 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
705 inboundItemSubject.setUri(uri); //CSPACE-4037
708 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
713 // this method calls the RelationResource to have it create the relations and persist them.
714 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
715 for (RelationsCommonList.RelationListItem item : inboundList) {
716 RelationsCommon rc = new RelationsCommon();
717 //rc.setCsid(item.getCsid());
718 //todo: assignTo(item, rc);
719 RelationsDocListItem itemSubject = item.getSubject();
720 RelationsDocListItem itemObject = item.getObject();
722 // Set at least one of CSID and refName for Subject and Object
723 // Either value might be null for for each of Subject and Object
724 String subjectCsid = itemSubject.getCsid();
725 rc.setSubjectCsid(subjectCsid);
726 rc.setDocumentId1(subjectCsid); // populate legacy field for backward compatibility
728 String objCsid = itemObject.getCsid();
729 rc.setObjectCsid(objCsid);
730 rc.setDocumentId2(objCsid); // populate legacy field for backward compatibility
732 rc.setSubjectRefName(itemSubject.getRefName());
733 rc.setObjectRefName(itemObject.getRefName());
735 rc.setRelationshipType(item.getPredicate());
736 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
737 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
739 // This is superfluous, since it will be fetched by the Relations Create logic.
740 rc.setSubjectDocumentType(itemSubject.getDocumentType());
741 rc.setObjectDocumentType(itemObject.getDocumentType());
742 // populate legacy fields for backward compatibility
743 rc.setDocumentType1(itemSubject.getDocumentType());
744 rc.setDocumentType2(itemObject.getDocumentType());
746 // This is superfluous, since it will be fetched by the Relations Create logic.
747 rc.setSubjectUri(itemSubject.getUri());
748 rc.setObjectUri(itemObject.getUri());
749 // May not have the info here. Only really require CSID or refName.
750 // Rest is handled in the Relation create mechanism
751 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
753 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
754 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
755 payloadOut.addPart(outputPart);
756 //System.out.println("\r\n==== TO CREATE: "+rc.getDocumentId1()+"==>"+rc.getPredicate()+"==>"+rc.getDocumentId2());
757 RelationResource relationResource = new RelationResource();
758 Object res = relationResource.create(ctx.getResourceMap(),
759 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
763 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
765 //if (list.size()>0){ logger.info("==== deleteRelations from : "+listName); }
766 for (RelationsCommonList.RelationListItem item : list) {
767 RelationResource relationResource = new RelationResource();
768 //logger.info("==== TO DELETE: " + item.getCsid() + ": " + item.getSubject().getCsid() + "--" + item.getPredicate() + "-->" + item.getObject().getCsid());
769 Object res = relationResource.delete(item.getCsid());
771 } catch (Throwable t) {
772 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
777 private List<RelationsCommonList.RelationListItem> newList() {
778 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
782 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
783 List<RelationsCommonList.RelationListItem> result = newList();
784 for (RelationsCommonList.RelationListItem item : inboundList) {
790 private RelationsCommonList.RelationListItem findInList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
791 for (RelationsCommonList.RelationListItem listItem : list) {
792 if (itemsEqual(listItem, item)) { //equals must be defined, else
799 private boolean itemsEqual(RelationsCommonList.RelationListItem item, RelationsCommonList.RelationListItem item2) {
800 if (item == null || item2 == null) {
803 RelationsDocListItem subj1 = item.getSubject();
804 RelationsDocListItem subj2 = item2.getSubject();
805 RelationsDocListItem obj1 = item.getObject();
806 RelationsDocListItem obj2 = item2.getObject();
808 return (subj1.getCsid().equals(subj2.getCsid()))
809 && (obj1.getCsid().equals(obj1.getCsid()))
810 && ((item.getPredicate().equals(item2.getPredicate()))
811 && (item.getRelationshipType().equals(item2.getRelationshipType())))
812 && (obj1.getDocumentType().equals(obj2.getDocumentType()))
813 && (subj1.getDocumentType().equals(subj2.getDocumentType()));
816 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
820 /* don't even THINK of re-using this method.
821 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
823 private String extractInAuthorityCSID(String uri) {
824 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
825 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
826 Matcher m = p.matcher(uri);
828 if (m.groupCount() < 3) {
829 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
832 //String service = m.group(1);
833 String inauth = m.group(2);
834 //String theRest = m.group(3);
836 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
839 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
844 //ensures CSPACE-4042
845 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
846 String authorityCSID = extractInAuthorityCSID(thisURI);
847 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
848 if (Tools.isBlank(authorityCSID)
849 || Tools.isBlank(authorityCSIDForInbound)
850 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
851 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
855 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
856 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
857 ServiceContext ctx = getServiceContext();
858 MultivaluedMap queryParams = ctx.getQueryParams();
859 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
860 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
861 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
863 RelationResource relationResource = new RelationResource();
864 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
865 return relationsCommonList;
867 //============================= END TODO refactor ==========================