2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.common.vocabulary.nuxeo;
26 import org.collectionspace.services.client.AuthorityClient;
27 import org.collectionspace.services.client.PayloadInputPart;
28 import org.collectionspace.services.client.PayloadOutputPart;
29 import org.collectionspace.services.client.PoxPayloadIn;
30 import org.collectionspace.services.client.PoxPayloadOut;
31 import org.collectionspace.services.client.RelationClient;
32 import org.collectionspace.services.common.api.CommonAPI;
33 import org.collectionspace.services.common.api.RefName;
34 import org.collectionspace.services.common.api.Tools;
35 import org.collectionspace.services.common.context.MultipartServiceContext;
36 import org.collectionspace.services.common.context.ServiceContext;
37 import org.collectionspace.services.common.document.DocumentWrapper;
38 import org.collectionspace.services.common.document.DocumentWrapperImpl;
39 import org.collectionspace.services.common.relation.IRelationsManager;
40 import org.collectionspace.services.common.repository.RepositoryClient;
41 import org.collectionspace.services.common.repository.RepositoryClientFactory;
42 import org.collectionspace.services.common.service.ObjectPartType;
43 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
44 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
45 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
46 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
47 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
48 import org.collectionspace.services.relation.RelationResource;
49 import org.collectionspace.services.relation.RelationsCommon;
50 import org.collectionspace.services.relation.RelationsCommonList;
51 import org.collectionspace.services.relation.RelationsDocListItem;
52 import org.collectionspace.services.relation.RelationshipType;
53 import org.nuxeo.ecm.core.api.DocumentModel;
54 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
58 import javax.ws.rs.core.MultivaluedMap;
59 import javax.ws.rs.core.UriInfo;
60 import java.util.ArrayList;
61 import java.util.List;
63 import java.util.regex.Matcher;
64 import java.util.regex.Pattern;
66 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
68 * AuthorityItemDocumentModelHandler
70 * $LastChangedRevision: $
73 public abstract class AuthorityItemDocumentModelHandler<AICommon>
74 extends DocHandlerBase<AICommon> {
76 private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
77 private String authorityItemCommonSchemaName;
79 * inVocabulary is the parent Authority for this context
81 protected String inAuthority;
82 protected String authorityRefNameBase;
84 // Used to determine when the displayName changes as part of the update.
85 protected String oldDisplayNameOnUpdate = null;
86 protected String oldRefNameOnUpdate = null;
87 protected String newRefNameOnUpdate = null;
89 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
90 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
93 public String getInAuthority() {
97 public void setInAuthority(String inAuthority) {
98 this.inAuthority = inAuthority;
101 /** Subclasses may override this to customize the URI segment. */
102 public String getAuthorityServicePath() {
103 return getServiceContext().getServiceName().toLowerCase(); // Laramie20110510 CSPACE-3932
107 public String getUri(DocumentModel docModel) {
108 // Laramie20110510 CSPACE-3932
109 String authorityServicePath = getAuthorityServicePath();
110 return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
113 public String getAuthorityRefNameBase() {
114 return this.authorityRefNameBase;
117 public void setAuthorityRefNameBase(String value) {
118 this.authorityRefNameBase = value;
122 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
125 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
126 // first fill all the parts of the document
127 super.handleCreate(wrapDoc);
128 handleInAuthority(wrapDoc.getWrappedObject());
129 handleComputedDisplayNames(wrapDoc.getWrappedObject());
130 String displayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
131 AuthorityItemJAXBSchema.DISPLAY_NAME);
132 if(Tools.isEmpty(displayName)) {
133 logger.warn("Creating Authority Item with no displayName!");
136 // Uncomment once debugged and App layer is read to integrate
137 // Experimenting with these uncommented now ...
138 handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
139 updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
143 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
146 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
147 // First, get a copy of the old displayName
148 oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
149 AuthorityItemJAXBSchema.DISPLAY_NAME);
150 oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
151 AuthorityItemJAXBSchema.REF_NAME);
152 super.handleUpdate(wrapDoc);
153 handleComputedDisplayNames(wrapDoc.getWrappedObject());
154 String newDisplayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
155 AuthorityItemJAXBSchema.DISPLAY_NAME);
156 if(newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
157 // Need to update the refName, and then fix all references.
158 newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
160 // Mark as not needing attention in completeUpdate phase.
161 newRefNameOnUpdate = null;
162 oldRefNameOnUpdate = null;
167 * Handle display name.
169 * @param docModel the doc model
170 * @throws Exception the exception
172 protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
173 // Do nothing by default.
177 * Handle refName updates for changes to display name.
178 * Assumes refName is already correct. Just ensures it is right.
180 * @param docModel the doc model
181 * @throws Exception the exception
183 protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
184 String newDisplayName) throws Exception {
185 //String suppliedRefName = (String) docModel.getProperty(authorityItemCommonSchemaName,
186 // AuthorityItemJAXBSchema.REF_NAME);
187 RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
188 if(authItem == null) {
189 String err = "Authority Item has illegal refName: "+oldRefNameOnUpdate;
191 throw new IllegalArgumentException(err);
193 authItem.displayName = newDisplayName;
194 String updatedRefName = authItem.toString();
195 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
196 return updatedRefName;
201 * Checks to see if the refName has changed, and if so,
202 * uses utilities to find all references and update them.
204 protected void handleItemRefNameReferenceUpdate() {
205 if(newRefNameOnUpdate != null && oldRefNameOnUpdate!= null) {
206 // We have work to do.
207 logger.debug("Need to find and update references to Item.");
208 logger.debug("Old refName" + oldRefNameOnUpdate);
209 logger.debug("New refName" + newRefNameOnUpdate);
214 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
215 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
216 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
217 String shortDisplayName = "";
219 shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
220 } catch (PropertyNotFoundException pnfe) {
221 // Do nothing on exception. Some vocabulary schemas may not include a short display name.
223 if (Tools.isEmpty(shortIdentifier)) {
224 String generatedShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
225 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
229 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
231 String authorityRefBaseName) throws Exception {
232 DocumentModel docModel = wrapDoc.getWrappedObject();
233 String suppliedRefName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME);
235 // Temporarily accept client-supplied refName values, rather than always generating such values.
236 // Remove first block and the surrounding 'if' statement when clients should no longer supply refName values.
237 if(!Tools.isEmpty(suppliedRefName) ) {
238 // Supplied refName must at least be legal
239 RefName.AuthorityItem item = RefName.AuthorityItem.parse(suppliedRefName);
241 logger.error("Passed refName for authority item not legal: "+suppliedRefName);
242 suppliedRefName = null; // Clear this and compute a new one below.
245 // Recheck, in case we cleared it for being illegal
246 if(Tools.isEmpty(suppliedRefName) ) {
247 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
248 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
249 if (Tools.isEmpty(authorityRefBaseName)) {
250 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
252 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
253 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
254 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
259 * Check the logic around the parent pointer. Note that we only need do this on
260 * create, since we have logic to make this read-only on update.
264 * @throws Exception the exception
266 private void handleInAuthority(DocumentModel docModel) throws Exception {
267 docModel.setProperty(authorityItemCommonSchemaName,
268 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
273 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
276 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
278 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
280 // Add the CSID to the common part
281 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
282 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
283 unQObjectProperties.put("csid", csid);
286 return unQObjectProperties;
290 * Filters out selected values supplied in an update request.
292 * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
293 * that the link to the item's parent remains untouched.
295 * @param objectProps the properties filtered out from the update payload
296 * @param partMeta metadata for the object to fill
299 public void filterReadOnlyPropertiesForPart(
300 Map<String, Object> objectProps, ObjectPartType partMeta) {
301 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
302 String commonPartLabel = getServiceContext().getCommonPartLabel();
303 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
304 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
305 objectProps.remove(AuthorityItemJAXBSchema.CSID);
306 objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
307 // Enable when clients should no longer supply refName values
308 // objectProps.remove(AuthorityItemJAXBSchema.REF_NAME); // CSPACE-3178
314 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
315 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
316 super.extractAllParts(wrapDoc);
318 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
319 if (Tools.isTrue(showSiblings)) {
320 showSiblings(wrapDoc, ctx);
321 return; // actual result is returned on ctx.addOutputPart();
324 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
325 if (Tools.isTrue(showRelations)) {
326 showRelations(wrapDoc, ctx);
327 return; // actual result is returned on ctx.addOutputPart();
330 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
331 if (Tools.isTrue(showAllRelations)) {
332 showAllRelations(wrapDoc, ctx);
333 return; // actual result is returned on ctx.addOutputPart();
337 /** @return null on parent not found
339 protected String getParentCSID(String thisCSID) throws Exception {
340 String parentCSID = null;
342 String predicate = RelationshipType.HAS_BROADER.value();
343 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
344 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
345 if (parentList != null) {
346 if (parentList.size() == 0) {
349 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
350 parentCSID = relationListItem.getObjectCsid();
353 } catch (Exception e) {
354 logger.error("Could not find parent for this: " + thisCSID, e);
359 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
360 MultipartServiceContext ctx) throws Exception {
361 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
363 String predicate = RelationshipType.HAS_BROADER.value();
364 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
365 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
367 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
368 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
370 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
371 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
372 //Not optimal, but that's the current design spec.
374 for (RelationsCommonList.RelationListItem parent : parentList) {
375 childrenList.add(parent);
378 long childrenSize = childrenList.size();
379 childrenListOuter.setTotalItems(childrenSize);
380 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
382 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
383 ctx.addOutputPart(relationsPart);
386 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
387 MultipartServiceContext ctx) throws Exception {
388 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
389 String parentCSID = getParentCSID(thisCSID);
390 if (parentCSID == null) {
391 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
395 String predicate = RelationshipType.HAS_BROADER.value();
396 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
397 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
399 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
402 RelationsCommonList.RelationListItem item = null;
403 for (RelationsCommonList.RelationListItem sibling : siblingList) {
404 if (thisCSID.equals(sibling.getSubjectCsid())) {
405 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.
408 //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.
409 for (RelationsCommonList.RelationListItem self : toRemoveList) {
410 removeFromList(siblingList, self);
413 long siblingSize = siblingList.size();
414 siblingListOuter.setTotalItems(siblingSize);
415 siblingListOuter.setItemsInPage(siblingSize);
417 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
418 ctx.addOutputPart(relationsPart);
421 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
422 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
424 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
425 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
427 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
428 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
431 subjectList.addAll(objectList);
433 //now subjectList actually has records BOTH where thisCSID is subject and object.
434 long relatedSize = subjectList.size();
435 subjectListOuter.setTotalItems(relatedSize);
436 subjectListOuter.setItemsInPage(relatedSize);
438 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
439 ctx.addOutputPart(relationsPart);
442 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
443 super.fillAllParts(wrapDoc, action);
444 ServiceContext ctx = getServiceContext();
445 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
446 DocumentModel documentModel = (wrapDoc.getWrappedObject());
447 String itemCsid = documentModel.getName();
449 //UPDATE and CREATE will call. Updates relations part
450 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
452 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
453 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
456 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
457 super.completeUpdate(wrapDoc);
458 //now we add part for relations list
459 ServiceContext ctx = getServiceContext();
460 PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
461 ((PoxPayloadOut) ctx.getOutput()).addPart(foo);
462 handleItemRefNameReferenceUpdate();
465 /** updateRelations strategy:
467 go through inboundList, remove anything from childList that matches from childList
468 go through inboundList, remove anything from parentList that matches from parentList
469 go through parentList, delete all remaining
470 go through childList, delete all remaining
471 go through actionList, add all remaining.
472 check for duplicate children
473 check for more than one parent.
475 inboundList parentList childList actionList
476 ---------------- --------------- ---------------- ----------------
477 child-a parent-c child-a child-b
478 child-b parent-d child-c
481 public RelationsCommonList updateRelations(String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc)
483 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
485 return null; //nothing to do--they didn't send a list of relations.
487 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
489 ServiceContext ctx = getServiceContext();
490 UriInfo uriInfo = ctx.getUriInfo();
491 MultivaluedMap queryParams = uriInfo.getQueryParameters();
493 //Run getList() once as sent to get childListOuter:
494 String predicate = RelationshipType.HAS_BROADER.value();
495 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
496 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
497 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
498 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
499 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
500 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
502 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
503 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
504 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
505 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
506 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
508 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
510 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
511 List<RelationsCommonList.RelationListItem> actionList = newList();
512 List<RelationsCommonList.RelationListItem> childList = childListOuter.getRelationListItem();
513 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
515 if (parentList.size() > 1) {
516 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
519 DocumentModel docModel = wrapDoc.getWrappedObject();
521 //Do magic replacement of ${itemCSID} and fix URI's.
522 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
524 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
525 if (inboundItem.getObject().getCsid().equals(itemCSID) && inboundItem.getPredicate().equals(HAS_BROADER)) {
526 //then this is an item that says we have a child. That child is inboundItem
527 RelationsCommonList.RelationListItem childItem = findInList(childList, inboundItem);
528 if (childItem != null) {
529 removeFromList(childList, childItem); //exists, just take it off delete list
531 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
533 ensureChildHasNoOtherParents(ctx, queryParams, inboundItem.getSubject().getCsid());
535 } else if (inboundItem.getSubject().getCsid().equals(itemCSID) && inboundItem.getPredicate().equals(HAS_BROADER)) {
536 //then this is an item that says we have a parent. inboundItem is that parent.
537 RelationsCommonList.RelationListItem parentItem = findInList(parentList, inboundItem);
538 if (parentItem != null) {
539 removeFromList(parentList, parentItem); //exists, just take it off delete list
541 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
544 logger.warn("Element didn't match parent or child, but may have partial fields that match. inboundItem: " + inboundItem);
545 //not dealing with: hasNarrower or any other predicate.
548 String dump = dumpLists(itemCSID, parentList, childList, actionList);
549 //System.out.println("====dump====="+CR+dump);
550 logger.info("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
551 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
552 deleteRelations(childList, ctx, "childList");
553 createRelations(actionList, ctx);
554 //We return all elements on the inbound list, since we have just worked to make them exist in the system
555 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
556 return relationsCommonListBody;
559 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
560 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
561 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
562 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
563 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
564 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
565 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
566 deleteRelations(parentList, ctx, "parentList-delete");
569 private String dumpLists(String itemCSID,
570 List<RelationsCommonList.RelationListItem> parentList,
571 List<RelationsCommonList.RelationListItem> childList,
572 List<RelationsCommonList.RelationListItem> actionList) {
573 StringBuffer sb = new StringBuffer();
574 sb.append("itemCSID: " + itemCSID + CR);
575 sb.append(dumpList(parentList, "parentList"));
576 sb.append(dumpList(childList, "childList"));
577 sb.append(dumpList(actionList, "actionList"));
578 return sb.toString();
580 private final static String CR = "\r\n";
581 private final static String T = " ";
583 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
584 StringBuffer sb = new StringBuffer();
586 if (list.size() > 0) {
587 sb.append("=========== " + label + " ==========" + CR);
589 for (RelationsCommonList.RelationListItem item : list) {
591 T + item.getSubject().getCsid() //+T4 + item.getSubject().getUri()
592 + T + item.getPredicate()
593 + T + item.getObject().getCsid() //+T4 + item.getObject().getUri()
594 + CR //+"subject:{"+item.getSubject()+"}\r\n object:{"+item.getObject()+"}"
595 //+ CR + "relation-record: {"+item+"}"
600 return sb.toString();
603 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
604 * and sets URI correctly for related items.
605 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
607 protected void fixupInboundListItems(ServiceContext ctx,
608 List<RelationsCommonList.RelationListItem> inboundList,
609 DocumentModel docModel,
610 String itemCSID) throws Exception {
611 String thisURI = this.getUri(docModel);
612 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
613 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
614 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
615 RelationsDocListItem inboundItemObject = inboundItem.getObject();
616 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
618 if (inboundItemObject.getCsid().equalsIgnoreCase(CommonAPI.AuthorityItemCSID_REPLACE)) {
619 inboundItem.setObjectCsid(itemCSID);
620 inboundItemObject.setCsid(itemCSID);
621 inboundItemObject.setUri(getUri(docModel));
623 String objectCsid = inboundItemObject.getCsid();
624 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
625 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
626 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
627 inboundItemObject.setUri(uri); //CSPACE-4037
629 uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
631 if (inboundItemSubject.getCsid().equalsIgnoreCase(CommonAPI.AuthorityItemCSID_REPLACE)) {
632 inboundItem.setSubjectCsid(itemCSID);
633 inboundItemSubject.setCsid(itemCSID);
634 inboundItemSubject.setUri(getUri(docModel));
636 String subjectCsid = inboundItemSubject.getCsid();
637 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
638 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
639 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
640 inboundItemSubject.setUri(uri); //CSPACE-4037
642 uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
647 public RepositoryClient getRepositoryClient(ServiceContext ctx) {
648 RepositoryClient repositoryClient = RepositoryClientFactory.getInstance().getClient(ctx.getRepositoryClientName());
649 return repositoryClient;
652 // this method calls the RelationResource to have it create the relations and persist them.
653 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) {
654 for (RelationsCommonList.RelationListItem item : inboundList) {
655 RelationsCommon rc = new RelationsCommon();
656 //rc.setCsid(item.getCsid());
657 //todo: assignTo(item, rc);
658 RelationsDocListItem itemSubject = item.getSubject();
659 RelationsDocListItem itemObject = item.getObject();
661 String subjectCsid = itemSubject.getCsid();
662 rc.setDocumentId1(subjectCsid);
663 rc.setSubjectCsid(subjectCsid);
665 String objCsid = item.getObject().getCsid();
666 rc.setDocumentId2(objCsid);
667 rc.setObjectCsid(objCsid);
669 rc.setRelationshipType(item.getPredicate());
670 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
671 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
673 rc.setDocumentType1(itemSubject.getDocumentType());
674 rc.setDocumentType2(itemObject.getDocumentType());
676 rc.setSubjectUri(itemSubject.getUri());
677 rc.setObjectUri(itemObject.getUri());
680 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
681 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
682 payloadOut.addPart(outputPart);
683 //System.out.println("\r\n==== TO CREATE: "+rc.getDocumentId1()+"==>"+rc.getPredicate()+"==>"+rc.getDocumentId2());
684 RelationResource relationResource = new RelationResource();
685 Object res = relationResource.create(ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
689 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
691 //if (list.size()>0){ logger.info("==== deleteRelations from : "+listName); }
692 for (RelationsCommonList.RelationListItem item : list) {
693 RelationResource relationResource = new RelationResource();
694 //logger.info("==== TO DELETE: " + item.getCsid() + ": " + item.getSubject().getCsid() + "--" + item.getPredicate() + "-->" + item.getObject().getCsid());
695 Object res = relationResource.delete(item.getCsid());
697 } catch (Throwable t) {
698 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
703 private List<RelationsCommonList.RelationListItem> newList() {
704 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
708 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
709 List<RelationsCommonList.RelationListItem> result = newList();
710 for (RelationsCommonList.RelationListItem item : inboundList) {
716 private RelationsCommonList.RelationListItem findInList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
717 for (RelationsCommonList.RelationListItem listItem : list) {
718 if (itemsEqual(listItem, item)) { //equals must be defined, else
725 private boolean itemsEqual(RelationsCommonList.RelationListItem item, RelationsCommonList.RelationListItem item2) {
726 if (item == null || item2 == null) {
729 RelationsDocListItem subj1 = item.getSubject();
730 RelationsDocListItem subj2 = item2.getSubject();
731 RelationsDocListItem obj1 = item.getObject();
732 RelationsDocListItem obj2 = item2.getObject();
734 return (subj1.getCsid().equals(subj2.getCsid()))
735 && (obj1.getCsid().equals(obj1.getCsid()))
736 && ((item.getPredicate().equals(item2.getPredicate()))
737 && (item.getRelationshipType().equals(item2.getRelationshipType())))
738 && (obj1.getDocumentType().equals(obj2.getDocumentType()))
739 && (subj1.getDocumentType().equals(subj2.getDocumentType()));
742 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
746 /* don't even THINK of re-using this method.
747 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
749 private String extractInAuthorityCSID(String uri) {
750 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
751 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
752 Matcher m = p.matcher(uri);
754 if (m.groupCount() < 3) {
755 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
758 //String service = m.group(1);
759 String inauth = m.group(2);
760 //String theRest = m.group(3);
762 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
765 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
770 //ensures CSPACE-4042
771 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
772 String authorityCSID = extractInAuthorityCSID(thisURI);
773 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
774 if (Tools.isBlank(authorityCSID)
775 || Tools.isBlank(authorityCSIDForInbound)
776 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
777 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
781 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
782 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
783 ServiceContext ctx = getServiceContext();
784 MultivaluedMap queryParams = ctx.getQueryParams();
785 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
786 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
787 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
789 RelationResource relationResource = new RelationResource();
790 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
791 return relationsCommonList;
793 //============================= END TODO refactor ==========================