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.ServiceBindingUtils;
37 import org.collectionspace.services.common.context.ServiceContext;
38 import org.collectionspace.services.common.document.DocumentWrapper;
39 import org.collectionspace.services.common.document.DocumentWrapperImpl;
40 import org.collectionspace.services.common.relation.IRelationsManager;
41 import org.collectionspace.services.common.repository.RepositoryClient;
42 import org.collectionspace.services.common.repository.RepositoryClientFactory;
43 import org.collectionspace.services.common.service.ObjectPartType;
44 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
45 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
46 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
47 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
48 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
49 import org.collectionspace.services.relation.RelationResource;
50 import org.collectionspace.services.relation.RelationsCommon;
51 import org.collectionspace.services.relation.RelationsCommonList;
52 import org.collectionspace.services.relation.RelationsDocListItem;
53 import org.collectionspace.services.relation.RelationshipType;
54 import org.nuxeo.ecm.core.api.DocumentModel;
55 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
59 import javax.ws.rs.core.MultivaluedMap;
60 import javax.ws.rs.core.UriInfo;
61 import java.util.ArrayList;
62 import java.util.List;
64 import java.util.regex.Matcher;
65 import java.util.regex.Pattern;
67 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
69 * AuthorityItemDocumentModelHandler
71 * $LastChangedRevision: $
74 public abstract class AuthorityItemDocumentModelHandler<AICommon>
75 extends DocHandlerBase<AICommon> {
77 private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
78 private String authorityItemCommonSchemaName;
80 * inVocabulary is the parent Authority for this context
82 protected String inAuthority;
83 protected String authorityRefNameBase;
85 // Used to determine when the displayName changes as part of the update.
86 protected String oldDisplayNameOnUpdate = null;
87 protected String oldRefNameOnUpdate = null;
88 protected String newRefNameOnUpdate = null;
90 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
91 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
94 public String getInAuthority() {
98 public void setInAuthority(String inAuthority) {
99 this.inAuthority = inAuthority;
102 /** Subclasses may override this to customize the URI segment. */
103 public String getAuthorityServicePath() {
104 return getServiceContext().getServiceName().toLowerCase(); // Laramie20110510 CSPACE-3932
108 public String getUri(DocumentModel docModel) {
109 // Laramie20110510 CSPACE-3932
110 String authorityServicePath = getAuthorityServicePath();
111 return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
114 public String getAuthorityRefNameBase() {
115 return this.authorityRefNameBase;
118 public void setAuthorityRefNameBase(String value) {
119 this.authorityRefNameBase = value;
123 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
126 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
127 // first fill all the parts of the document
128 super.handleCreate(wrapDoc);
129 // Ensure we have required fields set properly
130 handleInAuthority(wrapDoc.getWrappedObject());
132 handleComputedDisplayNames(wrapDoc.getWrappedObject());
133 String displayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
134 AuthorityItemJAXBSchema.DISPLAY_NAME);
135 if(Tools.isEmpty(displayName)) {
136 logger.warn("Creating Authority Item with no displayName!");
139 handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
140 // refName includes displayName, so we force a correct value here.
141 updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
145 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
148 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
149 // First, get a copy of the old displayName
150 oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
151 AuthorityItemJAXBSchema.DISPLAY_NAME);
152 oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
153 AuthorityItemJAXBSchema.REF_NAME);
154 super.handleUpdate(wrapDoc);
155 handleComputedDisplayNames(wrapDoc.getWrappedObject());
156 String newDisplayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
157 AuthorityItemJAXBSchema.DISPLAY_NAME);
158 if(newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
159 // Need to update the refName, and then fix all references.
160 newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
162 // Mark as not needing attention in completeUpdate phase.
163 newRefNameOnUpdate = null;
164 oldRefNameOnUpdate = null;
169 * Handle display name.
171 * @param docModel the doc model
172 * @throws Exception the exception
174 protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
175 // Do nothing by default.
179 * Handle refName updates for changes to display name.
180 * Assumes refName is already correct. Just ensures it is right.
182 * @param docModel the doc model
183 * @throws Exception the exception
185 protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
186 String newDisplayName) throws Exception {
187 //String suppliedRefName = (String) docModel.getProperty(authorityItemCommonSchemaName,
188 // AuthorityItemJAXBSchema.REF_NAME);
189 RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
190 if(authItem == null) {
191 String err = "Authority Item has illegal refName: "+oldRefNameOnUpdate;
193 throw new IllegalArgumentException(err);
195 authItem.displayName = newDisplayName;
196 String updatedRefName = authItem.toString();
197 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
198 return updatedRefName;
203 * Checks to see if the refName has changed, and if so,
204 * uses utilities to find all references and update them.
206 protected void handleItemRefNameReferenceUpdate() {
207 if(newRefNameOnUpdate != null && oldRefNameOnUpdate!= null) {
208 // We have work to do.
209 if(logger.isDebugEnabled()) {
210 String eol = System.getProperty("line.separator");
211 logger.debug("Need to find and update references to Item."+eol
212 +" Old refName" + oldRefNameOnUpdate + eol
213 +" New refName" + newRefNameOnUpdate);
215 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
216 RepositoryClient repoClient = getRepositoryClient(ctx);
217 // HACK - this should be defined for each handler, as with
218 // AuthorityResource.getRefPropName()
219 String refNameProp = ServiceBindingUtils.AUTH_REF_PROP;
221 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient,
222 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp );
223 if(logger.isDebugEnabled()) {
224 logger.debug("Updated "+nUpdated+" instances of oldRefName to newRefName");
230 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
231 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
232 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
233 String shortDisplayName = "";
235 shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
236 } catch (PropertyNotFoundException pnfe) {
237 // Do nothing on exception. Some vocabulary schemas may not include a short display name.
239 if (Tools.isEmpty(shortIdentifier)) {
240 String generatedShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
241 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
245 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
247 String authorityRefBaseName) throws Exception {
248 DocumentModel docModel = wrapDoc.getWrappedObject();
249 String suppliedRefName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME);
251 // Temporarily accept client-supplied refName values, rather than always generating such values.
252 // Remove first block and the surrounding 'if' statement when clients should no longer supply refName values.
253 if(!Tools.isEmpty(suppliedRefName) ) {
254 // Supplied refName must at least be legal
255 RefName.AuthorityItem item = RefName.AuthorityItem.parse(suppliedRefName);
257 logger.error("Passed refName for authority item not legal: "+suppliedRefName);
258 suppliedRefName = null; // Clear this and compute a new one below.
261 // Recheck, in case we cleared it for being illegal
262 if(Tools.isEmpty(suppliedRefName) ) {
263 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
264 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
265 if (Tools.isEmpty(authorityRefBaseName)) {
266 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
268 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
269 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
270 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
275 * Check the logic around the parent pointer. Note that we only need do this on
276 * create, since we have logic to make this read-only on update.
280 * @throws Exception the exception
282 private void handleInAuthority(DocumentModel docModel) throws Exception {
283 docModel.setProperty(authorityItemCommonSchemaName,
284 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
289 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
292 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
294 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
296 // Add the CSID to the common part, since they may have fetched via the shortId.
297 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
298 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
299 unQObjectProperties.put("csid", csid);
302 return unQObjectProperties;
306 * Filters out selected values supplied in an update request.
308 * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
309 * that the link to the item's parent remains untouched.
311 * @param objectProps the properties filtered out from the update payload
312 * @param partMeta metadata for the object to fill
315 public void filterReadOnlyPropertiesForPart(
316 Map<String, Object> objectProps, ObjectPartType partMeta) {
317 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
318 String commonPartLabel = getServiceContext().getCommonPartLabel();
319 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
320 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
321 objectProps.remove(AuthorityItemJAXBSchema.CSID);
322 objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
323 // Enable when clients should no longer supply refName values
324 // objectProps.remove(AuthorityItemJAXBSchema.REF_NAME); // CSPACE-3178
330 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
331 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
332 super.extractAllParts(wrapDoc);
334 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
335 if (Tools.isTrue(showSiblings)) {
336 showSiblings(wrapDoc, ctx);
337 return; // actual result is returned on ctx.addOutputPart();
340 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
341 if (Tools.isTrue(showRelations)) {
342 showRelations(wrapDoc, ctx);
343 return; // actual result is returned on ctx.addOutputPart();
346 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
347 if (Tools.isTrue(showAllRelations)) {
348 showAllRelations(wrapDoc, ctx);
349 return; // actual result is returned on ctx.addOutputPart();
353 /** @return null on parent not found
355 protected String getParentCSID(String thisCSID) throws Exception {
356 String parentCSID = null;
358 String predicate = RelationshipType.HAS_BROADER.value();
359 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
360 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
361 if (parentList != null) {
362 if (parentList.size() == 0) {
365 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
366 parentCSID = relationListItem.getObjectCsid();
369 } catch (Exception e) {
370 logger.error("Could not find parent for this: " + thisCSID, e);
375 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
376 MultipartServiceContext ctx) throws Exception {
377 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
379 String predicate = RelationshipType.HAS_BROADER.value();
380 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
381 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
383 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
384 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
386 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
387 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
388 //Not optimal, but that's the current design spec.
390 for (RelationsCommonList.RelationListItem parent : parentList) {
391 childrenList.add(parent);
394 long childrenSize = childrenList.size();
395 childrenListOuter.setTotalItems(childrenSize);
396 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
398 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
399 ctx.addOutputPart(relationsPart);
402 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
403 MultipartServiceContext ctx) throws Exception {
404 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
405 String parentCSID = getParentCSID(thisCSID);
406 if (parentCSID == null) {
407 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
411 String predicate = RelationshipType.HAS_BROADER.value();
412 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
413 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
415 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
418 RelationsCommonList.RelationListItem item = null;
419 for (RelationsCommonList.RelationListItem sibling : siblingList) {
420 if (thisCSID.equals(sibling.getSubjectCsid())) {
421 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.
424 //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.
425 for (RelationsCommonList.RelationListItem self : toRemoveList) {
426 removeFromList(siblingList, self);
429 long siblingSize = siblingList.size();
430 siblingListOuter.setTotalItems(siblingSize);
431 siblingListOuter.setItemsInPage(siblingSize);
433 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
434 ctx.addOutputPart(relationsPart);
437 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
438 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
440 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
441 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
443 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
444 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
447 subjectList.addAll(objectList);
449 //now subjectList actually has records BOTH where thisCSID is subject and object.
450 long relatedSize = subjectList.size();
451 subjectListOuter.setTotalItems(relatedSize);
452 subjectListOuter.setItemsInPage(relatedSize);
454 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
455 ctx.addOutputPart(relationsPart);
458 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
459 super.fillAllParts(wrapDoc, action);
461 ServiceContext ctx = getServiceContext();
462 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
463 DocumentModel documentModel = (wrapDoc.getWrappedObject());
464 String itemCsid = documentModel.getName();
466 //UPDATE and CREATE will call. Updates relations part
467 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
469 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
470 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
474 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
475 super.completeCreate(wrapDoc);
476 handleRelationsPayload(wrapDoc, false);
479 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
480 super.completeUpdate(wrapDoc);
481 handleRelationsPayload(wrapDoc, true);
482 handleItemRefNameReferenceUpdate();
485 // Note that we must do this after we have completed the Update, so that the repository has the
486 // info for the item itself. The relations code must call into the repo to get info for each end.
487 // This could be optimized to pass in the parent docModel, since it will often be one end.
488 // Nevertheless, we should complete the item save before we do work on the relations, especially
489 // since a save on Create might fail, and we would not want to create relations for something
490 // that may not be created...
491 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
492 ServiceContext ctx = getServiceContext();
493 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
494 DocumentModel documentModel = (wrapDoc.getWrappedObject());
495 String itemCsid = documentModel.getName();
497 //Updates relations part
498 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
500 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
501 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
503 //now we add part for relations list
504 //ServiceContext ctx = getServiceContext();
505 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
506 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
509 /** updateRelations strategy:
511 go through inboundList, remove anything from childList that matches from childList
512 go through inboundList, remove anything from parentList that matches from parentList
513 go through parentList, delete all remaining
514 go through childList, delete all remaining
515 go through actionList, add all remaining.
516 check for duplicate children
517 check for more than one parent.
519 inboundList parentList childList actionList
520 ---------------- --------------- ---------------- ----------------
521 child-a parent-c child-a child-b
522 child-b parent-d child-c
525 private RelationsCommonList updateRelations(
526 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
528 if(logger.isTraceEnabled()) {
529 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID);
531 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
533 return null; //nothing to do--they didn't send a list of relations.
535 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
536 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
537 List<RelationsCommonList.RelationListItem> actionList = newList();
538 List<RelationsCommonList.RelationListItem> childList = null;
539 List<RelationsCommonList.RelationListItem> parentList = null;
540 DocumentModel docModel = wrapDoc.getWrappedObject();
542 ServiceContext ctx = getServiceContext();
543 //Do magic replacement of ${itemCSID} and fix URI's.
544 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
546 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
547 UriInfo uriInfo = ctx.getUriInfo();
548 MultivaluedMap queryParams = uriInfo.getQueryParameters();
551 //Run getList() once as sent to get childListOuter:
552 String predicate = RelationshipType.HAS_BROADER.value();
553 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
554 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
555 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
556 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
557 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
558 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
560 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
561 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
562 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
563 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
564 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
567 childList = childListOuter.getRelationListItem();
568 parentList = parentListOuter.getRelationListItem();
570 if (parentList.size() > 1) {
571 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
574 if(logger.isTraceEnabled()) {
575 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" got existing relations.");
580 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
581 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
582 // and so the CSID for those may be null
583 if (itemCSID.equals(inboundItem.getObject().getCsid())
584 && inboundItem.getPredicate().equals(HAS_BROADER)) {
585 //then this is an item that says we have a child. That child is inboundItem
586 RelationsCommonList.RelationListItem childItem =
587 (childList==null)?null:findInList(childList, inboundItem);
588 if (childItem != null) {
589 removeFromList(childList, childItem); //exists, just take it off delete list
591 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
593 ensureChildHasNoOtherParents(ctx, queryParams, inboundItem.getSubject().getCsid());
595 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
596 && inboundItem.getPredicate().equals(HAS_BROADER)) {
597 //then this is an item that says we have a parent. inboundItem is that parent.
598 RelationsCommonList.RelationListItem parentItem =
599 (parentList==null)?null:findInList(parentList, inboundItem);
600 if (parentItem != null) {
601 removeFromList(parentList, parentItem); //exists, just take it off delete list
603 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
606 logger.warn("Element didn't match parent or child, but may have partial fields that match. inboundItem: " + inboundItem);
607 //not dealing with: hasNarrower or any other predicate.
610 if(logger.isTraceEnabled()) {
611 String dump = dumpLists(itemCSID, parentList, childList, actionList);
612 //System.out.println("====dump====="+CR+dump);
613 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
616 if(logger.isTraceEnabled()) {
617 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" deleting "
618 +parentList.size()+" existing parents and "+childList.size()+" existing children.");
620 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
621 deleteRelations(childList, ctx, "childList");
623 if(logger.isTraceEnabled()) {
624 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" adding "
625 +actionList.size()+" new parents and children.");
627 createRelations(actionList, ctx);
628 if(logger.isTraceEnabled()) {
629 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" done.");
631 //We return all elements on the inbound list, since we have just worked to make them exist in the system
632 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
633 return relationsCommonListBody;
636 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
637 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
638 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
639 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
640 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
641 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
642 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
643 deleteRelations(parentList, ctx, "parentList-delete");
646 private String dumpLists(String itemCSID,
647 List<RelationsCommonList.RelationListItem> parentList,
648 List<RelationsCommonList.RelationListItem> childList,
649 List<RelationsCommonList.RelationListItem> actionList) {
650 StringBuffer sb = new StringBuffer();
651 sb.append("itemCSID: " + itemCSID + CR);
652 sb.append(dumpList(parentList, "parentList"));
653 sb.append(dumpList(childList, "childList"));
654 sb.append(dumpList(actionList, "actionList"));
655 return sb.toString();
657 private final static String CR = "\r\n";
658 private final static String T = " ";
660 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
661 StringBuffer sb = new StringBuffer();
663 if (list.size() > 0) {
664 sb.append("=========== " + label + " ==========" + CR);
666 for (RelationsCommonList.RelationListItem item : list) {
668 T + item.getSubject().getCsid() //+T4 + item.getSubject().getUri()
669 + T + item.getPredicate()
670 + T + item.getObject().getCsid() //+T4 + item.getObject().getUri()
671 + CR //+"subject:{"+item.getSubject()+"}\r\n object:{"+item.getObject()+"}"
672 //+ CR + "relation-record: {"+item+"}"
677 return sb.toString();
680 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
681 * and sets URI correctly for related items.
682 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
684 protected void fixupInboundListItems(ServiceContext ctx,
685 List<RelationsCommonList.RelationListItem> inboundList,
686 DocumentModel docModel,
687 String itemCSID) throws Exception {
688 String thisURI = this.getUri(docModel);
689 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
690 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
691 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
692 RelationsDocListItem inboundItemObject = inboundItem.getObject();
693 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
695 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
696 inboundItem.setObjectCsid(itemCSID);
697 inboundItemObject.setCsid(itemCSID);
698 //inboundItemObject.setUri(getUri(docModel));
701 String objectCsid = inboundItemObject.getCsid();
702 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
703 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
704 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
705 inboundItemObject.setUri(uri); //CSPACE-4037
708 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
710 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
711 inboundItem.setSubjectCsid(itemCSID);
712 inboundItemSubject.setCsid(itemCSID);
713 //inboundItemSubject.setUri(getUri(docModel));
716 String subjectCsid = inboundItemSubject.getCsid();
717 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
718 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
719 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
720 inboundItemSubject.setUri(uri); //CSPACE-4037
723 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
728 // this method calls the RelationResource to have it create the relations and persist them.
729 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
730 for (RelationsCommonList.RelationListItem item : inboundList) {
731 RelationsCommon rc = new RelationsCommon();
732 //rc.setCsid(item.getCsid());
733 //todo: assignTo(item, rc);
734 RelationsDocListItem itemSubject = item.getSubject();
735 RelationsDocListItem itemObject = item.getObject();
737 // Set at least one of CSID and refName for Subject and Object
738 // Either value might be null for for each of Subject and Object
739 String subjectCsid = itemSubject.getCsid();
740 rc.setSubjectCsid(subjectCsid);
741 rc.setDocumentId1(subjectCsid); // populate legacy field for backward compatibility
743 String objCsid = itemObject.getCsid();
744 rc.setObjectCsid(objCsid);
745 rc.setDocumentId2(objCsid); // populate legacy field for backward compatibility
747 rc.setSubjectRefName(itemSubject.getRefName());
748 rc.setObjectRefName(itemObject.getRefName());
750 rc.setRelationshipType(item.getPredicate());
751 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
752 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
754 // This is superfluous, since it will be fetched by the Relations Create logic.
755 rc.setSubjectDocumentType(itemSubject.getDocumentType());
756 rc.setObjectDocumentType(itemObject.getDocumentType());
757 // populate legacy fields for backward compatibility
758 rc.setDocumentType1(itemSubject.getDocumentType());
759 rc.setDocumentType2(itemObject.getDocumentType());
761 // This is superfluous, since it will be fetched by the Relations Create logic.
762 rc.setSubjectUri(itemSubject.getUri());
763 rc.setObjectUri(itemObject.getUri());
764 // May not have the info here. Only really require CSID or refName.
765 // Rest is handled in the Relation create mechanism
766 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
768 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
769 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
770 payloadOut.addPart(outputPart);
771 //System.out.println("\r\n==== TO CREATE: "+rc.getDocumentId1()+"==>"+rc.getPredicate()+"==>"+rc.getDocumentId2());
772 RelationResource relationResource = new RelationResource();
773 Object res = relationResource.create(ctx.getResourceMap(),
774 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
778 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
780 //if (list.size()>0){ logger.info("==== deleteRelations from : "+listName); }
781 for (RelationsCommonList.RelationListItem item : list) {
782 RelationResource relationResource = new RelationResource();
783 //logger.info("==== TO DELETE: " + item.getCsid() + ": " + item.getSubject().getCsid() + "--" + item.getPredicate() + "-->" + item.getObject().getCsid());
784 Object res = relationResource.delete(item.getCsid());
786 } catch (Throwable t) {
787 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
792 private List<RelationsCommonList.RelationListItem> newList() {
793 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
797 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
798 List<RelationsCommonList.RelationListItem> result = newList();
799 for (RelationsCommonList.RelationListItem item : inboundList) {
805 private RelationsCommonList.RelationListItem findInList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
806 for (RelationsCommonList.RelationListItem listItem : list) {
807 if (itemsEqual(listItem, item)) { //equals must be defined, else
814 private boolean itemsEqual(RelationsCommonList.RelationListItem item, RelationsCommonList.RelationListItem item2) {
815 if (item == null || item2 == null) {
818 RelationsDocListItem subj1 = item.getSubject();
819 RelationsDocListItem subj2 = item2.getSubject();
820 RelationsDocListItem obj1 = item.getObject();
821 RelationsDocListItem obj2 = item2.getObject();
823 return (subj1.getCsid().equals(subj2.getCsid()))
824 && (obj1.getCsid().equals(obj1.getCsid()))
825 && ((item.getPredicate().equals(item2.getPredicate()))
826 && (item.getRelationshipType().equals(item2.getRelationshipType())))
827 && (obj1.getDocumentType().equals(obj2.getDocumentType()))
828 && (subj1.getDocumentType().equals(subj2.getDocumentType()));
831 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
835 /* don't even THINK of re-using this method.
836 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
838 private String extractInAuthorityCSID(String uri) {
839 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
840 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
841 Matcher m = p.matcher(uri);
843 if (m.groupCount() < 3) {
844 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
847 //String service = m.group(1);
848 String inauth = m.group(2);
849 //String theRest = m.group(3);
851 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
854 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
859 //ensures CSPACE-4042
860 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
861 String authorityCSID = extractInAuthorityCSID(thisURI);
862 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
863 if (Tools.isBlank(authorityCSID)
864 || Tools.isBlank(authorityCSIDForInbound)
865 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
866 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
870 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
871 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
872 ServiceContext ctx = getServiceContext();
873 MultivaluedMap queryParams = ctx.getQueryParams();
874 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
875 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
876 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
878 RelationResource relationResource = new RelationResource();
879 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
880 return relationsCommonList;
882 //============================= END TODO refactor ==========================