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 * @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;
255 * Checks to see if the refName has changed, and if so,
256 * uses utilities to find all references and update them.
258 protected void handleItemRefNameReferenceUpdate() {
259 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
260 // We have work to do.
261 if (logger.isDebugEnabled()) {
262 String eol = System.getProperty("line.separator");
263 logger.debug("Need to find and update references to Item." + eol
264 + " Old refName" + oldRefNameOnUpdate + eol
265 + " New refName" + newRefNameOnUpdate);
267 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
268 RepositoryClient repoClient = getRepositoryClient(ctx);
269 // HACK - this should be defined for each handler, as with
270 // AuthorityResource.getRefPropName()
271 String refNameProp = ServiceBindingUtils.AUTH_REF_PROP;
273 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient,
274 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
275 if (logger.isDebugEnabled()) {
276 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
281 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
282 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
283 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
284 String shortDisplayName = "";
286 shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
287 } catch (PropertyNotFoundException pnfe) {
288 // Do nothing on exception. Some vocabulary schemas may not include a short display name.
290 if (Tools.isEmpty(shortIdentifier)) {
291 String generatedShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
292 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
296 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
298 String authorityRefBaseName) throws Exception {
299 DocumentModel docModel = wrapDoc.getWrappedObject();
300 String suppliedRefName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME);
302 // Temporarily accept client-supplied refName values, rather than always generating such values.
303 // Remove first block and the surrounding 'if' statement when clients should no longer supply refName values.
304 if (!Tools.isEmpty(suppliedRefName)) {
305 // Supplied refName must at least be legal
306 RefName.AuthorityItem item = RefName.AuthorityItem.parse(suppliedRefName);
308 logger.error("Passed refName for authority item not legal: " + suppliedRefName);
309 suppliedRefName = null; // Clear this and compute a new one below.
312 // Recheck, in case we cleared it for being illegal
313 if (Tools.isEmpty(suppliedRefName)) {
314 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
315 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
316 if (Tools.isEmpty(authorityRefBaseName)) {
317 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
319 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
320 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
321 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 // Enable when clients should no longer supply refName values
375 // objectProps.remove(AuthorityItemJAXBSchema.REF_NAME); // CSPACE-3178
381 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
382 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
383 super.extractAllParts(wrapDoc);
385 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
386 if (Tools.isTrue(showSiblings)) {
387 showSiblings(wrapDoc, ctx);
388 return; // actual result is returned on ctx.addOutputPart();
391 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
392 if (Tools.isTrue(showRelations)) {
393 showRelations(wrapDoc, ctx);
394 return; // actual result is returned on ctx.addOutputPart();
397 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
398 if (Tools.isTrue(showAllRelations)) {
399 showAllRelations(wrapDoc, ctx);
400 return; // actual result is returned on ctx.addOutputPart();
404 /** @return null on parent not found
406 protected String getParentCSID(String thisCSID) throws Exception {
407 String parentCSID = null;
409 String predicate = RelationshipType.HAS_BROADER.value();
410 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
411 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
412 if (parentList != null) {
413 if (parentList.size() == 0) {
416 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
417 parentCSID = relationListItem.getObjectCsid();
420 } catch (Exception e) {
421 logger.error("Could not find parent for this: " + thisCSID, e);
426 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
427 MultipartServiceContext ctx) throws Exception {
428 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
430 String predicate = RelationshipType.HAS_BROADER.value();
431 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
432 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
434 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
435 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
437 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
438 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
439 //Not optimal, but that's the current design spec.
441 for (RelationsCommonList.RelationListItem parent : parentList) {
442 childrenList.add(parent);
445 long childrenSize = childrenList.size();
446 childrenListOuter.setTotalItems(childrenSize);
447 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
449 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
450 ctx.addOutputPart(relationsPart);
453 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
454 MultipartServiceContext ctx) throws Exception {
455 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
456 String parentCSID = getParentCSID(thisCSID);
457 if (parentCSID == null) {
458 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
462 String predicate = RelationshipType.HAS_BROADER.value();
463 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
464 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
466 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
469 RelationsCommonList.RelationListItem item = null;
470 for (RelationsCommonList.RelationListItem sibling : siblingList) {
471 if (thisCSID.equals(sibling.getSubjectCsid())) {
472 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.
475 //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.
476 for (RelationsCommonList.RelationListItem self : toRemoveList) {
477 removeFromList(siblingList, self);
480 long siblingSize = siblingList.size();
481 siblingListOuter.setTotalItems(siblingSize);
482 siblingListOuter.setItemsInPage(siblingSize);
484 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
485 ctx.addOutputPart(relationsPart);
488 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
489 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
491 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
492 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
494 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
495 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
498 subjectList.addAll(objectList);
500 //now subjectList actually has records BOTH where thisCSID is subject and object.
501 long relatedSize = subjectList.size();
502 subjectListOuter.setTotalItems(relatedSize);
503 subjectListOuter.setItemsInPage(relatedSize);
505 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
506 ctx.addOutputPart(relationsPart);
509 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
510 super.fillAllParts(wrapDoc, action);
512 ServiceContext ctx = getServiceContext();
513 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
514 DocumentModel documentModel = (wrapDoc.getWrappedObject());
515 String itemCsid = documentModel.getName();
517 //UPDATE and CREATE will call. Updates relations part
518 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
520 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
521 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
525 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
526 super.completeCreate(wrapDoc);
527 handleRelationsPayload(wrapDoc, false);
530 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
531 super.completeUpdate(wrapDoc);
532 handleRelationsPayload(wrapDoc, true);
533 handleItemRefNameReferenceUpdate();
536 // Note that we must do this after we have completed the Update, so that the repository has the
537 // info for the item itself. The relations code must call into the repo to get info for each end.
538 // This could be optimized to pass in the parent docModel, since it will often be one end.
539 // Nevertheless, we should complete the item save before we do work on the relations, especially
540 // since a save on Create might fail, and we would not want to create relations for something
541 // that may not be created...
542 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
543 ServiceContext ctx = getServiceContext();
544 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
545 DocumentModel documentModel = (wrapDoc.getWrappedObject());
546 String itemCsid = documentModel.getName();
548 //Updates relations part
549 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
551 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
552 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
554 //now we add part for relations list
555 //ServiceContext ctx = getServiceContext();
556 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
557 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
560 /** updateRelations strategy:
562 go through inboundList, remove anything from childList that matches from childList
563 go through inboundList, remove anything from parentList that matches from parentList
564 go through parentList, delete all remaining
565 go through childList, delete all remaining
566 go through actionList, add all remaining.
567 check for duplicate children
568 check for more than one parent.
570 inboundList parentList childList actionList
571 ---------------- --------------- ---------------- ----------------
572 child-a parent-c child-a child-b
573 child-b parent-d child-c
576 private RelationsCommonList updateRelations(
577 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
579 if (logger.isTraceEnabled()) {
580 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
582 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
584 return null; //nothing to do--they didn't send a list of relations.
586 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
587 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
588 List<RelationsCommonList.RelationListItem> actionList = newList();
589 List<RelationsCommonList.RelationListItem> childList = null;
590 List<RelationsCommonList.RelationListItem> parentList = null;
591 DocumentModel docModel = wrapDoc.getWrappedObject();
593 ServiceContext ctx = getServiceContext();
594 //Do magic replacement of ${itemCSID} and fix URI's.
595 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
597 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
598 UriInfo uriInfo = ctx.getUriInfo();
599 MultivaluedMap queryParams = uriInfo.getQueryParameters();
602 //Run getList() once as sent to get childListOuter:
603 String predicate = RelationshipType.HAS_BROADER.value();
604 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
605 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
606 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
607 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
608 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
609 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
611 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
612 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
613 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
614 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
615 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
618 childList = childListOuter.getRelationListItem();
619 parentList = parentListOuter.getRelationListItem();
621 if (parentList.size() > 1) {
622 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
625 if (logger.isTraceEnabled()) {
626 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
631 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
632 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
633 // and so the CSID for those may be null
634 if (itemCSID.equals(inboundItem.getObject().getCsid())
635 && inboundItem.getPredicate().equals(HAS_BROADER)) {
636 //then this is an item that says we have a child. That child is inboundItem
637 RelationsCommonList.RelationListItem childItem =
638 (childList == null) ? null : findInList(childList, inboundItem);
639 if (childItem != null) {
640 removeFromList(childList, childItem); //exists, just take it off delete list
642 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
644 ensureChildHasNoOtherParents(ctx, queryParams, inboundItem.getSubject().getCsid());
646 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
647 && inboundItem.getPredicate().equals(HAS_BROADER)) {
648 //then this is an item that says we have a parent. inboundItem is that parent.
649 RelationsCommonList.RelationListItem parentItem =
650 (parentList == null) ? null : findInList(parentList, inboundItem);
651 if (parentItem != null) {
652 removeFromList(parentList, parentItem); //exists, just take it off delete list
654 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
657 logger.warn("Element didn't match parent or child, but may have partial fields that match. inboundItem: " + inboundItem);
658 //not dealing with: hasNarrower or any other predicate.
661 if (logger.isTraceEnabled()) {
662 String dump = dumpLists(itemCSID, parentList, childList, actionList);
663 //System.out.println("====dump====="+CR+dump);
664 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
667 if (logger.isTraceEnabled()) {
668 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
669 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
671 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
672 deleteRelations(childList, ctx, "childList");
674 if (logger.isTraceEnabled()) {
675 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
676 + actionList.size() + " new parents and children.");
678 createRelations(actionList, ctx);
679 if (logger.isTraceEnabled()) {
680 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
682 //We return all elements on the inbound list, since we have just worked to make them exist in the system
683 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
684 return relationsCommonListBody;
687 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
688 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
689 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
690 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
691 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
692 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
693 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
694 deleteRelations(parentList, ctx, "parentList-delete");
697 private String dumpLists(String itemCSID,
698 List<RelationsCommonList.RelationListItem> parentList,
699 List<RelationsCommonList.RelationListItem> childList,
700 List<RelationsCommonList.RelationListItem> actionList) {
701 StringBuffer sb = new StringBuffer();
702 sb.append("itemCSID: " + itemCSID + CR);
703 sb.append(dumpList(parentList, "parentList"));
704 sb.append(dumpList(childList, "childList"));
705 sb.append(dumpList(actionList, "actionList"));
706 return sb.toString();
708 private final static String CR = "\r\n";
709 private final static String T = " ";
711 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
712 StringBuffer sb = new StringBuffer();
714 if (list.size() > 0) {
715 sb.append("=========== " + label + " ==========" + CR);
717 for (RelationsCommonList.RelationListItem item : list) {
719 T + item.getSubject().getCsid() //+T4 + item.getSubject().getUri()
720 + T + item.getPredicate()
721 + T + item.getObject().getCsid() //+T4 + item.getObject().getUri()
722 + CR //+"subject:{"+item.getSubject()+"}\r\n object:{"+item.getObject()+"}"
723 //+ CR + "relation-record: {"+item+"}"
728 return sb.toString();
731 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
732 * and sets URI correctly for related items.
733 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
735 protected void fixupInboundListItems(ServiceContext ctx,
736 List<RelationsCommonList.RelationListItem> inboundList,
737 DocumentModel docModel,
738 String itemCSID) throws Exception {
739 String thisURI = this.getUri(docModel);
740 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
741 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
742 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
743 RelationsDocListItem inboundItemObject = inboundItem.getObject();
744 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
746 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
747 inboundItem.setObjectCsid(itemCSID);
748 inboundItemObject.setCsid(itemCSID);
749 //inboundItemObject.setUri(getUri(docModel));
752 String objectCsid = inboundItemObject.getCsid();
753 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
754 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
755 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
756 inboundItemObject.setUri(uri); //CSPACE-4037
759 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
761 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
762 inboundItem.setSubjectCsid(itemCSID);
763 inboundItemSubject.setCsid(itemCSID);
764 //inboundItemSubject.setUri(getUri(docModel));
767 String subjectCsid = inboundItemSubject.getCsid();
768 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
769 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
770 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
771 inboundItemSubject.setUri(uri); //CSPACE-4037
774 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
779 // this method calls the RelationResource to have it create the relations and persist them.
780 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
781 for (RelationsCommonList.RelationListItem item : inboundList) {
782 RelationsCommon rc = new RelationsCommon();
783 //rc.setCsid(item.getCsid());
784 //todo: assignTo(item, rc);
785 RelationsDocListItem itemSubject = item.getSubject();
786 RelationsDocListItem itemObject = item.getObject();
788 // Set at least one of CSID and refName for Subject and Object
789 // Either value might be null for for each of Subject and Object
790 String subjectCsid = itemSubject.getCsid();
791 rc.setSubjectCsid(subjectCsid);
792 rc.setDocumentId1(subjectCsid); // populate legacy field for backward compatibility
794 String objCsid = itemObject.getCsid();
795 rc.setObjectCsid(objCsid);
796 rc.setDocumentId2(objCsid); // populate legacy field for backward compatibility
798 rc.setSubjectRefName(itemSubject.getRefName());
799 rc.setObjectRefName(itemObject.getRefName());
801 rc.setRelationshipType(item.getPredicate());
802 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
803 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
805 // This is superfluous, since it will be fetched by the Relations Create logic.
806 rc.setSubjectDocumentType(itemSubject.getDocumentType());
807 rc.setObjectDocumentType(itemObject.getDocumentType());
808 // populate legacy fields for backward compatibility
809 rc.setDocumentType1(itemSubject.getDocumentType());
810 rc.setDocumentType2(itemObject.getDocumentType());
812 // This is superfluous, since it will be fetched by the Relations Create logic.
813 rc.setSubjectUri(itemSubject.getUri());
814 rc.setObjectUri(itemObject.getUri());
815 // May not have the info here. Only really require CSID or refName.
816 // Rest is handled in the Relation create mechanism
817 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
819 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
820 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
821 payloadOut.addPart(outputPart);
822 //System.out.println("\r\n==== TO CREATE: "+rc.getDocumentId1()+"==>"+rc.getPredicate()+"==>"+rc.getDocumentId2());
823 RelationResource relationResource = new RelationResource();
824 Object res = relationResource.create(ctx.getResourceMap(),
825 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
829 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
831 //if (list.size()>0){ logger.info("==== deleteRelations from : "+listName); }
832 for (RelationsCommonList.RelationListItem item : list) {
833 RelationResource relationResource = new RelationResource();
834 //logger.info("==== TO DELETE: " + item.getCsid() + ": " + item.getSubject().getCsid() + "--" + item.getPredicate() + "-->" + item.getObject().getCsid());
835 Object res = relationResource.delete(item.getCsid());
837 } catch (Throwable t) {
838 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
843 private List<RelationsCommonList.RelationListItem> newList() {
844 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
848 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
849 List<RelationsCommonList.RelationListItem> result = newList();
850 for (RelationsCommonList.RelationListItem item : inboundList) {
856 private RelationsCommonList.RelationListItem findInList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
857 for (RelationsCommonList.RelationListItem listItem : list) {
858 if (itemsEqual(listItem, item)) { //equals must be defined, else
865 private boolean itemsEqual(RelationsCommonList.RelationListItem item, RelationsCommonList.RelationListItem item2) {
866 if (item == null || item2 == null) {
869 RelationsDocListItem subj1 = item.getSubject();
870 RelationsDocListItem subj2 = item2.getSubject();
871 RelationsDocListItem obj1 = item.getObject();
872 RelationsDocListItem obj2 = item2.getObject();
874 return (subj1.getCsid().equals(subj2.getCsid()))
875 && (obj1.getCsid().equals(obj1.getCsid()))
876 && ((item.getPredicate().equals(item2.getPredicate()))
877 && (item.getRelationshipType().equals(item2.getRelationshipType())))
878 && (obj1.getDocumentType().equals(obj2.getDocumentType()))
879 && (subj1.getDocumentType().equals(subj2.getDocumentType()));
882 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
886 /* don't even THINK of re-using this method.
887 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
889 private String extractInAuthorityCSID(String uri) {
890 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
891 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
892 Matcher m = p.matcher(uri);
894 if (m.groupCount() < 3) {
895 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
898 //String service = m.group(1);
899 String inauth = m.group(2);
900 //String theRest = m.group(3);
902 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
905 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
910 //ensures CSPACE-4042
911 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
912 String authorityCSID = extractInAuthorityCSID(thisURI);
913 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
914 if (Tools.isBlank(authorityCSID)
915 || Tools.isBlank(authorityCSIDForInbound)
916 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
917 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
921 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
922 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
923 ServiceContext ctx = getServiceContext();
924 MultivaluedMap queryParams = ctx.getQueryParams();
925 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
926 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
927 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
929 RelationResource relationResource = new RelationResource();
930 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
931 return relationsCommonList;
933 //============================= END TODO refactor ==========================