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;
86 // Used to determine when the displayName changes as part of the update.
87 protected String oldDisplayNameOnUpdate = null;
88 protected String oldRefNameOnUpdate = null;
89 protected String newRefNameOnUpdate = null;
91 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
92 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
95 public String getInAuthority() {
99 public void setInAuthority(String inAuthority) {
100 this.inAuthority = inAuthority;
103 /** Subclasses may override this to customize the URI segment. */
104 public String getAuthorityServicePath() {
105 return getServiceContext().getServiceName().toLowerCase(); // Laramie20110510 CSPACE-3932
109 public String getUri(DocumentModel docModel) {
110 // Laramie20110510 CSPACE-3932
111 String authorityServicePath = getAuthorityServicePath();
112 return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
115 public String getAuthorityRefNameBase() {
116 return this.authorityRefNameBase;
119 public void setAuthorityRefNameBase(String value) {
120 this.authorityRefNameBase = value;
124 public List<ListResultField> getListItemsArray() throws DocumentException {
125 List<ListResultField> list = super.getListItemsArray();
126 int nFields = list.size();
127 // Ensure some common fields so do not depend upon config for general logic
128 boolean hasDisplayName = false;
129 boolean hasShortId = false;
130 boolean hasRefName = false;
131 boolean hasTermStatus = false;
132 for (int i = 0; i < nFields; i++) {
133 ListResultField field = list.get(i);
134 String elName = field.getElement();
135 if (AuthorityItemJAXBSchema.DISPLAY_NAME.equals(elName)) {
136 hasDisplayName = true;
137 } else if (AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName)) {
139 } else if (AuthorityItemJAXBSchema.REF_NAME.equals(elName)) {
141 } else if (AuthorityItemJAXBSchema.TERM_STATUS.equals(elName)) {
142 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);
164 if (!hasTermStatus) {
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 * @param newDisplayName the new display name
237 * @throws Exception the exception
239 protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
240 String newDisplayName) throws Exception {
241 RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
242 if (authItem == null) {
243 String err = "Authority Item has illegal refName: " + oldRefNameOnUpdate;
245 throw new IllegalArgumentException(err);
247 authItem.displayName = newDisplayName;
248 String updatedRefName = authItem.toString();
249 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
250 return updatedRefName;
254 * Checks to see if the refName has changed, and if so,
255 * uses utilities to find all references and update them.
257 protected void handleItemRefNameReferenceUpdate() {
258 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
259 // We have work to do.
260 if (logger.isDebugEnabled()) {
261 String eol = System.getProperty("line.separator");
262 logger.debug("Need to find and update references to Item." + eol
263 + " Old refName" + oldRefNameOnUpdate + eol
264 + " New refName" + newRefNameOnUpdate);
266 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
267 RepositoryClient repoClient = getRepositoryClient(ctx);
268 // FIXME HACK - this should be defined for each handler, as with
269 // AuthorityResource.getRefPropName()
270 String refNameProp = ServiceBindingUtils.AUTH_REF_PROP;
272 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient,
273 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
274 if (logger.isDebugEnabled()) {
275 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
281 * If no short identifier was provided in the input payload,
282 * generate a short identifier from the display name.
284 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
285 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
286 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
287 String shortDisplayName = "";
289 shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
290 } catch (PropertyNotFoundException pnfe) {
291 // Do nothing on exception. Some vocabulary schemas may not include a short display name.
293 if (Tools.isEmpty(shortIdentifier)) {
294 String generatedShortIdentifier =
295 AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
296 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
301 * Generate a refName for the authority item from the short identifier
304 * All refNames for authority items are generated. If a client supplies
305 * a refName, it will be overwritten during create (per this method)
306 * or discarded during update (per filterReadOnlyPropertiesForPart).
308 * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
311 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
313 String authorityRefBaseName) throws Exception {
314 DocumentModel docModel = wrapDoc.getWrappedObject();
315 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
316 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
317 if (Tools.isEmpty(authorityRefBaseName)) {
318 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
320 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
321 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
322 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
326 * Check the logic around the parent pointer. Note that we only need do this on
327 * create, since we have logic to make this read-only on update.
331 * @throws Exception the exception
333 private void handleInAuthority(DocumentModel docModel) throws Exception {
334 docModel.setProperty(authorityItemCommonSchemaName,
335 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
340 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
343 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
345 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
347 // Add the CSID to the common part, since they may have fetched via the shortId.
348 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
349 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
350 unQObjectProperties.put("csid", csid);
353 return unQObjectProperties;
357 * Filters out selected values supplied in an update request.
359 * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
360 * that the link to the item's parent remains untouched.
362 * @param objectProps the properties filtered out from the update payload
363 * @param partMeta metadata for the object to fill
366 public void filterReadOnlyPropertiesForPart(
367 Map<String, Object> objectProps, ObjectPartType partMeta) {
368 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
369 String commonPartLabel = getServiceContext().getCommonPartLabel();
370 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
371 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
372 objectProps.remove(AuthorityItemJAXBSchema.CSID);
373 objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
374 objectProps.remove(AuthorityItemJAXBSchema.REF_NAME);
379 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
380 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
381 super.extractAllParts(wrapDoc);
383 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
384 if (Tools.isTrue(showSiblings)) {
385 showSiblings(wrapDoc, ctx);
386 return; // actual result is returned on ctx.addOutputPart();
389 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
390 if (Tools.isTrue(showRelations)) {
391 showRelations(wrapDoc, ctx);
392 return; // actual result is returned on ctx.addOutputPart();
395 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
396 if (Tools.isTrue(showAllRelations)) {
397 showAllRelations(wrapDoc, ctx);
398 return; // actual result is returned on ctx.addOutputPart();
402 /** @return null on parent not found
404 protected String getParentCSID(String thisCSID) throws Exception {
405 String parentCSID = null;
407 String predicate = RelationshipType.HAS_BROADER.value();
408 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
409 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
410 if (parentList != null) {
411 if (parentList.size() == 0) {
414 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
415 parentCSID = relationListItem.getObjectCsid();
418 } catch (Exception e) {
419 logger.error("Could not find parent for this: " + thisCSID, e);
424 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
425 MultipartServiceContext ctx) throws Exception {
426 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
428 String predicate = RelationshipType.HAS_BROADER.value();
429 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
430 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
432 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
433 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
435 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
436 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
437 //Not optimal, but that's the current design spec.
439 for (RelationsCommonList.RelationListItem parent : parentList) {
440 childrenList.add(parent);
443 long childrenSize = childrenList.size();
444 childrenListOuter.setTotalItems(childrenSize);
445 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
447 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
448 ctx.addOutputPart(relationsPart);
451 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
452 MultipartServiceContext ctx) throws Exception {
453 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
454 String parentCSID = getParentCSID(thisCSID);
455 if (parentCSID == null) {
456 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
460 String predicate = RelationshipType.HAS_BROADER.value();
461 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
462 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
464 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
467 RelationsCommonList.RelationListItem item = null;
468 for (RelationsCommonList.RelationListItem sibling : siblingList) {
469 if (thisCSID.equals(sibling.getSubjectCsid())) {
470 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.
473 //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.
474 for (RelationsCommonList.RelationListItem self : toRemoveList) {
475 removeFromList(siblingList, self);
478 long siblingSize = siblingList.size();
479 siblingListOuter.setTotalItems(siblingSize);
480 siblingListOuter.setItemsInPage(siblingSize);
482 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
483 ctx.addOutputPart(relationsPart);
486 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
487 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
489 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
490 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
492 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
493 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
496 subjectList.addAll(objectList);
498 //now subjectList actually has records BOTH where thisCSID is subject and object.
499 long relatedSize = subjectList.size();
500 subjectListOuter.setTotalItems(relatedSize);
501 subjectListOuter.setItemsInPage(relatedSize);
503 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
504 ctx.addOutputPart(relationsPart);
507 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
508 super.fillAllParts(wrapDoc, action);
510 ServiceContext ctx = getServiceContext();
511 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
512 DocumentModel documentModel = (wrapDoc.getWrappedObject());
513 String itemCsid = documentModel.getName();
515 //UPDATE and CREATE will call. Updates relations part
516 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
518 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
519 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
523 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
524 super.completeCreate(wrapDoc);
525 handleRelationsPayload(wrapDoc, false);
528 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
529 super.completeUpdate(wrapDoc);
530 handleRelationsPayload(wrapDoc, true);
531 handleItemRefNameReferenceUpdate();
534 // Note that we must do this after we have completed the Update, so that the repository has the
535 // info for the item itself. The relations code must call into the repo to get info for each end.
536 // This could be optimized to pass in the parent docModel, since it will often be one end.
537 // Nevertheless, we should complete the item save before we do work on the relations, especially
538 // since a save on Create might fail, and we would not want to create relations for something
539 // that may not be created...
540 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
541 ServiceContext ctx = getServiceContext();
542 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
543 DocumentModel documentModel = (wrapDoc.getWrappedObject());
544 String itemCsid = documentModel.getName();
546 //Updates relations part
547 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
549 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
550 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
552 //now we add part for relations list
553 //ServiceContext ctx = getServiceContext();
554 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
555 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
558 /** updateRelations strategy:
560 go through inboundList, remove anything from childList that matches from childList
561 go through inboundList, remove anything from parentList that matches from parentList
562 go through parentList, delete all remaining
563 go through childList, delete all remaining
564 go through actionList, add all remaining.
565 check for duplicate children
566 check for more than one parent.
568 inboundList parentList childList actionList
569 ---------------- --------------- ---------------- ----------------
570 child-a parent-c child-a child-b
571 child-b parent-d child-c
574 private RelationsCommonList updateRelations(
575 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
577 if (logger.isTraceEnabled()) {
578 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
580 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
582 return null; //nothing to do--they didn't send a list of relations.
584 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
585 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
586 List<RelationsCommonList.RelationListItem> actionList = newList();
587 List<RelationsCommonList.RelationListItem> childList = null;
588 List<RelationsCommonList.RelationListItem> parentList = null;
589 DocumentModel docModel = wrapDoc.getWrappedObject();
591 ServiceContext ctx = getServiceContext();
592 //Do magic replacement of ${itemCSID} and fix URI's.
593 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
595 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
596 UriInfo uriInfo = ctx.getUriInfo();
597 MultivaluedMap queryParams = uriInfo.getQueryParameters();
600 //Run getList() once as sent to get childListOuter:
601 String predicate = RelationshipType.HAS_BROADER.value();
602 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
603 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
604 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
605 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
606 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
607 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
609 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
610 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
611 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
612 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
613 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
616 childList = childListOuter.getRelationListItem();
617 parentList = parentListOuter.getRelationListItem();
619 if (parentList.size() > 1) {
620 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
623 if (logger.isTraceEnabled()) {
624 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
629 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
630 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
631 // and so the CSID for those may be null
632 if (itemCSID.equals(inboundItem.getObject().getCsid())
633 && inboundItem.getPredicate().equals(HAS_BROADER)) {
634 //then this is an item that says we have a child. That child is inboundItem
635 RelationsCommonList.RelationListItem childItem =
636 (childList == null) ? null : findInList(childList, inboundItem);
637 if (childItem != null) {
638 removeFromList(childList, childItem); //exists, just take it off delete list
640 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
642 ensureChildHasNoOtherParents(ctx, queryParams, inboundItem.getSubject().getCsid());
644 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
645 && inboundItem.getPredicate().equals(HAS_BROADER)) {
646 //then this is an item that says we have a parent. inboundItem is that parent.
647 RelationsCommonList.RelationListItem parentItem =
648 (parentList == null) ? null : findInList(parentList, inboundItem);
649 if (parentItem != null) {
650 removeFromList(parentList, parentItem); //exists, just take it off delete list
652 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
655 logger.warn("Element didn't match parent or child, but may have partial fields that match. inboundItem: " + inboundItem);
656 //not dealing with: hasNarrower or any other predicate.
659 if (logger.isTraceEnabled()) {
660 String dump = dumpLists(itemCSID, parentList, childList, actionList);
661 //System.out.println("====dump====="+CR+dump);
662 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
665 if (logger.isTraceEnabled()) {
666 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
667 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
669 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
670 deleteRelations(childList, ctx, "childList");
672 if (logger.isTraceEnabled()) {
673 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
674 + actionList.size() + " new parents and children.");
676 createRelations(actionList, ctx);
677 if (logger.isTraceEnabled()) {
678 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
680 //We return all elements on the inbound list, since we have just worked to make them exist in the system
681 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
682 return relationsCommonListBody;
685 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
686 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
687 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
688 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
689 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
690 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
691 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
692 deleteRelations(parentList, ctx, "parentList-delete");
695 private String dumpLists(String itemCSID,
696 List<RelationsCommonList.RelationListItem> parentList,
697 List<RelationsCommonList.RelationListItem> childList,
698 List<RelationsCommonList.RelationListItem> actionList) {
699 StringBuffer sb = new StringBuffer();
700 sb.append("itemCSID: " + itemCSID + CR);
701 sb.append(dumpList(parentList, "parentList"));
702 sb.append(dumpList(childList, "childList"));
703 sb.append(dumpList(actionList, "actionList"));
704 return sb.toString();
706 private final static String CR = "\r\n";
707 private final static String T = " ";
709 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
710 StringBuffer sb = new StringBuffer();
712 if (list.size() > 0) {
713 sb.append("=========== " + label + " ==========" + CR);
715 for (RelationsCommonList.RelationListItem item : list) {
717 T + item.getSubject().getCsid() //+T4 + item.getSubject().getUri()
718 + T + item.getPredicate()
719 + T + item.getObject().getCsid() //+T4 + item.getObject().getUri()
720 + CR //+"subject:{"+item.getSubject()+"}\r\n object:{"+item.getObject()+"}"
721 //+ CR + "relation-record: {"+item+"}"
726 return sb.toString();
729 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
730 * and sets URI correctly for related items.
731 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
733 protected void fixupInboundListItems(ServiceContext ctx,
734 List<RelationsCommonList.RelationListItem> inboundList,
735 DocumentModel docModel,
736 String itemCSID) throws Exception {
737 String thisURI = this.getUri(docModel);
738 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
739 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
740 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
741 RelationsDocListItem inboundItemObject = inboundItem.getObject();
742 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
744 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
745 inboundItem.setObjectCsid(itemCSID);
746 inboundItemObject.setCsid(itemCSID);
747 //inboundItemObject.setUri(getUri(docModel));
750 String objectCsid = inboundItemObject.getCsid();
751 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
752 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
753 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
754 inboundItemObject.setUri(uri); //CSPACE-4037
757 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
759 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
760 inboundItem.setSubjectCsid(itemCSID);
761 inboundItemSubject.setCsid(itemCSID);
762 //inboundItemSubject.setUri(getUri(docModel));
765 String subjectCsid = inboundItemSubject.getCsid();
766 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
767 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
768 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
769 inboundItemSubject.setUri(uri); //CSPACE-4037
772 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
777 // this method calls the RelationResource to have it create the relations and persist them.
778 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
779 for (RelationsCommonList.RelationListItem item : inboundList) {
780 RelationsCommon rc = new RelationsCommon();
781 //rc.setCsid(item.getCsid());
782 //todo: assignTo(item, rc);
783 RelationsDocListItem itemSubject = item.getSubject();
784 RelationsDocListItem itemObject = item.getObject();
786 // Set at least one of CSID and refName for Subject and Object
787 // Either value might be null for for each of Subject and Object
788 String subjectCsid = itemSubject.getCsid();
789 rc.setSubjectCsid(subjectCsid);
790 rc.setDocumentId1(subjectCsid); // populate legacy field for backward compatibility
792 String objCsid = itemObject.getCsid();
793 rc.setObjectCsid(objCsid);
794 rc.setDocumentId2(objCsid); // populate legacy field for backward compatibility
796 rc.setSubjectRefName(itemSubject.getRefName());
797 rc.setObjectRefName(itemObject.getRefName());
799 rc.setRelationshipType(item.getPredicate());
800 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
801 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
803 // This is superfluous, since it will be fetched by the Relations Create logic.
804 rc.setSubjectDocumentType(itemSubject.getDocumentType());
805 rc.setObjectDocumentType(itemObject.getDocumentType());
806 // populate legacy fields for backward compatibility
807 rc.setDocumentType1(itemSubject.getDocumentType());
808 rc.setDocumentType2(itemObject.getDocumentType());
810 // This is superfluous, since it will be fetched by the Relations Create logic.
811 rc.setSubjectUri(itemSubject.getUri());
812 rc.setObjectUri(itemObject.getUri());
813 // May not have the info here. Only really require CSID or refName.
814 // Rest is handled in the Relation create mechanism
815 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
817 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
818 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
819 payloadOut.addPart(outputPart);
820 //System.out.println("\r\n==== TO CREATE: "+rc.getDocumentId1()+"==>"+rc.getPredicate()+"==>"+rc.getDocumentId2());
821 RelationResource relationResource = new RelationResource();
822 Object res = relationResource.create(ctx.getResourceMap(),
823 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
827 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
829 //if (list.size()>0){ logger.info("==== deleteRelations from : "+listName); }
830 for (RelationsCommonList.RelationListItem item : list) {
831 RelationResource relationResource = new RelationResource();
832 //logger.info("==== TO DELETE: " + item.getCsid() + ": " + item.getSubject().getCsid() + "--" + item.getPredicate() + "-->" + item.getObject().getCsid());
833 Object res = relationResource.delete(item.getCsid());
835 } catch (Throwable t) {
836 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
841 private List<RelationsCommonList.RelationListItem> newList() {
842 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
846 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
847 List<RelationsCommonList.RelationListItem> result = newList();
848 for (RelationsCommonList.RelationListItem item : inboundList) {
854 private RelationsCommonList.RelationListItem findInList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
855 for (RelationsCommonList.RelationListItem listItem : list) {
856 if (itemsEqual(listItem, item)) { //equals must be defined, else
863 private boolean itemsEqual(RelationsCommonList.RelationListItem item, RelationsCommonList.RelationListItem item2) {
864 if (item == null || item2 == null) {
867 RelationsDocListItem subj1 = item.getSubject();
868 RelationsDocListItem subj2 = item2.getSubject();
869 RelationsDocListItem obj1 = item.getObject();
870 RelationsDocListItem obj2 = item2.getObject();
872 return (subj1.getCsid().equals(subj2.getCsid()))
873 && (obj1.getCsid().equals(obj1.getCsid()))
874 && ((item.getPredicate().equals(item2.getPredicate()))
875 && (item.getRelationshipType().equals(item2.getRelationshipType())))
876 && (obj1.getDocumentType().equals(obj2.getDocumentType()))
877 && (subj1.getDocumentType().equals(subj2.getDocumentType()));
880 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
884 /* don't even THINK of re-using this method.
885 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
887 private String extractInAuthorityCSID(String uri) {
888 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
889 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
890 Matcher m = p.matcher(uri);
892 if (m.groupCount() < 3) {
893 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
896 //String service = m.group(1);
897 String inauth = m.group(2);
898 //String theRest = m.group(3);
900 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
903 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
908 //ensures CSPACE-4042
909 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
910 String authorityCSID = extractInAuthorityCSID(thisURI);
911 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
912 if (Tools.isBlank(authorityCSID)
913 || Tools.isBlank(authorityCSIDForInbound)
914 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
915 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
919 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
920 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
921 ServiceContext ctx = getServiceContext();
922 MultivaluedMap queryParams = ctx.getQueryParams();
923 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
924 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
925 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
927 RelationResource relationResource = new RelationResource();
928 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
929 return relationsCommonList;
931 //============================= END TODO refactor ==========================