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.RemoteDocumentModelHandlerImpl;
55 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
56 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
57 import org.collectionspace.services.relation.RelationResource;
58 import org.collectionspace.services.relation.RelationsCommon;
59 import org.collectionspace.services.relation.RelationsCommonList;
60 import org.collectionspace.services.relation.RelationsDocListItem;
61 import org.collectionspace.services.relation.RelationshipType;
62 import org.nuxeo.ecm.core.api.ClientException;
63 import org.nuxeo.ecm.core.api.DocumentModel;
64 import org.nuxeo.ecm.core.api.model.PropertyException;
65 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
66 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
67 import org.nuxeo.runtime.transaction.TransactionHelper;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
71 import javax.ws.rs.PathParam;
72 import javax.ws.rs.WebApplicationException;
73 import javax.ws.rs.core.Context;
74 import javax.ws.rs.core.MultivaluedMap;
75 import javax.ws.rs.core.Response;
76 import javax.ws.rs.core.UriInfo;
77 import java.util.ArrayList;
78 import java.util.List;
80 import java.util.regex.Matcher;
81 import java.util.regex.Pattern;
82 import org.collectionspace.services.nuxeo.client.java.DocumentModelHandler;
84 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
86 * AuthorityItemDocumentModelHandler
88 * $LastChangedRevision: $
91 public abstract class AuthorityItemDocumentModelHandler<AICommon>
92 extends DocHandlerBase<AICommon> {
94 private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
95 private String authorityItemCommonSchemaName;
97 * inVocabulary is the parent Authority for this context
99 protected String inAuthority = null;
100 protected String authorityRefNameBase = null;
101 // Used to determine when the displayName changes as part of the update.
102 protected String oldDisplayNameOnUpdate = null;
103 protected String oldRefNameOnUpdate = null;
104 protected String newRefNameOnUpdate = null;
106 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
107 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
110 public void setInAuthority(String inAuthority) {
111 this.inAuthority = inAuthority;
114 /** Subclasses may override this to customize the URI segment. */
115 public String getAuthorityServicePath() {
116 return getServiceContext().getServiceName().toLowerCase(); // Laramie20110510 CSPACE-3932
120 public String getUri(DocumentModel docModel) {
121 // Laramie20110510 CSPACE-3932
122 String authorityServicePath = getAuthorityServicePath();
123 if(inAuthority==null) { // Only happens on queries to wildcarded authorities
125 inAuthority = (String) docModel.getProperty(authorityItemCommonSchemaName,
126 AuthorityItemJAXBSchema.IN_AUTHORITY);
127 } catch (ClientException pe) {
128 throw new RuntimeException("Could not get parent specifier for item!");
131 return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
134 protected String getAuthorityRefNameBase() {
135 return this.authorityRefNameBase;
138 public void setAuthorityRefNameBase(String value) {
139 this.authorityRefNameBase = value;
143 public List<ListResultField> getListItemsArray() throws DocumentException {
144 List<ListResultField> list = super.getListItemsArray();
145 int nFields = list.size();
146 // Ensure some common fields so do not depend upon config for general logic
147 boolean hasDisplayName = false;
148 boolean hasShortId = false;
149 boolean hasRefName = false;
150 boolean hasTermStatus = false;
151 for (int i = 0; i < nFields; i++) {
152 ListResultField field = list.get(i);
153 String elName = field.getElement();
154 if (AuthorityItemJAXBSchema.DISPLAY_NAME.equals(elName)) {
155 hasDisplayName = true;
156 } else if (AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName)) {
158 } else if (AuthorityItemJAXBSchema.REF_NAME.equals(elName)) {
160 } else if (AuthorityItemJAXBSchema.TERM_STATUS.equals(elName)) {
161 hasTermStatus = true;
164 ListResultField field;
165 if (!hasDisplayName) {
166 field = new ListResultField();
167 field.setElement(AuthorityItemJAXBSchema.DISPLAY_NAME);
168 field.setXpath(AuthorityItemJAXBSchema.DISPLAY_NAME);
172 field = new ListResultField();
173 field.setElement(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
174 field.setXpath(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
178 field = new ListResultField();
179 field.setElement(AuthorityItemJAXBSchema.REF_NAME);
180 field.setXpath(AuthorityItemJAXBSchema.REF_NAME);
183 if (!hasTermStatus) {
184 field = new ListResultField();
185 field.setElement(AuthorityItemJAXBSchema.TERM_STATUS);
186 field.setXpath(AuthorityItemJAXBSchema.TERM_STATUS);
195 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
198 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
199 // first fill all the parts of the document
200 super.handleCreate(wrapDoc);
201 // Ensure we have required fields set properly
202 handleInAuthority(wrapDoc.getWrappedObject());
206 handleComputedDisplayNames(wrapDoc.getWrappedObject());
207 String displayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
208 AuthorityItemJAXBSchema.DISPLAY_NAME);
209 if (Tools.isEmpty(displayName)) {
210 logger.warn("Creating Authority Item with no displayName!");
215 // handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
216 // refName includes displayName, so we force a correct value here.
217 updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
221 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
224 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
225 // First, get a copy of the old displayName
226 // oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
227 // AuthorityItemJAXBSchema.DISPLAY_NAME);
228 oldDisplayNameOnUpdate = (String) RemoteDocumentModelHandlerImpl.getXPathStringValue(wrapDoc.getWrappedObject(),
229 authorityItemCommonSchemaName, AuthorityItemJAXBSchema.PREFERRED_TERM_DISPLAY_NAME_XPATH);
230 oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
231 AuthorityItemJAXBSchema.REF_NAME);
232 super.handleUpdate(wrapDoc);
233 // handleComputedDisplayNames(wrapDoc.getWrappedObject());
234 // String newDisplayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
235 // AuthorityItemJAXBSchema.DISPLAY_NAME);
236 String newDisplayName = (String) RemoteDocumentModelHandlerImpl.getXPathStringValue(wrapDoc.getWrappedObject(),
237 authorityItemCommonSchemaName, AuthorityItemJAXBSchema.PREFERRED_TERM_DISPLAY_NAME_XPATH);
238 if (newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
239 // Need to update the refName, and then fix all references.
240 newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
242 // Mark as not needing attention in completeUpdate phase.
243 newRefNameOnUpdate = null;
244 oldRefNameOnUpdate = null;
249 * Handle display name.
251 * @param docModel the doc model
252 * @throws Exception the exception
254 protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
255 // Do nothing by default.
259 * Handle refName updates for changes to display name.
260 * Assumes refName is already correct. Just ensures it is right.
262 * @param docModel the doc model
263 * @param newDisplayName the new display name
264 * @throws Exception the exception
266 protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
267 String newDisplayName) throws Exception {
268 RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
269 if (authItem == null) {
270 String err = "Authority Item has illegal refName: " + oldRefNameOnUpdate;
272 throw new IllegalArgumentException(err);
274 authItem.displayName = newDisplayName;
275 String updatedRefName = authItem.toString();
276 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
277 return updatedRefName;
280 protected String getRefPropName() {
281 return ServiceBindingUtils.AUTH_REF_PROP;
285 * Checks to see if the refName has changed, and if so,
286 * uses utilities to find all references and update them.
289 protected void handleItemRefNameReferenceUpdate() throws Exception {
290 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
291 // We have work to do.
292 if (logger.isDebugEnabled()) {
293 String eol = System.getProperty("line.separator");
294 logger.debug("Need to find and update references to Item." + eol
295 + " Old refName" + oldRefNameOnUpdate + eol
296 + " New refName" + newRefNameOnUpdate);
298 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
299 RepositoryClient repoClient = getRepositoryClient(ctx);
300 String refNameProp = getRefPropName();
302 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
303 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
304 if (logger.isDebugEnabled()) {
305 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
311 * If no short identifier was provided in the input payload, generate a
312 * short identifier from the preferred term display name or term name.
314 private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
315 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
316 String termDisplayName =
317 (String) RemoteDocumentModelHandlerImpl.getXPathStringValue(docModel,
318 schemaName, AuthorityItemJAXBSchema.PREFERRED_TERM_DISPLAY_NAME_XPATH);
319 String termName = (String) RemoteDocumentModelHandlerImpl.getXPathStringValue(docModel,
320 schemaName, AuthorityItemJAXBSchema.PREFERRED_TERM_NAME_XPATH);
321 if (Tools.isEmpty(shortIdentifier)) {
322 String generatedShortIdentifier =
323 AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(termDisplayName, termName);
324 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
329 * Generate a refName for the authority item from the short identifier
332 * All refNames for authority items are generated. If a client supplies
333 * a refName, it will be overwritten during create (per this method)
334 * or discarded during update (per filterReadOnlyPropertiesForPart).
336 * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
339 protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
341 String authorityRefBaseName) throws Exception {
342 DocumentModel docModel = wrapDoc.getWrappedObject();
343 String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
345 RemoteDocumentModelHandlerImpl.getXPathStringValue(docModel, schemaName,
346 AuthorityItemJAXBSchema.PREFERRED_TERM_DISPLAY_NAME_XPATH);
347 if (Tools.isEmpty(authorityRefBaseName)) {
348 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
350 RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
351 String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
352 docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
356 * Check the logic around the parent pointer. Note that we only need do this on
357 * create, since we have logic to make this read-only on update.
361 * @throws Exception the exception
363 private void handleInAuthority(DocumentModel docModel) throws Exception {
364 if(inAuthority==null) { // Only happens on queries to wildcarded authorities
365 throw new IllegalStateException("Trying to Create an object with no inAuthority value!");
367 docModel.setProperty(authorityItemCommonSchemaName,
368 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
372 public AuthorityRefDocList getReferencingObjects(
373 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
374 List<String> serviceTypes,
376 String itemcsid) throws Exception {
377 AuthorityRefDocList authRefDocList = null;
378 RepositoryInstance repoSession = null;
379 boolean releaseRepoSession = false;
382 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
383 repoSession = this.getRepositorySession();
384 if (repoSession == null) {
385 repoSession = repoClient.getRepositorySession();
386 releaseRepoSession = true;
388 DocumentFilter myFilter = getDocumentFilter();
391 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, itemcsid);
392 DocumentModel docModel = wrapper.getWrappedObject();
393 String refName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
394 authRefDocList = RefNameServiceUtils.getAuthorityRefDocs(
395 repoSession, ctx, repoClient,
399 myFilter.getPageSize(), myFilter.getStartPage(), true /*computeTotal*/);
400 } catch (PropertyException pe) {
402 } catch (DocumentException de) {
404 } catch (Exception e) {
405 if (logger.isDebugEnabled()) {
406 logger.debug("Caught exception ", e);
408 throw new DocumentException(e);
410 if (releaseRepoSession && repoSession != null) {
411 repoClient.releaseRepositorySession(repoSession);
414 } catch (Exception e) {
415 if (logger.isDebugEnabled()) {
416 logger.debug("Caught exception ", e);
418 throw new DocumentException(e);
420 return authRefDocList;
427 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
430 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
432 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
434 // Add the CSID to the common part, since they may have fetched via the shortId.
435 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
436 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
437 unQObjectProperties.put("csid", csid);
440 return unQObjectProperties;
444 * Filters out selected values supplied in an update request.
446 * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
447 * that the link to the item's parent remains untouched.
449 * @param objectProps the properties filtered out from the update payload
450 * @param partMeta metadata for the object to fill
453 public void filterReadOnlyPropertiesForPart(
454 Map<String, Object> objectProps, ObjectPartType partMeta) {
455 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
456 String commonPartLabel = getServiceContext().getCommonPartLabel();
457 if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
458 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
459 objectProps.remove(AuthorityItemJAXBSchema.CSID);
460 objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
461 objectProps.remove(AuthorityItemJAXBSchema.REF_NAME);
466 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
467 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
468 super.extractAllParts(wrapDoc);
470 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
471 if (Tools.isTrue(showSiblings)) {
472 showSiblings(wrapDoc, ctx);
473 return; // actual result is returned on ctx.addOutputPart();
476 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
477 if (Tools.isTrue(showRelations)) {
478 showRelations(wrapDoc, ctx);
479 return; // actual result is returned on ctx.addOutputPart();
482 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
483 if (Tools.isTrue(showAllRelations)) {
484 showAllRelations(wrapDoc, ctx);
485 return; // actual result is returned on ctx.addOutputPart();
489 /** @return null on parent not found
491 protected String getParentCSID(String thisCSID) throws Exception {
492 String parentCSID = null;
494 String predicate = RelationshipType.HAS_BROADER.value();
495 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
496 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
497 if (parentList != null) {
498 if (parentList.size() == 0) {
501 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
502 parentCSID = relationListItem.getObjectCsid();
505 } catch (Exception e) {
506 logger.error("Could not find parent for this: " + thisCSID, e);
511 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
512 MultipartServiceContext ctx) throws Exception {
513 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
515 String predicate = RelationshipType.HAS_BROADER.value();
516 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
517 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
519 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
520 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
522 if(logger.isTraceEnabled()) {
523 String dump = dumpLists(thisCSID, parentList, childrenList, null);
524 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
527 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
528 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
529 //Not optimal, but that's the current design spec.
531 for (RelationsCommonList.RelationListItem parent : parentList) {
532 childrenList.add(parent);
535 long childrenSize = childrenList.size();
536 childrenListOuter.setTotalItems(childrenSize);
537 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
539 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
540 ctx.addOutputPart(relationsPart);
543 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
544 MultipartServiceContext ctx) throws Exception {
545 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
546 String parentCSID = getParentCSID(thisCSID);
547 if (parentCSID == null) {
548 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
552 String predicate = RelationshipType.HAS_BROADER.value();
553 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
554 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
556 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
559 RelationsCommonList.RelationListItem item = null;
560 for (RelationsCommonList.RelationListItem sibling : siblingList) {
561 if (thisCSID.equals(sibling.getSubjectCsid())) {
562 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.
565 //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.
566 for (RelationsCommonList.RelationListItem self : toRemoveList) {
567 removeFromList(siblingList, self);
570 long siblingSize = siblingList.size();
571 siblingListOuter.setTotalItems(siblingSize);
572 siblingListOuter.setItemsInPage(siblingSize);
573 if(logger.isTraceEnabled()) {
574 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
575 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
578 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
579 ctx.addOutputPart(relationsPart);
582 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
583 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
585 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
586 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
588 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
589 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
591 if(logger.isTraceEnabled()) {
592 String dump = dumpLists(thisCSID, subjectList, objectList, null);
593 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
596 subjectList.addAll(objectList);
598 //now subjectList actually has records BOTH where thisCSID is subject and object.
599 long relatedSize = subjectList.size();
600 subjectListOuter.setTotalItems(relatedSize);
601 subjectListOuter.setItemsInPage(relatedSize);
603 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
604 ctx.addOutputPart(relationsPart);
607 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
608 super.fillAllParts(wrapDoc, action);
610 ServiceContext ctx = getServiceContext();
611 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
612 DocumentModel documentModel = (wrapDoc.getWrappedObject());
613 String itemCsid = documentModel.getName();
615 //UPDATE and CREATE will call. Updates relations part
616 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
618 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
619 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
623 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
624 super.completeCreate(wrapDoc);
625 handleRelationsPayload(wrapDoc, false);
628 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
629 super.completeUpdate(wrapDoc);
630 handleRelationsPayload(wrapDoc, true);
631 handleItemRefNameReferenceUpdate();
634 // Note that we must do this after we have completed the Update, so that the repository has the
635 // info for the item itself. The relations code must call into the repo to get info for each end.
636 // This could be optimized to pass in the parent docModel, since it will often be one end.
637 // Nevertheless, we should complete the item save before we do work on the relations, especially
638 // since a save on Create might fail, and we would not want to create relations for something
639 // that may not be created...
640 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
641 ServiceContext ctx = getServiceContext();
642 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
643 DocumentModel documentModel = (wrapDoc.getWrappedObject());
644 String itemCsid = documentModel.getName();
646 //Updates relations part
647 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
649 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
650 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
652 //now we add part for relations list
653 //ServiceContext ctx = getServiceContext();
654 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
655 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
658 /** updateRelations strategy:
660 go through inboundList, remove anything from childList that matches from childList
661 go through inboundList, remove anything from parentList that matches from parentList
662 go through parentList, delete all remaining
663 go through childList, delete all remaining
664 go through actionList, add all remaining.
665 check for duplicate children
666 check for more than one parent.
668 inboundList parentList childList actionList
669 ---------------- --------------- ---------------- ----------------
670 child-a parent-c child-a child-b
671 child-b parent-d child-c
674 private RelationsCommonList updateRelations(
675 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
677 if (logger.isTraceEnabled()) {
678 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
680 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
682 return null; //nothing to do--they didn't send a list of relations.
684 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
685 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
686 List<RelationsCommonList.RelationListItem> actionList = newList();
687 List<RelationsCommonList.RelationListItem> childList = null;
688 List<RelationsCommonList.RelationListItem> parentList = null;
689 DocumentModel docModel = wrapDoc.getWrappedObject();
690 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
692 ServiceContext ctx = getServiceContext();
693 //Do magic replacement of ${itemCSID} and fix URI's.
694 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
696 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
697 UriInfo uriInfo = ctx.getUriInfo();
698 MultivaluedMap queryParams = uriInfo.getQueryParameters();
701 //Run getList() once as sent to get childListOuter:
702 String predicate = RelationshipType.HAS_BROADER.value();
703 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
704 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
705 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
706 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
707 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
708 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
710 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
711 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
712 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
713 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
714 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
717 childList = childListOuter.getRelationListItem();
718 parentList = parentListOuter.getRelationListItem();
720 if (parentList.size() > 1) {
721 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
724 if (logger.isTraceEnabled()) {
725 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
730 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
731 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
732 // and so the CSID for those may be null
733 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
734 // Look for parents and children
735 if(itemCSID.equals(inboundItem.getObject().getCsid())
736 || itemRefName.equals(inboundItem.getObject().getRefName())) {
737 //then this is an item that says we have a child. That child is inboundItem
738 RelationsCommonList.RelationListItem childItem =
739 (childList == null) ? null : findInList(childList, inboundItem);
740 if (childItem != null) {
741 if (logger.isTraceEnabled()) {
742 StringBuilder sb = new StringBuilder();
743 itemToString(sb, "== Child: ", childItem);
744 logger.trace("Found inboundChild in current child list: " + sb.toString());
746 removeFromList(childList, childItem); //exists, just take it off delete list
748 if (logger.isTraceEnabled()) {
749 StringBuilder sb = new StringBuilder();
750 itemToString(sb, "== Child: ", inboundItem);
751 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
753 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
754 String newChildCsid = inboundItem.getSubject().getCsid();
755 if(newChildCsid == null) {
756 String newChildRefName = inboundItem.getSubject().getRefName();
757 if(newChildRefName==null) {
758 throw new RuntimeException("Child with no CSID or refName!");
760 if (logger.isTraceEnabled()) {
761 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
763 DocumentModel newChildDocModel =
764 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
765 newChildRefName, getServiceContext().getResourceMap());
766 newChildCsid = getCsid(newChildDocModel);
768 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
771 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
772 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
773 //then this is an item that says we have a parent. inboundItem is that parent.
774 RelationsCommonList.RelationListItem parentItem =
775 (parentList == null) ? null : findInList(parentList, inboundItem);
776 if (parentItem != null) {
777 removeFromList(parentList, parentItem); //exists, just take it off delete list
779 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
782 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
785 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
788 if (logger.isTraceEnabled()) {
789 String dump = dumpLists(itemCSID, parentList, childList, actionList);
790 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
793 if (logger.isTraceEnabled()) {
794 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
795 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
797 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
798 deleteRelations(childList, ctx, "childList");
800 if (logger.isTraceEnabled()) {
801 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
802 + actionList.size() + " new parents and children.");
804 createRelations(actionList, ctx);
805 if (logger.isTraceEnabled()) {
806 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
808 //We return all elements on the inbound list, since we have just worked to make them exist in the system
809 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
810 return relationsCommonListBody;
813 private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
814 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
815 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
816 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
817 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
818 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
819 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
820 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
821 deleteRelations(parentList, ctx, "parentList-delete");
825 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
827 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
829 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
831 sb.append(item.getPredicate());
833 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
837 private String dumpLists(String itemCSID,
838 List<RelationsCommonList.RelationListItem> parentList,
839 List<RelationsCommonList.RelationListItem> childList,
840 List<RelationsCommonList.RelationListItem> actionList) {
841 StringBuilder sb = new StringBuilder();
842 sb.append("itemCSID: " + itemCSID + CR);
843 if(parentList!=null) {
844 sb.append(dumpList(parentList, "parentList"));
846 if(childList!=null) {
847 sb.append(dumpList(childList, "childList"));
849 if(actionList!=null) {
850 sb.append(dumpList(actionList, "actionList"));
852 return sb.toString();
854 private final static String CR = "\r\n";
855 private final static String T = " ";
857 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
858 StringBuilder sb = new StringBuilder();
860 if (list.size() > 0) {
861 sb.append("=========== " + label + " ==========" + CR);
863 for (RelationsCommonList.RelationListItem item : list) {
864 itemToString(sb, "== ", item);
867 return sb.toString();
870 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
871 * and sets URI correctly for related items.
872 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
874 protected void fixupInboundListItems(ServiceContext ctx,
875 List<RelationsCommonList.RelationListItem> inboundList,
876 DocumentModel docModel,
877 String itemCSID) throws Exception {
878 String thisURI = this.getUri(docModel);
879 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
880 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
881 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
882 RelationsDocListItem inboundItemObject = inboundItem.getObject();
883 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
885 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
886 inboundItem.setObjectCsid(itemCSID);
887 inboundItemObject.setCsid(itemCSID);
888 //inboundItemObject.setUri(getUri(docModel));
891 String objectCsid = inboundItemObject.getCsid();
892 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
893 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
894 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
895 inboundItemObject.setUri(uri); //CSPACE-4037
898 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
900 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
901 inboundItem.setSubjectCsid(itemCSID);
902 inboundItemSubject.setCsid(itemCSID);
903 //inboundItemSubject.setUri(getUri(docModel));
906 String subjectCsid = inboundItemSubject.getCsid();
907 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
908 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
909 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
910 inboundItemSubject.setUri(uri); //CSPACE-4037
913 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
918 // this method calls the RelationResource to have it create the relations and persist them.
919 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
920 for (RelationsCommonList.RelationListItem item : inboundList) {
921 RelationsCommon rc = new RelationsCommon();
922 //rc.setCsid(item.getCsid());
923 //todo: assignTo(item, rc);
924 RelationsDocListItem itemSubject = item.getSubject();
925 RelationsDocListItem itemObject = item.getObject();
927 // Set at least one of CSID and refName for Subject and Object
928 // Either value might be null for for each of Subject and Object
929 String subjectCsid = itemSubject.getCsid();
930 rc.setSubjectCsid(subjectCsid);
932 String objCsid = itemObject.getCsid();
933 rc.setObjectCsid(objCsid);
935 rc.setSubjectRefName(itemSubject.getRefName());
936 rc.setObjectRefName(itemObject.getRefName());
938 rc.setRelationshipType(item.getPredicate());
939 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
940 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
942 // This is superfluous, since it will be fetched by the Relations Create logic.
943 rc.setSubjectDocumentType(itemSubject.getDocumentType());
944 rc.setObjectDocumentType(itemObject.getDocumentType());
946 // This is superfluous, since it will be fetched by the Relations Create logic.
947 rc.setSubjectUri(itemSubject.getUri());
948 rc.setObjectUri(itemObject.getUri());
949 // May not have the info here. Only really require CSID or refName.
950 // Rest is handled in the Relation create mechanism
951 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
953 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
954 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
955 payloadOut.addPart(outputPart);
956 RelationResource relationResource = new RelationResource();
957 Object res = relationResource.create(ctx.getResourceMap(),
958 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
962 private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
964 for (RelationsCommonList.RelationListItem item : list) {
965 RelationResource relationResource = new RelationResource();
966 if(logger.isTraceEnabled()) {
967 StringBuilder sb = new StringBuilder();
968 itemToString(sb, "==== TO DELETE: ", item);
969 logger.trace(sb.toString());
971 Object res = relationResource.delete(item.getCsid());
973 } catch (Throwable t) {
974 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
979 private List<RelationsCommonList.RelationListItem> newList() {
980 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
984 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
985 List<RelationsCommonList.RelationListItem> result = newList();
986 for (RelationsCommonList.RelationListItem item : inboundList) {
992 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
993 // But the list items must not be sparse
994 private RelationsCommonList.RelationListItem findInList(
995 List<RelationsCommonList.RelationListItem> list,
996 RelationsCommonList.RelationListItem item) {
997 RelationsCommonList.RelationListItem foundItem = null;
998 for (RelationsCommonList.RelationListItem listItem : list) {
999 if (itemsEqual(listItem, item)) { //equals must be defined, else
1000 foundItem = listItem;
1007 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1008 // But item1 must not be sparse
1009 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1010 if (item1 == null || item2 == null) {
1013 RelationsDocListItem subj1 = item1.getSubject();
1014 RelationsDocListItem subj2 = item2.getSubject();
1015 RelationsDocListItem obj1 = item1.getObject();
1016 RelationsDocListItem obj2 = item2.getObject();
1017 String subj1Csid = subj1.getCsid();
1018 String subj2Csid = subj2.getCsid();
1019 String subj1RefName = subj1.getRefName();
1020 String subj2RefName = subj2.getRefName();
1022 String obj1Csid = obj1.getCsid();
1023 String obj2Csid = obj2.getCsid();
1024 String obj1RefName = obj1.getRefName();
1025 String obj2RefName = obj2.getRefName();
1028 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1029 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1030 // predicate is proper, but still allow relationshipType
1031 && (item1.getPredicate().equals(item2.getPredicate())
1032 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1033 // Allow missing docTypes, so long as they do not conflict
1034 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1035 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1039 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1043 /* don't even THINK of re-using this method.
1044 * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
1046 private String extractInAuthorityCSID(String uri) {
1047 String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
1048 Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
1049 Matcher m = p.matcher(uri);
1051 if (m.groupCount() < 3) {
1052 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
1055 //String service = m.group(1);
1056 String inauth = m.group(2);
1057 //String theRest = m.group(3);
1059 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
1062 logger.warn("REGEX-NOT-MATCHED looking in " + uri);
1067 //ensures CSPACE-4042
1068 protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
1069 String authorityCSID = extractInAuthorityCSID(thisURI);
1070 String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
1071 if (Tools.isBlank(authorityCSID)
1072 || Tools.isBlank(authorityCSIDForInbound)
1073 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
1074 throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
1078 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1079 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1080 ServiceContext ctx = getServiceContext();
1081 MultivaluedMap queryParams = ctx.getQueryParams();
1082 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1083 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1084 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1086 RelationResource relationResource = new RelationResource();
1087 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
1088 return relationsCommonList;
1090 //============================= END TODO refactor ==========================