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.DocumentException;
39 import org.collectionspace.services.common.document.DocumentWrapper;
40 import org.collectionspace.services.common.document.DocumentWrapperImpl;
41 import org.collectionspace.services.common.relation.IRelationsManager;
42 import org.collectionspace.services.common.repository.RepositoryClient;
43 import org.collectionspace.services.common.repository.RepositoryClientFactory;
44 import org.collectionspace.services.common.service.ObjectPartType;
45 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
46 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
47 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
48 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
49 import org.collectionspace.services.common.service.ListResultField;
50 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
51 import org.collectionspace.services.relation.RelationResource;
52 import org.collectionspace.services.relation.RelationsCommon;
53 import org.collectionspace.services.relation.RelationsCommonList;
54 import org.collectionspace.services.relation.RelationsDocListItem;
55 import org.collectionspace.services.relation.RelationshipType;
56 import org.nuxeo.ecm.core.api.DocumentModel;
57 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
61 import javax.ws.rs.core.MultivaluedMap;
62 import javax.ws.rs.core.UriInfo;
63 import java.util.ArrayList;
64 import java.util.List;
66 import java.util.regex.Matcher;
67 import java.util.regex.Pattern;
69 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
71 * AuthorityItemDocumentModelHandler
73 * $LastChangedRevision: $
76 public abstract class AuthorityItemDocumentModelHandler<AICommon>
77 extends DocHandlerBase<AICommon> {
79 private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
80 private String authorityItemCommonSchemaName;
82 * inVocabulary is the parent Authority for this context
84 protected String inAuthority;
85 protected String authorityRefNameBase;
87 // Used to determine when the displayName changes as part of the update.
88 protected String oldDisplayNameOnUpdate = null;
89 protected String oldRefNameOnUpdate = null;
90 protected String newRefNameOnUpdate = null;
92 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
93 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
96 public String getInAuthority() {
100 public void setInAuthority(String inAuthority) {
101 this.inAuthority = inAuthority;
104 /** Subclasses may override this to customize the URI segment. */
105 public String getAuthorityServicePath() {
106 return getServiceContext().getServiceName().toLowerCase(); // Laramie20110510 CSPACE-3932
110 public String getUri(DocumentModel docModel) {
111 // Laramie20110510 CSPACE-3932
112 String authorityServicePath = getAuthorityServicePath();
113 return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
116 public String getAuthorityRefNameBase() {
117 return this.authorityRefNameBase;
120 public void setAuthorityRefNameBase(String value) {
121 this.authorityRefNameBase = value;
125 public List<ListResultField> getListItemsArray() throws DocumentException {
126 List<ListResultField> list = super.getListItemsArray();
127 int nFields = list.size();
128 // Ensure some common fields so do not depend upon config for general logic
129 boolean hasDisplayName = false;
130 boolean hasShortId = false;
131 boolean hasRefName = false;
132 boolean hasTermStatus = false;
133 for(int i=0;i<nFields;i++) {
134 ListResultField field = list.get(i);
135 String elName = field.getElement();
136 if(AuthorityItemJAXBSchema.DISPLAY_NAME.equals(elName))
137 hasDisplayName = true;
138 else if(AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName))
140 else if(AuthorityItemJAXBSchema.REF_NAME.equals(elName))
142 else if(AuthorityItemJAXBSchema.TERM_STATUS.equals(elName))
143 hasTermStatus = true;
145 ListResultField field;
146 if(!hasDisplayName) {
147 field = new ListResultField();
148 field.setElement(AuthorityItemJAXBSchema.DISPLAY_NAME);
149 field.setXpath(AuthorityItemJAXBSchema.DISPLAY_NAME);
153 field = new ListResultField();
154 field.setElement(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
155 field.setXpath(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
159 field = new ListResultField();
160 field.setElement(AuthorityItemJAXBSchema.REF_NAME);
161 field.setXpath(AuthorityItemJAXBSchema.REF_NAME);
165 field = new ListResultField();
166 field.setElement(AuthorityItemJAXBSchema.TERM_STATUS);
167 field.setXpath(AuthorityItemJAXBSchema.TERM_STATUS);
176 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
179 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
180 // first fill all the parts of the document
181 super.handleCreate(wrapDoc);
182 // Ensure we have required fields set properly
183 handleInAuthority(wrapDoc.getWrappedObject());
185 handleComputedDisplayNames(wrapDoc.getWrappedObject());
186 String displayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
187 AuthorityItemJAXBSchema.DISPLAY_NAME);
188 if(Tools.isEmpty(displayName)) {
189 logger.warn("Creating Authority Item with no displayName!");
192 handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
193 // refName includes displayName, so we force a correct value here.
194 updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
198 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
201 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
202 // First, get a copy of the old displayName
203 oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
204 AuthorityItemJAXBSchema.DISPLAY_NAME);
205 oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
206 AuthorityItemJAXBSchema.REF_NAME);
207 super.handleUpdate(wrapDoc);
208 handleComputedDisplayNames(wrapDoc.getWrappedObject());
209 String newDisplayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
210 AuthorityItemJAXBSchema.DISPLAY_NAME);
211 if(newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
212 // Need to update the refName, and then fix all references.
213 newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
215 // Mark as not needing attention in completeUpdate phase.
216 newRefNameOnUpdate = null;
217 oldRefNameOnUpdate = null;
222 * Handle display name.
224 * @param docModel the doc model
225 * @throws Exception the exception
227 protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
228 // Do nothing by default.
232 * Handle refName updates for changes to display name.
233 * Assumes refName is already correct. Just ensures it is right.
235 * @param docModel the doc model
236 * @throws Exception the exception
238 protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
239 String newDisplayName) throws Exception {
240 //String suppliedRefName = (String) docModel.getProperty(authorityItemCommonSchemaName,
241 // AuthorityItemJAXBSchema.REF_NAME);
242 RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
243 if(authItem == null) {
244 String err = "Authority Item has illegal refName: "+oldRefNameOnUpdate;
246 throw new IllegalArgumentException(err);
248 authItem.displayName = newDisplayName;
249 String updatedRefName = authItem.toString();
250 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
251 return updatedRefName;
256 * Checks to see if the refName has changed, and if so,
257 * uses utilities to find all references and update them.
259 protected void handleItemRefNameReferenceUpdate() {
260 if(newRefNameOnUpdate != null && oldRefNameOnUpdate!= null) {
261 // We have work to do.
262 if(logger.isDebugEnabled()) {
263 String eol = System.getProperty("line.separator");
264 logger.debug("Need to find and update references to Item."+eol
265 +" Old refName" + oldRefNameOnUpdate + eol
266 +" New refName" + newRefNameOnUpdate);
268 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
269 RepositoryClient repoClient = getRepositoryClient(ctx);
270 // HACK - this should be defined for each handler, as with
271 // AuthorityResource.getRefPropName()
272 String refNameProp = ServiceBindingUtils.AUTH_REF_PROP;
274 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient,
275 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp );
276 if(logger.isDebugEnabled()) {
277 logger.debug("Updated "+nUpdated+" instances of oldRefName to newRefName");
283 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
284 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
285 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
286 String shortDisplayName = "";
288 shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
289 } catch (PropertyNotFoundException pnfe) {
290 // Do nothing on exception. Some vocabulary schemas may not include a short display name.
292 if (Tools.isEmpty(shortIdentifier)) {
293 String generatedShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
294 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
298 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
300 String authorityRefBaseName) throws Exception {
301 DocumentModel docModel = wrapDoc.getWrappedObject();
302 String suppliedRefName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME);
304 // Temporarily accept client-supplied refName values, rather than always generating such values.
305 // Remove first block and the surrounding 'if' statement when clients should no longer supply refName values.
306 if(!Tools.isEmpty(suppliedRefName) ) {
307 // Supplied refName must at least be legal
308 RefName.AuthorityItem item = RefName.AuthorityItem.parse(suppliedRefName);
310 logger.error("Passed refName for authority item not legal: "+suppliedRefName);
311 suppliedRefName = null; // Clear this and compute a new one below.
314 // Recheck, in case we cleared it for being illegal
315 if(Tools.isEmpty(suppliedRefName) ) {
316 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
317 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
318 if (Tools.isEmpty(authorityRefBaseName)) {
319 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
321 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
322 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
323 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
328 * Check the logic around the parent pointer. Note that we only need do this on
329 * create, since we have logic to make this read-only on update.
333 * @throws Exception the exception
335 private void handleInAuthority(DocumentModel docModel) throws Exception {
336 docModel.setProperty(authorityItemCommonSchemaName,
337 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
342 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
345 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
347 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
349 // Add the CSID to the common part, since they may have fetched via the shortId.
350 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
351 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
352 unQObjectProperties.put("csid", csid);
355 return unQObjectProperties;
359 * Filters out selected values supplied in an update request.
361 * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
362 * that the link to the item's parent remains untouched.
364 * @param objectProps the properties filtered out from the update payload
365 * @param partMeta metadata for the object to fill
368 public void filterReadOnlyPropertiesForPart(
369 Map<String, Object> objectProps, ObjectPartType partMeta) {
370 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
371 String commonPartLabel = getServiceContext().getCommonPartLabel();
372 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
373 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
374 objectProps.remove(AuthorityItemJAXBSchema.CSID);
375 objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
376 // Enable when clients should no longer supply refName values
377 // objectProps.remove(AuthorityItemJAXBSchema.REF_NAME); // CSPACE-3178
383 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
384 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
385 super.extractAllParts(wrapDoc);
387 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
388 if (Tools.isTrue(showSiblings)) {
389 showSiblings(wrapDoc, ctx);
390 return; // actual result is returned on ctx.addOutputPart();
393 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
394 if (Tools.isTrue(showRelations)) {
395 showRelations(wrapDoc, ctx);
396 return; // actual result is returned on ctx.addOutputPart();
399 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
400 if (Tools.isTrue(showAllRelations)) {
401 showAllRelations(wrapDoc, ctx);
402 return; // actual result is returned on ctx.addOutputPart();
406 /** @return null on parent not found
408 protected String getParentCSID(String thisCSID) throws Exception {
409 String parentCSID = null;
411 String predicate = RelationshipType.HAS_BROADER.value();
412 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
413 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
414 if (parentList != null) {
415 if (parentList.size() == 0) {
418 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
419 parentCSID = relationListItem.getObjectCsid();
422 } catch (Exception e) {
423 logger.error("Could not find parent for this: " + thisCSID, e);
428 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
429 MultipartServiceContext ctx) throws Exception {
430 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
432 String predicate = RelationshipType.HAS_BROADER.value();
433 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
434 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
436 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
437 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
439 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
440 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
441 //Not optimal, but that's the current design spec.
443 for (RelationsCommonList.RelationListItem parent : parentList) {
444 childrenList.add(parent);
447 long childrenSize = childrenList.size();
448 childrenListOuter.setTotalItems(childrenSize);
449 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
451 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
452 ctx.addOutputPart(relationsPart);
455 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
456 MultipartServiceContext ctx) throws Exception {
457 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
458 String parentCSID = getParentCSID(thisCSID);
459 if (parentCSID == null) {
460 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
464 String predicate = RelationshipType.HAS_BROADER.value();
465 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
466 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
468 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
471 RelationsCommonList.RelationListItem item = null;
472 for (RelationsCommonList.RelationListItem sibling : siblingList) {
473 if (thisCSID.equals(sibling.getSubjectCsid())) {
474 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.
477 //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.
478 for (RelationsCommonList.RelationListItem self : toRemoveList) {
479 removeFromList(siblingList, self);
482 long siblingSize = siblingList.size();
483 siblingListOuter.setTotalItems(siblingSize);
484 siblingListOuter.setItemsInPage(siblingSize);
486 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
487 ctx.addOutputPart(relationsPart);
490 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
491 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
493 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
494 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
496 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
497 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
500 subjectList.addAll(objectList);
502 //now subjectList actually has records BOTH where thisCSID is subject and object.
503 long relatedSize = subjectList.size();
504 subjectListOuter.setTotalItems(relatedSize);
505 subjectListOuter.setItemsInPage(relatedSize);
507 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
508 ctx.addOutputPart(relationsPart);
511 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
512 super.fillAllParts(wrapDoc, action);
514 ServiceContext ctx = getServiceContext();
515 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
516 DocumentModel documentModel = (wrapDoc.getWrappedObject());
517 String itemCsid = documentModel.getName();
519 //UPDATE and CREATE will call. Updates relations part
520 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
522 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
523 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
527 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
528 super.completeCreate(wrapDoc);
529 handleRelationsPayload(wrapDoc, false);
532 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
533 super.completeUpdate(wrapDoc);
534 handleRelationsPayload(wrapDoc, true);
535 handleItemRefNameReferenceUpdate();
538 // Note that we must do this after we have completed the Update, so that the repository has the
539 // info for the item itself. The relations code must call into the repo to get info for each end.
540 // This could be optimized to pass in the parent docModel, since it will often be one end.
541 // Nevertheless, we should complete the item save before we do work on the relations, especially
542 // since a save on Create might fail, and we would not want to create relations for something
543 // that may not be created...
544 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
545 ServiceContext ctx = getServiceContext();
546 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
547 DocumentModel documentModel = (wrapDoc.getWrappedObject());
548 String itemCsid = documentModel.getName();
550 //Updates relations part
551 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
553 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
554 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
556 //now we add part for relations list
557 //ServiceContext ctx = getServiceContext();
558 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
559 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
562 /** updateRelations strategy:
564 go through inboundList, remove anything from childList that matches from childList
565 go through inboundList, remove anything from parentList that matches from parentList
566 go through parentList, delete all remaining
567 go through childList, delete all remaining
568 go through actionList, add all remaining.
569 check for duplicate children
570 check for more than one parent.
572 inboundList parentList childList actionList
573 ---------------- --------------- ---------------- ----------------
574 child-a parent-c child-a child-b
575 child-b parent-d child-c
578 private RelationsCommonList updateRelations(
579 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
581 if(logger.isTraceEnabled()) {
582 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID);
584 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
586 return null; //nothing to do--they didn't send a list of relations.
588 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
589 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
590 List<RelationsCommonList.RelationListItem> actionList = newList();
591 List<RelationsCommonList.RelationListItem> childList = null;
592 List<RelationsCommonList.RelationListItem> parentList = null;
593 DocumentModel docModel = wrapDoc.getWrappedObject();
595 ServiceContext ctx = getServiceContext();
596 //Do magic replacement of ${itemCSID} and fix URI's.
597 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
599 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
600 UriInfo uriInfo = ctx.getUriInfo();
601 MultivaluedMap queryParams = uriInfo.getQueryParameters();
604 //Run getList() once as sent to get childListOuter:
605 String predicate = RelationshipType.HAS_BROADER.value();
606 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
607 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
608 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
609 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
610 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
611 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
613 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
614 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
615 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
616 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
617 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
620 childList = childListOuter.getRelationListItem();
621 parentList = parentListOuter.getRelationListItem();
623 if (parentList.size() > 1) {
624 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
627 if(logger.isTraceEnabled()) {
628 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" got existing relations.");
633 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
634 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
635 // and so the CSID for those may be null
636 if (itemCSID.equals(inboundItem.getObject().getCsid())
637 && inboundItem.getPredicate().equals(HAS_BROADER)) {
638 //then this is an item that says we have a child. That child is inboundItem
639 RelationsCommonList.RelationListItem childItem =
640 (childList==null)?null:findInList(childList, inboundItem);
641 if (childItem != null) {
642 removeFromList(childList, childItem); //exists, just take it off delete list
644 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
646 ensureChildHasNoOtherParents(ctx, queryParams, inboundItem.getSubject().getCsid());
648 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
649 && inboundItem.getPredicate().equals(HAS_BROADER)) {
650 //then this is an item that says we have a parent. inboundItem is that parent.
651 RelationsCommonList.RelationListItem parentItem =
652 (parentList==null)?null:findInList(parentList, inboundItem);
653 if (parentItem != null) {
654 removeFromList(parentList, parentItem); //exists, just take it off delete list
656 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
659 logger.warn("Element didn't match parent or child, but may have partial fields that match. inboundItem: " + inboundItem);
660 //not dealing with: hasNarrower or any other predicate.
663 if(logger.isTraceEnabled()) {
664 String dump = dumpLists(itemCSID, parentList, childList, actionList);
665 //System.out.println("====dump====="+CR+dump);
666 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
669 if(logger.isTraceEnabled()) {
670 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" deleting "
671 +parentList.size()+" existing parents and "+childList.size()+" existing children.");
673 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
674 deleteRelations(childList, ctx, "childList");
676 if(logger.isTraceEnabled()) {
677 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" adding "
678 +actionList.size()+" new parents and children.");
680 createRelations(actionList, ctx);
681 if(logger.isTraceEnabled()) {
682 logger.trace("AuthItemDocHndler.updateRelations for: "+itemCSID+" done.");
684 //We return all elements on the inbound list, since we have just worked to make them exist in the system
685 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
686 return relationsCommonListBody;
689 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
690 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
691 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
692 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
693 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
694 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
695 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
696 deleteRelations(parentList, ctx, "parentList-delete");
699 private String dumpLists(String itemCSID,
700 List<RelationsCommonList.RelationListItem> parentList,
701 List<RelationsCommonList.RelationListItem> childList,
702 List<RelationsCommonList.RelationListItem> actionList) {
703 StringBuffer sb = new StringBuffer();
704 sb.append("itemCSID: " + itemCSID + CR);
705 sb.append(dumpList(parentList, "parentList"));
706 sb.append(dumpList(childList, "childList"));
707 sb.append(dumpList(actionList, "actionList"));
708 return sb.toString();
710 private final static String CR = "\r\n";
711 private final static String T = " ";
713 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
714 StringBuffer sb = new StringBuffer();
716 if (list.size() > 0) {
717 sb.append("=========== " + label + " ==========" + CR);
719 for (RelationsCommonList.RelationListItem item : list) {
721 T + item.getSubject().getCsid() //+T4 + item.getSubject().getUri()
722 + T + item.getPredicate()
723 + T + item.getObject().getCsid() //+T4 + item.getObject().getUri()
724 + CR //+"subject:{"+item.getSubject()+"}\r\n object:{"+item.getObject()+"}"
725 //+ CR + "relation-record: {"+item+"}"
730 return sb.toString();
733 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
734 * and sets URI correctly for related items.
735 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
737 protected void fixupInboundListItems(ServiceContext ctx,
738 List<RelationsCommonList.RelationListItem> inboundList,
739 DocumentModel docModel,
740 String itemCSID) throws Exception {
741 String thisURI = this.getUri(docModel);
742 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
743 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
744 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
745 RelationsDocListItem inboundItemObject = inboundItem.getObject();
746 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
748 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
749 inboundItem.setObjectCsid(itemCSID);
750 inboundItemObject.setCsid(itemCSID);
751 //inboundItemObject.setUri(getUri(docModel));
754 String objectCsid = inboundItemObject.getCsid();
755 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
756 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
757 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
758 inboundItemObject.setUri(uri); //CSPACE-4037
761 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
763 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
764 inboundItem.setSubjectCsid(itemCSID);
765 inboundItemSubject.setCsid(itemCSID);
766 //inboundItemSubject.setUri(getUri(docModel));
769 String subjectCsid = inboundItemSubject.getCsid();
770 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
771 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
772 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
773 inboundItemSubject.setUri(uri); //CSPACE-4037
776 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
781 // this method calls the RelationResource to have it create the relations and persist them.
782 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
783 for (RelationsCommonList.RelationListItem item : inboundList) {
784 RelationsCommon rc = new RelationsCommon();
785 //rc.setCsid(item.getCsid());
786 //todo: assignTo(item, rc);
787 RelationsDocListItem itemSubject = item.getSubject();
788 RelationsDocListItem itemObject = item.getObject();
790 // Set at least one of CSID and refName for Subject and Object
791 // Either value might be null for for each of Subject and Object
792 String subjectCsid = itemSubject.getCsid();
793 rc.setSubjectCsid(subjectCsid);
794 rc.setDocumentId1(subjectCsid); // populate legacy field for backward compatibility
796 String objCsid = itemObject.getCsid();
797 rc.setObjectCsid(objCsid);
798 rc.setDocumentId2(objCsid); // populate legacy field for backward compatibility
800 rc.setSubjectRefName(itemSubject.getRefName());
801 rc.setObjectRefName(itemObject.getRefName());
803 rc.setRelationshipType(item.getPredicate());
804 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
805 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
807 // This is superfluous, since it will be fetched by the Relations Create logic.
808 rc.setSubjectDocumentType(itemSubject.getDocumentType());
809 rc.setObjectDocumentType(itemObject.getDocumentType());
810 // populate legacy fields for backward compatibility
811 rc.setDocumentType1(itemSubject.getDocumentType());
812 rc.setDocumentType2(itemObject.getDocumentType());
814 // This is superfluous, since it will be fetched by the Relations Create logic.
815 rc.setSubjectUri(itemSubject.getUri());
816 rc.setObjectUri(itemObject.getUri());
817 // May not have the info here. Only really require CSID or refName.
818 // Rest is handled in the Relation create mechanism
819 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
821 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
822 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
823 payloadOut.addPart(outputPart);
824 //System.out.println("\r\n==== TO CREATE: "+rc.getDocumentId1()+"==>"+rc.getPredicate()+"==>"+rc.getDocumentId2());
825 RelationResource relationResource = new RelationResource();
826 Object res = relationResource.create(ctx.getResourceMap(),
827 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
831 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
833 //if (list.size()>0){ logger.info("==== deleteRelations from : "+listName); }
834 for (RelationsCommonList.RelationListItem item : list) {
835 RelationResource relationResource = new RelationResource();
836 //logger.info("==== TO DELETE: " + item.getCsid() + ": " + item.getSubject().getCsid() + "--" + item.getPredicate() + "-->" + item.getObject().getCsid());
837 Object res = relationResource.delete(item.getCsid());
839 } catch (Throwable t) {
840 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
845 private List<RelationsCommonList.RelationListItem> newList() {
846 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
850 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
851 List<RelationsCommonList.RelationListItem> result = newList();
852 for (RelationsCommonList.RelationListItem item : inboundList) {
858 private RelationsCommonList.RelationListItem findInList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
859 for (RelationsCommonList.RelationListItem listItem : list) {
860 if (itemsEqual(listItem, item)) { //equals must be defined, else
867 private boolean itemsEqual(RelationsCommonList.RelationListItem item, RelationsCommonList.RelationListItem item2) {
868 if (item == null || item2 == null) {
871 RelationsDocListItem subj1 = item.getSubject();
872 RelationsDocListItem subj2 = item2.getSubject();
873 RelationsDocListItem obj1 = item.getObject();
874 RelationsDocListItem obj2 = item2.getObject();
876 return (subj1.getCsid().equals(subj2.getCsid()))
877 && (obj1.getCsid().equals(obj1.getCsid()))
878 && ((item.getPredicate().equals(item2.getPredicate()))
879 && (item.getRelationshipType().equals(item2.getRelationshipType())))
880 && (obj1.getDocumentType().equals(obj2.getDocumentType()))
881 && (subj1.getDocumentType().equals(subj2.getDocumentType()));
884 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
888 /* don't even THINK of re-using this method.
889 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
891 private String extractInAuthorityCSID(String uri) {
892 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
893 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
894 Matcher m = p.matcher(uri);
896 if (m.groupCount() < 3) {
897 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
900 //String service = m.group(1);
901 String inauth = m.group(2);
902 //String theRest = m.group(3);
904 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
907 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
912 //ensures CSPACE-4042
913 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
914 String authorityCSID = extractInAuthorityCSID(thisURI);
915 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
916 if (Tools.isBlank(authorityCSID)
917 || Tools.isBlank(authorityCSIDForInbound)
918 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
919 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
923 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
924 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
925 ServiceContext ctx = getServiceContext();
926 MultivaluedMap queryParams = ctx.getQueryParams();
927 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
928 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
929 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
931 RelationResource relationResource = new RelationResource();
932 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
933 return relationsCommonList;
935 //============================= END TODO refactor ==========================