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.ResourceBase;
33 import org.collectionspace.services.common.ServiceMessages;
34 import org.collectionspace.services.common.api.CommonAPI;
35 import org.collectionspace.services.common.api.RefName;
36 import org.collectionspace.services.common.api.Tools;
37 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
38 import org.collectionspace.services.common.context.MultipartServiceContext;
39 import org.collectionspace.services.common.context.ServiceBindingUtils;
40 import org.collectionspace.services.common.context.ServiceContext;
41 import org.collectionspace.services.common.document.DocumentException;
42 import org.collectionspace.services.common.document.DocumentFilter;
43 import org.collectionspace.services.common.document.DocumentWrapper;
44 import org.collectionspace.services.common.document.DocumentWrapperImpl;
45 import org.collectionspace.services.common.relation.IRelationsManager;
46 import org.collectionspace.services.common.repository.RepositoryClient;
47 import org.collectionspace.services.common.repository.RepositoryClientFactory;
48 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
49 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
50 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
51 import org.collectionspace.services.config.service.ListResultField;
52 import org.collectionspace.services.config.service.ObjectPartType;
53 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
54 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
55 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
56 import org.collectionspace.services.relation.RelationResource;
57 import org.collectionspace.services.relation.RelationsCommon;
58 import org.collectionspace.services.relation.RelationsCommonList;
59 import org.collectionspace.services.relation.RelationsDocListItem;
60 import org.collectionspace.services.relation.RelationshipType;
61 import org.nuxeo.ecm.core.api.ClientException;
62 import org.nuxeo.ecm.core.api.DocumentModel;
63 import org.nuxeo.ecm.core.api.model.PropertyException;
64 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
65 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
66 import org.nuxeo.runtime.transaction.TransactionHelper;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
70 import javax.ws.rs.PathParam;
71 import javax.ws.rs.WebApplicationException;
72 import javax.ws.rs.core.Context;
73 import javax.ws.rs.core.MultivaluedMap;
74 import javax.ws.rs.core.Response;
75 import javax.ws.rs.core.UriInfo;
76 import java.util.ArrayList;
77 import java.util.List;
79 import java.util.regex.Matcher;
80 import java.util.regex.Pattern;
82 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
84 * AuthorityItemDocumentModelHandler
86 * $LastChangedRevision: $
89 public abstract class AuthorityItemDocumentModelHandler<AICommon>
90 extends DocHandlerBase<AICommon> {
92 private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
93 private String authorityItemCommonSchemaName;
95 * inVocabulary is the parent Authority for this context
97 protected String inAuthority = null;
98 protected String authorityRefNameBase = null;
99 // Used to determine when the displayName changes as part of the update.
100 protected String oldDisplayNameOnUpdate = null;
101 protected String oldRefNameOnUpdate = null;
102 protected String newRefNameOnUpdate = null;
104 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
105 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
108 public void setInAuthority(String inAuthority) {
109 this.inAuthority = inAuthority;
112 /** Subclasses may override this to customize the URI segment. */
113 public String getAuthorityServicePath() {
114 return getServiceContext().getServiceName().toLowerCase(); // Laramie20110510 CSPACE-3932
118 public String getUri(DocumentModel docModel) {
119 // Laramie20110510 CSPACE-3932
120 String authorityServicePath = getAuthorityServicePath();
121 if(inAuthority==null) { // Only happens on queries to wildcarded authorities
123 inAuthority = (String) docModel.getProperty(authorityItemCommonSchemaName,
124 AuthorityItemJAXBSchema.IN_AUTHORITY);
125 } catch (ClientException pe) {
126 throw new RuntimeException("Could not get parent specifier for item!");
129 return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
132 protected String getAuthorityRefNameBase() {
133 return this.authorityRefNameBase;
136 public void setAuthorityRefNameBase(String value) {
137 this.authorityRefNameBase = value;
141 public List<ListResultField> getListItemsArray() throws DocumentException {
142 List<ListResultField> list = super.getListItemsArray();
143 int nFields = list.size();
144 // Ensure some common fields so do not depend upon config for general logic
145 boolean hasDisplayName = false;
146 boolean hasShortId = false;
147 boolean hasRefName = false;
148 boolean hasTermStatus = false;
149 for (int i = 0; i < nFields; i++) {
150 ListResultField field = list.get(i);
151 String elName = field.getElement();
152 if (AuthorityItemJAXBSchema.DISPLAY_NAME.equals(elName)) {
153 hasDisplayName = true;
154 } else if (AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName)) {
156 } else if (AuthorityItemJAXBSchema.REF_NAME.equals(elName)) {
158 } else if (AuthorityItemJAXBSchema.TERM_STATUS.equals(elName)) {
159 hasTermStatus = true;
162 ListResultField field;
163 if (!hasDisplayName) {
164 field = new ListResultField();
165 field.setElement(AuthorityItemJAXBSchema.DISPLAY_NAME);
166 field.setXpath(AuthorityItemJAXBSchema.DISPLAY_NAME);
170 field = new ListResultField();
171 field.setElement(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
172 field.setXpath(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
176 field = new ListResultField();
177 field.setElement(AuthorityItemJAXBSchema.REF_NAME);
178 field.setXpath(AuthorityItemJAXBSchema.REF_NAME);
181 if (!hasTermStatus) {
182 field = new ListResultField();
183 field.setElement(AuthorityItemJAXBSchema.TERM_STATUS);
184 field.setXpath(AuthorityItemJAXBSchema.TERM_STATUS);
193 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
196 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
197 // first fill all the parts of the document
198 super.handleCreate(wrapDoc);
199 // Ensure we have required fields set properly
200 handleInAuthority(wrapDoc.getWrappedObject());
202 handleComputedDisplayNames(wrapDoc.getWrappedObject());
203 String displayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
204 AuthorityItemJAXBSchema.DISPLAY_NAME);
205 if (Tools.isEmpty(displayName)) {
206 logger.warn("Creating Authority Item with no displayName!");
209 handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
210 // refName includes displayName, so we force a correct value here.
211 updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
215 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
218 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
219 // First, get a copy of the old displayName
220 oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
221 AuthorityItemJAXBSchema.DISPLAY_NAME);
222 oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
223 AuthorityItemJAXBSchema.REF_NAME);
224 super.handleUpdate(wrapDoc);
225 handleComputedDisplayNames(wrapDoc.getWrappedObject());
226 String newDisplayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
227 AuthorityItemJAXBSchema.DISPLAY_NAME);
228 if (newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
229 // Need to update the refName, and then fix all references.
230 newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
232 // Mark as not needing attention in completeUpdate phase.
233 newRefNameOnUpdate = null;
234 oldRefNameOnUpdate = null;
239 * Handle display name.
241 * @param docModel the doc model
242 * @throws Exception the exception
244 protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
245 // Do nothing by default.
249 * Handle refName updates for changes to display name.
250 * Assumes refName is already correct. Just ensures it is right.
252 * @param docModel the doc model
253 * @param newDisplayName the new display name
254 * @throws Exception the exception
256 protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
257 String newDisplayName) throws Exception {
258 RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
259 if (authItem == null) {
260 String err = "Authority Item has illegal refName: " + oldRefNameOnUpdate;
262 throw new IllegalArgumentException(err);
264 authItem.displayName = newDisplayName;
265 String updatedRefName = authItem.toString();
266 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
267 return updatedRefName;
270 protected String getRefPropName() {
271 return ServiceBindingUtils.AUTH_REF_PROP;
275 * Checks to see if the refName has changed, and if so,
276 * uses utilities to find all references and update them.
279 protected void handleItemRefNameReferenceUpdate() throws Exception {
280 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
281 // We have work to do.
282 if (logger.isDebugEnabled()) {
283 String eol = System.getProperty("line.separator");
284 logger.debug("Need to find and update references to Item." + eol
285 + " Old refName" + oldRefNameOnUpdate + eol
286 + " New refName" + newRefNameOnUpdate);
288 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
289 RepositoryClient repoClient = getRepositoryClient(ctx);
290 String refNameProp = getRefPropName();
292 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
293 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
294 if (logger.isDebugEnabled()) {
295 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
301 * If no short identifier was provided in the input payload,
302 * generate a short identifier from the display name.
304 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
305 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
306 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
307 String shortDisplayName = "";
309 shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
310 } catch (PropertyNotFoundException pnfe) {
311 // Do nothing on exception. Some vocabulary schemas may not include a short display name.
313 if (Tools.isEmpty(shortIdentifier)) {
314 String generatedShortIdentifier =
315 AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
316 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
321 * Generate a refName for the authority item from the short identifier
324 * All refNames for authority items are generated. If a client supplies
325 * a refName, it will be overwritten during create (per this method)
326 * or discarded during update (per filterReadOnlyPropertiesForPart).
328 * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
331 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
333 String authorityRefBaseName) throws Exception {
334 DocumentModel docModel = wrapDoc.getWrappedObject();
335 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
336 String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
337 if (Tools.isEmpty(authorityRefBaseName)) {
338 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
340 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
341 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
342 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
346 * Check the logic around the parent pointer. Note that we only need do this on
347 * create, since we have logic to make this read-only on update.
351 * @throws Exception the exception
353 private void handleInAuthority(DocumentModel docModel) throws Exception {
354 if(inAuthority==null) { // Only happens on queries to wildcarded authorities
355 throw new IllegalStateException("Trying to Create an object with no inAuthority value!");
357 docModel.setProperty(authorityItemCommonSchemaName,
358 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
362 public AuthorityRefDocList getReferencingObjects(
363 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
364 List<String> serviceTypes,
366 String itemcsid) throws Exception {
367 AuthorityRefDocList authRefDocList = null;
368 RepositoryInstance repoSession = null;
369 boolean releaseRepoSession = false;
372 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
373 repoSession = this.getRepositorySession();
374 if (repoSession == null) {
375 repoSession = repoClient.getRepositorySession();
376 releaseRepoSession = true;
378 DocumentFilter myFilter = getDocumentFilter();
381 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, itemcsid);
382 DocumentModel docModel = wrapper.getWrappedObject();
383 String refName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
384 authRefDocList = RefNameServiceUtils.getAuthorityRefDocs(
385 repoSession, ctx, repoClient,
389 myFilter.getPageSize(), myFilter.getStartPage(), true /*computeTotal*/);
390 } catch (PropertyException pe) {
392 } catch (DocumentException de) {
394 } catch (Exception e) {
395 if (logger.isDebugEnabled()) {
396 logger.debug("Caught exception ", e);
398 throw new DocumentException(e);
400 if (releaseRepoSession && repoSession != null) {
401 repoClient.releaseRepositorySession(repoSession);
404 } catch (Exception e) {
405 if (logger.isDebugEnabled()) {
406 logger.debug("Caught exception ", e);
408 throw new DocumentException(e);
410 return authRefDocList;
417 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
420 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
422 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
424 // Add the CSID to the common part, since they may have fetched via the shortId.
425 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
426 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
427 unQObjectProperties.put("csid", csid);
430 return unQObjectProperties;
434 * Filters out selected values supplied in an update request.
436 * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
437 * that the link to the item's parent remains untouched.
439 * @param objectProps the properties filtered out from the update payload
440 * @param partMeta metadata for the object to fill
443 public void filterReadOnlyPropertiesForPart(
444 Map<String, Object> objectProps, ObjectPartType partMeta) {
445 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
446 String commonPartLabel = getServiceContext().getCommonPartLabel();
447 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
448 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
449 objectProps.remove(AuthorityItemJAXBSchema.CSID);
450 objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
451 objectProps.remove(AuthorityItemJAXBSchema.REF_NAME);
456 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
457 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
458 super.extractAllParts(wrapDoc);
460 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
461 if (Tools.isTrue(showSiblings)) {
462 showSiblings(wrapDoc, ctx);
463 return; // actual result is returned on ctx.addOutputPart();
466 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
467 if (Tools.isTrue(showRelations)) {
468 showRelations(wrapDoc, ctx);
469 return; // actual result is returned on ctx.addOutputPart();
472 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
473 if (Tools.isTrue(showAllRelations)) {
474 showAllRelations(wrapDoc, ctx);
475 return; // actual result is returned on ctx.addOutputPart();
479 /** @return null on parent not found
481 protected String getParentCSID(String thisCSID) throws Exception {
482 String parentCSID = null;
484 String predicate = RelationshipType.HAS_BROADER.value();
485 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
486 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
487 if (parentList != null) {
488 if (parentList.size() == 0) {
491 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
492 parentCSID = relationListItem.getObjectCsid();
495 } catch (Exception e) {
496 logger.error("Could not find parent for this: " + thisCSID, e);
501 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
502 MultipartServiceContext ctx) throws Exception {
503 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
505 String predicate = RelationshipType.HAS_BROADER.value();
506 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
507 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
509 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
510 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
512 if(logger.isTraceEnabled()) {
513 String dump = dumpLists(thisCSID, parentList, childrenList, null);
514 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
517 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
518 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
519 //Not optimal, but that's the current design spec.
521 for (RelationsCommonList.RelationListItem parent : parentList) {
522 childrenList.add(parent);
525 long childrenSize = childrenList.size();
526 childrenListOuter.setTotalItems(childrenSize);
527 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
529 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
530 ctx.addOutputPart(relationsPart);
533 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
534 MultipartServiceContext ctx) throws Exception {
535 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
536 String parentCSID = getParentCSID(thisCSID);
537 if (parentCSID == null) {
538 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
542 String predicate = RelationshipType.HAS_BROADER.value();
543 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
544 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
546 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
549 RelationsCommonList.RelationListItem item = null;
550 for (RelationsCommonList.RelationListItem sibling : siblingList) {
551 if (thisCSID.equals(sibling.getSubjectCsid())) {
552 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.
555 //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.
556 for (RelationsCommonList.RelationListItem self : toRemoveList) {
557 removeFromList(siblingList, self);
560 long siblingSize = siblingList.size();
561 siblingListOuter.setTotalItems(siblingSize);
562 siblingListOuter.setItemsInPage(siblingSize);
563 if(logger.isTraceEnabled()) {
564 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
565 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
568 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
569 ctx.addOutputPart(relationsPart);
572 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
573 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
575 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
576 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
578 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
579 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
581 if(logger.isTraceEnabled()) {
582 String dump = dumpLists(thisCSID, subjectList, objectList, null);
583 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
586 subjectList.addAll(objectList);
588 //now subjectList actually has records BOTH where thisCSID is subject and object.
589 long relatedSize = subjectList.size();
590 subjectListOuter.setTotalItems(relatedSize);
591 subjectListOuter.setItemsInPage(relatedSize);
593 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
594 ctx.addOutputPart(relationsPart);
597 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
598 super.fillAllParts(wrapDoc, action);
600 ServiceContext ctx = getServiceContext();
601 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
602 DocumentModel documentModel = (wrapDoc.getWrappedObject());
603 String itemCsid = documentModel.getName();
605 //UPDATE and CREATE will call. Updates relations part
606 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
608 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
609 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
613 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
614 super.completeCreate(wrapDoc);
615 handleRelationsPayload(wrapDoc, false);
618 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
619 super.completeUpdate(wrapDoc);
620 handleRelationsPayload(wrapDoc, true);
621 handleItemRefNameReferenceUpdate();
624 // Note that we must do this after we have completed the Update, so that the repository has the
625 // info for the item itself. The relations code must call into the repo to get info for each end.
626 // This could be optimized to pass in the parent docModel, since it will often be one end.
627 // Nevertheless, we should complete the item save before we do work on the relations, especially
628 // since a save on Create might fail, and we would not want to create relations for something
629 // that may not be created...
630 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
631 ServiceContext ctx = getServiceContext();
632 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
633 DocumentModel documentModel = (wrapDoc.getWrappedObject());
634 String itemCsid = documentModel.getName();
636 //Updates relations part
637 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
639 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
640 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
642 //now we add part for relations list
643 //ServiceContext ctx = getServiceContext();
644 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
645 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
648 /** updateRelations strategy:
650 go through inboundList, remove anything from childList that matches from childList
651 go through inboundList, remove anything from parentList that matches from parentList
652 go through parentList, delete all remaining
653 go through childList, delete all remaining
654 go through actionList, add all remaining.
655 check for duplicate children
656 check for more than one parent.
658 inboundList parentList childList actionList
659 ---------------- --------------- ---------------- ----------------
660 child-a parent-c child-a child-b
661 child-b parent-d child-c
664 private RelationsCommonList updateRelations(
665 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
667 if (logger.isTraceEnabled()) {
668 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
670 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
672 return null; //nothing to do--they didn't send a list of relations.
674 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
675 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
676 List<RelationsCommonList.RelationListItem> actionList = newList();
677 List<RelationsCommonList.RelationListItem> childList = null;
678 List<RelationsCommonList.RelationListItem> parentList = null;
679 DocumentModel docModel = wrapDoc.getWrappedObject();
680 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
682 ServiceContext ctx = getServiceContext();
683 //Do magic replacement of ${itemCSID} and fix URI's.
684 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
686 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
687 UriInfo uriInfo = ctx.getUriInfo();
688 MultivaluedMap queryParams = uriInfo.getQueryParameters();
691 //Run getList() once as sent to get childListOuter:
692 String predicate = RelationshipType.HAS_BROADER.value();
693 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
694 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
695 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
696 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
697 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
698 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
700 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
701 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
702 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
703 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
704 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
707 childList = childListOuter.getRelationListItem();
708 parentList = parentListOuter.getRelationListItem();
710 if (parentList.size() > 1) {
711 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
714 if (logger.isTraceEnabled()) {
715 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
720 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
721 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
722 // and so the CSID for those may be null
723 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
724 // Look for parents and children
725 if(itemCSID.equals(inboundItem.getObject().getCsid())
726 || itemRefName.equals(inboundItem.getObject().getRefName())) {
727 //then this is an item that says we have a child. That child is inboundItem
728 RelationsCommonList.RelationListItem childItem =
729 (childList == null) ? null : findInList(childList, inboundItem);
730 if (childItem != null) {
731 if (logger.isTraceEnabled()) {
732 StringBuilder sb = new StringBuilder();
733 itemToString(sb, "== Child: ", childItem);
734 logger.trace("Found inboundChild in current child list: " + sb.toString());
736 removeFromList(childList, childItem); //exists, just take it off delete list
738 if (logger.isTraceEnabled()) {
739 StringBuilder sb = new StringBuilder();
740 itemToString(sb, "== Child: ", inboundItem);
741 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
743 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
744 String newChildCsid = inboundItem.getSubject().getCsid();
745 if(newChildCsid == null) {
746 String newChildRefName = inboundItem.getSubject().getRefName();
747 if(newChildRefName==null) {
748 throw new RuntimeException("Child with no CSID or refName!");
750 if (logger.isTraceEnabled()) {
751 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
753 DocumentModel newChildDocModel =
754 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
755 newChildRefName, getServiceContext().getResourceMap());
756 newChildCsid = getCsid(newChildDocModel);
758 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
761 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
762 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
763 //then this is an item that says we have a parent. inboundItem is that parent.
764 RelationsCommonList.RelationListItem parentItem =
765 (parentList == null) ? null : findInList(parentList, inboundItem);
766 if (parentItem != null) {
767 removeFromList(parentList, parentItem); //exists, just take it off delete list
769 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
772 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
775 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
778 if (logger.isTraceEnabled()) {
779 String dump = dumpLists(itemCSID, parentList, childList, actionList);
780 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
783 if (logger.isTraceEnabled()) {
784 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
785 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
787 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
788 deleteRelations(childList, ctx, "childList");
790 if (logger.isTraceEnabled()) {
791 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
792 + actionList.size() + " new parents and children.");
794 createRelations(actionList, ctx);
795 if (logger.isTraceEnabled()) {
796 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
798 //We return all elements on the inbound list, since we have just worked to make them exist in the system
799 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
800 return relationsCommonListBody;
803 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
804 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
805 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
806 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
807 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
808 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
809 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
810 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
811 deleteRelations(parentList, ctx, "parentList-delete");
815 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
817 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
819 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
821 sb.append(item.getPredicate());
823 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
827 private String dumpLists(String itemCSID,
828 List<RelationsCommonList.RelationListItem> parentList,
829 List<RelationsCommonList.RelationListItem> childList,
830 List<RelationsCommonList.RelationListItem> actionList) {
831 StringBuilder sb = new StringBuilder();
832 sb.append("itemCSID: " + itemCSID + CR);
833 if(parentList!=null) {
834 sb.append(dumpList(parentList, "parentList"));
836 if(childList!=null) {
837 sb.append(dumpList(childList, "childList"));
839 if(actionList!=null) {
840 sb.append(dumpList(actionList, "actionList"));
842 return sb.toString();
844 private final static String CR = "\r\n";
845 private final static String T = " ";
847 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
848 StringBuilder sb = new StringBuilder();
850 if (list.size() > 0) {
851 sb.append("=========== " + label + " ==========" + CR);
853 for (RelationsCommonList.RelationListItem item : list) {
854 itemToString(sb, "== ", item);
857 return sb.toString();
860 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
861 * and sets URI correctly for related items.
862 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
864 protected void fixupInboundListItems(ServiceContext ctx,
865 List<RelationsCommonList.RelationListItem> inboundList,
866 DocumentModel docModel,
867 String itemCSID) throws Exception {
868 String thisURI = this.getUri(docModel);
869 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
870 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
871 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
872 RelationsDocListItem inboundItemObject = inboundItem.getObject();
873 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
875 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
876 inboundItem.setObjectCsid(itemCSID);
877 inboundItemObject.setCsid(itemCSID);
878 //inboundItemObject.setUri(getUri(docModel));
881 String objectCsid = inboundItemObject.getCsid();
882 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
883 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
884 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
885 inboundItemObject.setUri(uri); //CSPACE-4037
888 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
890 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
891 inboundItem.setSubjectCsid(itemCSID);
892 inboundItemSubject.setCsid(itemCSID);
893 //inboundItemSubject.setUri(getUri(docModel));
896 String subjectCsid = inboundItemSubject.getCsid();
897 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
898 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
899 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
900 inboundItemSubject.setUri(uri); //CSPACE-4037
903 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
908 // this method calls the RelationResource to have it create the relations and persist them.
909 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
910 for (RelationsCommonList.RelationListItem item : inboundList) {
911 RelationsCommon rc = new RelationsCommon();
912 //rc.setCsid(item.getCsid());
913 //todo: assignTo(item, rc);
914 RelationsDocListItem itemSubject = item.getSubject();
915 RelationsDocListItem itemObject = item.getObject();
917 // Set at least one of CSID and refName for Subject and Object
918 // Either value might be null for for each of Subject and Object
919 String subjectCsid = itemSubject.getCsid();
920 rc.setSubjectCsid(subjectCsid);
922 String objCsid = itemObject.getCsid();
923 rc.setObjectCsid(objCsid);
925 rc.setSubjectRefName(itemSubject.getRefName());
926 rc.setObjectRefName(itemObject.getRefName());
928 rc.setRelationshipType(item.getPredicate());
929 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
930 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
932 // This is superfluous, since it will be fetched by the Relations Create logic.
933 rc.setSubjectDocumentType(itemSubject.getDocumentType());
934 rc.setObjectDocumentType(itemObject.getDocumentType());
936 // This is superfluous, since it will be fetched by the Relations Create logic.
937 rc.setSubjectUri(itemSubject.getUri());
938 rc.setObjectUri(itemObject.getUri());
939 // May not have the info here. Only really require CSID or refName.
940 // Rest is handled in the Relation create mechanism
941 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
943 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
944 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
945 payloadOut.addPart(outputPart);
946 RelationResource relationResource = new RelationResource();
947 Object res = relationResource.create(ctx.getResourceMap(),
948 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
952 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
954 for (RelationsCommonList.RelationListItem item : list) {
955 RelationResource relationResource = new RelationResource();
956 if(logger.isTraceEnabled()) {
957 StringBuilder sb = new StringBuilder();
958 itemToString(sb, "==== TO DELETE: ", item);
959 logger.trace(sb.toString());
961 Object res = relationResource.delete(item.getCsid());
963 } catch (Throwable t) {
964 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
969 private List<RelationsCommonList.RelationListItem> newList() {
970 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
974 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
975 List<RelationsCommonList.RelationListItem> result = newList();
976 for (RelationsCommonList.RelationListItem item : inboundList) {
982 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
983 // But the list items must not be sparse
984 private RelationsCommonList.RelationListItem findInList(
985 List<RelationsCommonList.RelationListItem> list,
986 RelationsCommonList.RelationListItem item) {
987 RelationsCommonList.RelationListItem foundItem = null;
988 for (RelationsCommonList.RelationListItem listItem : list) {
989 if (itemsEqual(listItem, item)) { //equals must be defined, else
990 foundItem = listItem;
997 // Note that item2 may be sparse (only refName, no CSID for subject or object)
998 // But item1 must not be sparse
999 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1000 if (item1 == null || item2 == null) {
1003 RelationsDocListItem subj1 = item1.getSubject();
1004 RelationsDocListItem subj2 = item2.getSubject();
1005 RelationsDocListItem obj1 = item1.getObject();
1006 RelationsDocListItem obj2 = item2.getObject();
1007 String subj1Csid = subj1.getCsid();
1008 String subj2Csid = subj2.getCsid();
1009 String subj1RefName = subj1.getRefName();
1010 String subj2RefName = subj2.getRefName();
1012 String obj1Csid = obj1.getCsid();
1013 String obj2Csid = obj2.getCsid();
1014 String obj1RefName = obj1.getRefName();
1015 String obj2RefName = obj2.getRefName();
1018 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1019 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1020 // predicate is proper, but still allow relationshipType
1021 && (item1.getPredicate().equals(item2.getPredicate())
1022 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1023 // Allow missing docTypes, so long as they do not conflict
1024 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1025 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1029 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1033 /* don't even THINK of re-using this method.
1034 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
1036 private String extractInAuthorityCSID(String uri) {
1037 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
1038 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
1039 Matcher m = p.matcher(uri);
1041 if (m.groupCount() < 3) {
1042 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
1045 //String service = m.group(1);
1046 String inauth = m.group(2);
1047 //String theRest = m.group(3);
1049 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
1052 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
1057 //ensures CSPACE-4042
1058 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
1059 String authorityCSID = extractInAuthorityCSID(thisURI);
1060 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
1061 if (Tools.isBlank(authorityCSID)
1062 || Tools.isBlank(authorityCSIDForInbound)
1063 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
1064 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
1068 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1069 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1070 ServiceContext ctx = getServiceContext();
1071 MultivaluedMap queryParams = ctx.getQueryParams();
1072 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1073 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1074 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1076 RelationResource relationResource = new RelationResource();
1077 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
1078 return relationsCommonList;
1080 //============================= END TODO refactor ==========================