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.nuxeo.client.java;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
30 import java.util.Map.Entry;
33 import javax.ws.rs.core.MediaType;
34 import javax.ws.rs.core.MultivaluedMap;
35 import javax.ws.rs.core.Response;
36 import javax.ws.rs.core.UriInfo;
37 import javax.xml.bind.JAXBElement;
39 import org.collectionspace.authentication.spi.AuthNContext;
40 import org.collectionspace.services.authorization.AccountPermission;
41 import org.collectionspace.services.jaxb.AbstractCommonList;
42 import org.collectionspace.services.lifecycle.TransitionDef;
43 import org.collectionspace.services.client.AccountClient;
44 import org.collectionspace.services.client.CollectionSpaceClient;
45 import org.collectionspace.services.client.PayloadInputPart;
46 import org.collectionspace.services.client.PayloadOutputPart;
47 import org.collectionspace.services.client.PoxPayloadIn;
48 import org.collectionspace.services.client.PoxPayloadOut;
49 import org.collectionspace.services.client.Profiler;
50 import org.collectionspace.services.client.RelationClient;
51 import org.collectionspace.services.client.workflow.WorkflowClient;
52 import org.collectionspace.services.common.CSWebApplicationException;
53 import org.collectionspace.services.common.NuxeoBasedResource;
54 import org.collectionspace.services.common.authorityref.AuthorityRefList;
55 import org.collectionspace.services.common.config.ServiceConfigUtils;
56 import org.collectionspace.services.common.context.JaxRsContext;
57 import org.collectionspace.services.common.context.MultipartServiceContext;
58 import org.collectionspace.services.common.context.ServiceBindingUtils;
59 import org.collectionspace.services.common.context.ServiceContext;
60 import org.collectionspace.services.common.document.BadRequestException;
61 import org.collectionspace.services.common.document.DocumentException;
62 import org.collectionspace.services.common.document.DocumentUtils;
63 import org.collectionspace.services.common.document.DocumentWrapper;
64 import org.collectionspace.services.common.document.DocumentFilter;
65 import org.collectionspace.services.client.IRelationsManager;
66 import org.collectionspace.services.common.relation.RelationResource;
67 import org.collectionspace.services.common.repository.RepositoryClient;
68 import org.collectionspace.services.common.security.SecurityUtils;
69 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
70 import org.collectionspace.services.common.api.CommonAPI;
71 import org.collectionspace.services.common.api.RefNameUtils;
72 import org.collectionspace.services.common.api.Tools;
73 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
74 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
75 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
76 import org.collectionspace.services.config.service.DocHandlerParams;
77 import org.collectionspace.services.config.service.ListResultField;
78 import org.collectionspace.services.config.service.ObjectPartType;
79 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
80 import org.collectionspace.services.relation.RelationsCommon;
81 import org.collectionspace.services.relation.RelationsCommonList;
82 import org.collectionspace.services.relation.RelationsDocListItem;
83 import org.collectionspace.services.relation.RelationshipType;
84 import org.dom4j.Element;
85 import org.nuxeo.ecm.core.api.DocumentModel;
86 import org.nuxeo.ecm.core.api.DocumentModelList;
87 import org.nuxeo.ecm.core.api.DocumentNotFoundException;
88 import org.nuxeo.ecm.core.api.model.PropertyException;
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
93 * RemoteDocumentModelHandler
95 * $LastChangedRevision: $
100 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
101 extends DocumentModelHandler<T, TL> {
104 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
105 private final static String CR = "\r\n";
106 private final static String EMPTYSTR = "";
107 private static final String COLLECTIONSPACE_CORE_SCHEMA = CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA;
108 private static final String ACCOUNT_PERMISSION_COMMON_PART_NAME = AccountClient.SERVICE_COMMON_PART_NAME;
111 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
114 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
115 if (ctx instanceof MultipartServiceContext) {
116 super.setServiceContext(ctx);
118 throw new IllegalArgumentException("setServiceContext requires instance of "
119 + MultipartServiceContext.class.getName());
124 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
125 return getRefnameDisplayName(docWrapper.getWrappedObject());
128 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
129 String result = null;
130 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
132 DocHandlerParams.Params params = null;
134 params = ServiceConfigUtils.getDocHandlerParams(ctx);
135 ListResultField field = params.getRefnameDisplayNameField();
137 String schema = field.getSchema();
138 if (schema == null || schema.trim().isEmpty()) {
139 schema = ctx.getCommonPartLabel();
142 result = getStringValue(docModel, schema, field);
143 } catch (Exception e) {
144 if (logger.isWarnEnabled()) {
145 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
153 public boolean supportsHierarchy() {
154 boolean result = false;
156 DocHandlerParams.Params params = null;
158 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
159 params = ServiceConfigUtils.getDocHandlerParams(ctx);
160 Boolean bool = params.isSupportsHierarchy();
162 result = bool.booleanValue();
164 } catch (DocumentException e) {
165 // TODO Auto-generated catch block
166 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
167 if (logger.isWarnEnabled() == true) {
176 public boolean supportsVersioning() {
177 boolean result = false;
179 DocHandlerParams.Params params = null;
181 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
182 params = ServiceConfigUtils.getDocHandlerParams(ctx);
183 Boolean bool = params.isSupportsVersioning();
185 result = bool.booleanValue();
187 } catch (DocumentException e) {
188 // TODO Auto-generated catch block
189 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
190 if (logger.isWarnEnabled() == true) {
199 public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
201 // Do nothing by default, but children can override if they want. The real workflow transition happens in the WorkflowDocumentModelHandler class
205 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
206 super.completeCreate(wrapDoc);
207 if (supportsHierarchy() == true) {
208 handleRelationsPayload(wrapDoc, false);
212 /* NOTE: The authority item doc handler overrides (after calling) this method. It performs refName updates. In this
213 * method we just update any and all relationship records that use refNames that have changed.
215 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
218 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
219 DocumentModel docModel = wrapDoc.getWrappedObject();
221 String[] schemas = docModel.getDeclaredSchemas();
222 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
223 for (String schema : schemas) {
224 ObjectPartType partMeta = partsMetaMap.get(schema);
225 if (partMeta == null) {
226 continue; // unknown part, ignore
228 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
229 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
230 addExtraCoreValues(docModel, unQObjectProperties);
232 addOutputPart(unQObjectProperties, schema, partMeta);
236 // If the resource's service supports hierarchy then we need to perform a little more work
238 if (supportsHierarchy() == true) {
239 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
240 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
245 * Adds the output part.
247 * @param unQObjectProperties the un q object properties
248 * @param schema the schema
249 * @param partMeta the part meta
250 * @throws Exception the exception
251 * MediaType.APPLICATION_XML_TYPE
253 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
255 Element doc = DocumentUtils.buildDocument(partMeta, schema,
256 unQObjectProperties);
257 if (logger.isTraceEnabled() == true) {
258 logger.trace(doc.asXML());
260 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
261 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
265 * Extract paging info.
267 * @param commonsList the commons list
269 * @throws Exception the exception
271 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
273 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
275 DocumentFilter docFilter = this.getDocumentFilter();
276 long pageSize = docFilter.getPageSize();
277 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
278 // set the page size and page number
279 commonList.setPageNum(pageNum);
280 commonList.setPageSize(pageSize);
281 DocumentModelList docList = wrapDoc.getWrappedObject();
282 // Set num of items in list. this is useful to our testing framework.
283 commonList.setItemsInPage(docList.size());
284 // set the total result size
285 commonList.setTotalItems(docList.totalSize());
287 return (TL) commonList;
291 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
294 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
297 DocumentModel docModel = wrapDoc.getWrappedObject();
298 String[] schemas = docModel.getDeclaredSchemas();
299 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
300 for (String schema : schemas) {
301 ObjectPartType partMeta = partsMetaMap.get(schema);
302 if (partMeta == null) {
303 continue; // unknown part, ignore
305 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
306 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
307 addExtraCoreValues(docModel, unQObjectProperties);
309 addOutputPart(unQObjectProperties, schema, partMeta);
312 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
314 if (supportsHierarchy() == true) {
315 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
316 if (Tools.isTrue(showSiblings)) {
317 showSiblings(wrapDoc, ctx);
318 return; // actual result is returned on ctx.addOutputPart();
321 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
322 if (Tools.isTrue(showRelations)) {
323 showRelations(wrapDoc, ctx);
324 return; // actual result is returned on ctx.addOutputPart();
327 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
328 if (Tools.isTrue(showAllRelations)) {
329 showAllRelations(wrapDoc, ctx);
330 return; // actual result is returned on ctx.addOutputPart();
334 String currentUser = ctx.getUserId();
335 if (currentUser.equalsIgnoreCase(AuthNContext.ANONYMOUS_USER) == false) {
336 addAccountPermissionsPart();
340 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
342 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
345 private void addAccountPermissionsPart() throws Exception {
346 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
349 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
350 String currentServiceName = ctx.getServiceName();
351 String workflowSubResource = "/";
352 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
353 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
354 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
355 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
357 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
359 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
360 currentServiceName, workflowSubResource);
361 org.collectionspace.services.authorization.ObjectFactory objectFactory =
362 new org.collectionspace.services.authorization.ObjectFactory();
363 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
364 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
365 ctx.addOutputPart(accountPermissionPart);
371 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
374 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
376 //TODO filling extension parts should be dynamic
377 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
378 //not an ideal way of populating objects.
379 DocumentModel docModel = wrapDoc.getWrappedObject();
380 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
381 PoxPayloadIn input = ctx.getInput();
382 if (input == null || input.getParts().isEmpty()) {
383 String msg = String.format("No payload found for '%s' action.", action);
384 logger.error(msg + "Ctx=" + getServiceContext().toString());
385 throw new BadRequestException(msg);
388 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
390 //iterate over parts received and fill those parts
391 boolean werePartsFilled = false;
392 List<PayloadInputPart> inputParts = input.getParts();
393 for (PayloadInputPart part : inputParts) {
395 String partLabel = part.getLabel();
396 if (partLabel == null) {
397 String msg = "Part label is missing or empty!";
398 logger.error(msg + "Ctx=" + getServiceContext().toString());
399 throw new BadRequestException(msg);
402 //skip if the part is not in metadata or if it is a system part
403 ObjectPartType partMeta = partsMetaMap.get(partLabel);
404 if (partMeta == null || isSystemPart(partLabel)) {
407 fillPart(part, docModel, partMeta, action, ctx);
408 werePartsFilled = true;
411 if (logger.isTraceEnabled() && werePartsFilled == false) {
412 String msg = String.format("%s request had no XML payload parts processed in the request. Could be a payload with only relations-common-list request.",
418 private boolean isSystemPart(String partLabel) {
419 boolean result = false;
421 if (partLabel != null && (partLabel.equalsIgnoreCase(COLLECTIONSPACE_CORE_SCHEMA) ||
422 partLabel.equalsIgnoreCase(ACCOUNT_PERMISSION_COMMON_PART_NAME))) {
430 * fillPart fills an XML part into given document model
431 * @param part to fill
432 * @param docModel for the given object
433 * @param partMeta metadata for the object to fill
436 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
437 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
439 //check if this is an xml part
440 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
441 Element element = part.getElementBody();
442 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
443 if (action == Action.UPDATE) {
444 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
446 docModel.setProperties(partMeta.getLabel(), objectProps);
451 * Filters out read only properties, so they cannot be set on update.
452 * TODO: add configuration support to do this generally
453 * @param objectProps the properties parsed from the update payload
454 * @param partMeta metadata for the object to fill
456 public void filterReadOnlyPropertiesForPart(
457 Map<String, Object> objectProps, ObjectPartType partMeta) {
458 // Should add in logic to filter most of the core items on update
459 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
460 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
461 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
462 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
463 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
464 // Note that the updatedAt/updatedBy fields are set internally
465 // in DocumentModelHandler.handleCoreValues().
470 * extractPart extracts an XML object from given DocumentModel
472 * @param schema of the object to extract
473 * @param partMeta metadata for the object to extract
476 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
478 return extractPart(docModel, schema, (Map<String, Object>)null);
482 * extractPart extracts an XML object from given DocumentModel
484 * @param schema of the object to extract
485 * @param partMeta metadata for the object to extract
489 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
491 return extractPart(docModel, schema, partMeta, null);
495 * extractPart extracts an XML object from given DocumentModel
497 * @param schema of the object to extract
498 * @param partMeta metadata for the object to extract
501 protected Map<String, Object> extractPart(
502 DocumentModel docModel,
504 Map<String, Object> addToMap)
506 Map<String, Object> result = null;
508 Map<String, Object> objectProps = docModel.getProperties(schema);
509 if (objectProps != null) {
510 //unqualify properties before sending the doc over the wire (to save bandwidh)
511 //FIXME: is there a better way to avoid duplication of a Map/Collection?
512 Map<String, Object> unQObjectProperties =
513 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
514 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
515 for (Entry<String, Object> entry : qualifiedEntries) {
516 String unqProp = getUnQProperty(entry.getKey());
517 unQObjectProperties.put(unqProp, entry.getValue());
519 result = unQObjectProperties;
526 * extractPart extracts an XML object from given DocumentModel
528 * @param schema of the object to extract
529 * @param partMeta metadata for the object to extract
533 protected Map<String, Object> extractPart(
534 DocumentModel docModel, String schema, ObjectPartType partMeta,
535 Map<String, Object> addToMap)
537 Map<String, Object> result = null;
539 result = this.extractPart(docModel, schema, addToMap);
545 public String getStringPropertyFromDoc(
548 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
549 RepositoryInstance repoSession = null;
550 boolean releaseRepoSession = false;
551 String returnValue = null;
554 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
555 repoSession = this.getRepositorySession();
556 if (repoSession == null) {
557 repoSession = repoClient.getRepositorySession();
558 releaseRepoSession = true;
562 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
563 DocumentModel docModel = wrapper.getWrappedObject();
564 returnValue = (String) docModel.getPropertyValue(propertyXPath);
565 } catch (PropertyException pe) {
567 } catch (DocumentException de) {
569 } catch (Exception e) {
570 if (logger.isDebugEnabled()) {
571 logger.debug("Caught exception ", e);
573 throw new DocumentException(e);
575 if (releaseRepoSession && repoSession != null) {
576 repoClient.releaseRepositorySession(repoSession);
579 } catch (Exception e) {
580 if (logger.isDebugEnabled()) {
581 logger.debug("Caught exception ", e);
583 throw new DocumentException(e);
587 if (logger.isWarnEnabled() == true) {
588 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
597 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
600 public AuthorityRefList getAuthorityRefs(
602 List<AuthRefConfigInfo> authRefConfigInfoList) throws PropertyException, Exception {
604 AuthorityRefList authRefList = new AuthorityRefList();
605 AbstractCommonList commonList = (AbstractCommonList) authRefList;
607 DocumentFilter docFilter = this.getDocumentFilter();
608 long pageSize = docFilter.getPageSize();
609 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
610 // set the page size and page number
611 commonList.setPageNum(pageNum);
612 commonList.setPageSize(pageSize);
614 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
617 int iFirstToUse = (int)(pageSize*pageNum);
618 int nFoundInPage = 0;
621 ArrayList<RefNameServiceUtils.AuthRefInfo> foundReferences
622 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
624 boolean releaseRepoSession = false;
625 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
626 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
627 CoreSessionInterface repoSession = this.getRepositorySession();
628 if (repoSession == null) {
629 repoSession = repoClient.getRepositorySession(ctx);
630 releaseRepoSession = true;
631 this.setRepositorySession(repoSession); // we (the doc handler) should keep track of this repository session in case we need it
635 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
636 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefConfigInfoList, null, foundReferences);
637 // Slightly goofy pagination support - how many refs do we expect from one object?
638 for(RefNameServiceUtils.AuthRefInfo ari:foundReferences) {
639 if ((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
640 if(appendToAuthRefsList(ari, list)) {
649 if (releaseRepoSession == true) {
650 repoClient.releaseRepositorySession(ctx, repoSession);
654 // Set num of items in list. this is useful to our testing framework.
655 commonList.setItemsInPage(nFoundInPage);
656 // set the total result size
657 commonList.setTotalItems(nFoundTotal);
659 } catch (PropertyException pe) {
660 String msg = "Attempted to retrieve value for invalid or missing authority field. "
661 + "Check authority field properties in tenant bindings.";
662 logger.warn(msg, pe);
664 } catch (Exception e) {
665 if (logger.isDebugEnabled()) {
666 logger.debug("Caught exception in getAuthorityRefs", e);
668 Response response = Response.status(
669 Response.Status.INTERNAL_SERVER_ERROR).entity(
670 "Failed to retrieve authority references").type(
671 "text/plain").build();
672 throw new CSWebApplicationException(e, response);
678 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
679 List<AuthorityRefList.AuthorityRefItem> list)
681 String fieldName = ari.getQualifiedDisplayName();
683 String refNameValue = (String)ari.getProperty().getValue();
684 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
685 if(item!=null) { // ignore garbage values.
689 } catch(PropertyException pe) {
690 String msg = "PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage();
691 if (logger.isDebugEnabled()) {
692 logger.debug(msg, pe);
701 * Fill in all the values to be returned in the authrefs payload for this item.
703 * @param authRefFieldName
707 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
709 // Find the CSID for the authority item
713 DocumentModel docModel = NuxeoUtils.getDocModelForRefName(getServiceContext(), refName, getServiceContext().getResourceMap());
714 csid = NuxeoUtils.getCsid(docModel);
715 } catch (Exception e1) {
716 String msg = String.format("Could not find CSID for authority reference with refname = %s.", refName);
717 if (logger.isDebugEnabled()) {
718 logger.debug(msg, e1);
724 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
726 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
727 if (Tools.isEmpty(csid) == false) {
728 ilistItem.setCsid(csid);
730 ilistItem.setRefName(refName);
731 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
732 ilistItem.setItemDisplayName(termInfo.displayName);
733 ilistItem.setSourceField(authRefFieldName);
734 ilistItem.setUri(termInfo.getRelativeUri());
735 } catch (Exception e) {
737 String msg = String.format("Trouble parsing refName from value: %s in field: %s. Error message: %s.",
738 refName, authRefFieldName, e.getLocalizedMessage());
739 if (logger.isDebugEnabled()) {
740 logger.debug(msg, e);
750 * Returns the primary value from a list of values.
752 * Assumes that the first value is the primary value.
753 * This assumption may change when and if the primary value
754 * is identified explicitly.
756 * @param values a list of values.
757 * @param propertyName the name of a property through
758 * which the value can be extracted.
759 * @return the primary value.
760 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
761 String primaryValue = "";
762 if (values == null || values.size() == 0) {
765 Object value = values.get(0);
766 if (value instanceof String) {
768 primaryValue = (String) value;
770 // Multivalue group of fields
771 } else if (value instanceof Map) {
773 Map map = (Map) value;
774 if (map.values().size() > 0) {
775 if (map.get(propertyName) != null) {
776 primaryValue = (String) map.get(propertyName);
781 logger.warn("Unexpected type for property " + propertyName
782 + " in multivalue list: not String or Map.");
789 * Gets a simple property from the document.
791 * For completeness, as this duplicates DocumentModel method.
793 * @param docModel The document model to get info from
794 * @param schema The name of the schema (part)
795 * @param propertyName The simple scalar property type
796 * @return property value as String
798 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
799 String xpath = "/"+schema+":"+propName;
801 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
802 } catch(PropertyException pe) {
803 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
804 +pe.getLocalizedMessage());
805 } catch(ClassCastException cce) {
806 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
807 +cce.getLocalizedMessage());
808 } catch(Exception e) {
809 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
810 +e.getLocalizedMessage());
815 * Gets first of a repeating list of scalar values, as a String, from the document.
817 * @param docModel The document model to get info from
818 * @param schema The name of the schema (part)
819 * @param listName The name of the scalar list property
820 * @return first value in list, as a String, or empty string if the list is empty
822 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
823 String schema, String listName) {
824 String xpath = "/" + schema + ":" + listName + "/[0]";
826 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
827 } catch (PropertyException pe) {
828 throw new RuntimeException("Problem retrieving property {" + xpath
829 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
830 } catch (IndexOutOfBoundsException ioobe) {
831 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
832 return ""; // gracefully handle missing elements
833 } catch (ClassCastException cce) {
834 throw new RuntimeException("Problem retrieving property {" + xpath
835 + "} as String. Not a repeating String property?"
836 + cce.getLocalizedMessage());
837 } catch (Exception e) {
838 throw new RuntimeException("Unknown problem retrieving property {"
839 + xpath + "}." + e.getLocalizedMessage());
844 * Gets first of a repeating list of scalar values, as a String, from the document.
846 * @param docModel The document model to get info from
847 * @param schema The name of the schema (part)
848 * @param listName The name of the scalar list property
849 * @return first value in list, as a String, or empty string if the list is empty
851 protected String getStringValueInPrimaryRepeatingComplexProperty(
852 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
853 String result = null;
855 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
857 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
858 } catch(PropertyException pe) {
859 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
860 +pe.getLocalizedMessage());
861 } catch(IndexOutOfBoundsException ioobe) {
862 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
863 result = ""; // gracefully handle missing elements
864 } catch(ClassCastException cce) {
865 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
866 +cce.getLocalizedMessage());
867 } catch(NullPointerException npe) {
868 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
869 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
871 } catch(Exception e) {
872 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
873 +e.getLocalizedMessage());
880 * Gets XPath value from schema. Note that only "/" and "[n]" are
881 * supported for xpath. Can omit grouping elements for repeating complex types,
882 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
883 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
884 * If there are no entries for a list of scalars or for a list of complex types,
885 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
886 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
887 * that many elements in the list.
888 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
890 * @param docModel The document model to get info from
891 * @param schema The name of the schema (part)
892 * @param xpath The XPath expression (without schema prefix)
893 * @return value the indicated property value as a String
894 * @throws DocumentException
896 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
897 String schema, ListResultField field) throws DocumentException {
898 Object result = null;
900 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
905 protected String getStringValue(DocumentModel docModel,
906 String schema, ListResultField field) throws DocumentException {
907 String result = null;
909 Object value = getListResultValue(docModel, schema, field);
910 if (value != null && value instanceof String) {
911 String strValue = (String) value;
912 if (strValue.trim().isEmpty() == false) {
920 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
924 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
926 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
928 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
930 sb.append(item.getPredicate());
932 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
936 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
937 StringBuilder sb = new StringBuilder();
939 if (list.size() > 0) {
940 sb.append("=========== " + label + " ==========" + CR);
942 for (RelationsCommonList.RelationListItem item : list) {
943 itemToString(sb, "== ", item);
946 return sb.toString();
949 /** @return null on parent not found
951 protected String getParentCSID(String thisCSID) throws Exception {
952 String parentCSID = null;
954 String predicate = RelationshipType.HAS_BROADER.value();
955 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
956 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
957 if (parentList != null) {
958 if (parentList.size() == 0) {
961 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
962 parentCSID = relationListItem.getObjectCsid();
965 } catch (Exception e) {
966 logger.error("Could not find parent for this: " + thisCSID, e);
971 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
972 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
976 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
977 MultipartServiceContext ctx) throws Exception {
978 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
979 String parentCSID = getParentCSID(thisCSID);
980 if (parentCSID == null) {
981 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
985 String predicate = RelationshipType.HAS_BROADER.value();
986 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
987 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
989 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
992 RelationsCommonList.RelationListItem item = null;
993 for (RelationsCommonList.RelationListItem sibling : siblingList) {
994 if (thisCSID.equals(sibling.getSubjectCsid())) {
995 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.
998 //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.
999 for (RelationsCommonList.RelationListItem self : toRemoveList) {
1000 removeFromList(siblingList, self);
1003 long siblingSize = siblingList.size();
1004 siblingListOuter.setTotalItems(siblingSize);
1005 siblingListOuter.setItemsInPage(siblingSize);
1006 if(logger.isTraceEnabled()) {
1007 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
1008 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1011 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
1012 ctx.addOutputPart(relationsPart);
1015 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
1016 MultipartServiceContext ctx) throws Exception {
1017 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1019 String predicate = RelationshipType.HAS_BROADER.value();
1020 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1021 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1023 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
1024 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
1026 if(logger.isTraceEnabled()) {
1027 String dump = dumpLists(thisCSID, parentList, childrenList, null);
1028 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1031 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
1032 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
1033 //Not optimal, but that's the current design spec.
1035 for (RelationsCommonList.RelationListItem parent : parentList) {
1036 childrenList.add(parent);
1039 long childrenSize = childrenList.size();
1040 childrenListOuter.setTotalItems(childrenSize);
1041 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1043 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1044 ctx.addOutputPart(relationsPart);
1047 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1048 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1050 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1051 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1053 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1054 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1056 if(logger.isTraceEnabled()) {
1057 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1058 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1061 subjectList.addAll(objectList);
1063 //now subjectList actually has records BOTH where thisCSID is subject and object.
1064 long relatedSize = subjectList.size();
1065 subjectListOuter.setTotalItems(relatedSize);
1066 subjectListOuter.setItemsInPage(relatedSize);
1068 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1069 ctx.addOutputPart(relationsPart);
1072 private String dumpLists(String itemCSID,
1073 List<RelationsCommonList.RelationListItem> parentList,
1074 List<RelationsCommonList.RelationListItem> childList,
1075 List<RelationsCommonList.RelationListItem> actionList) {
1076 StringBuilder sb = new StringBuilder();
1077 sb.append("itemCSID: " + itemCSID + CR);
1078 if(parentList!=null) {
1079 sb.append(dumpList(parentList, "parentList"));
1081 if(childList!=null) {
1082 sb.append(dumpList(childList, "childList"));
1084 if(actionList!=null) {
1085 sb.append(dumpList(actionList, "actionList"));
1087 return sb.toString();
1090 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1091 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1092 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1093 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1094 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1095 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1096 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1098 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1099 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1100 return relationsCommonList;
1102 //============================= END TODO refactor ==========================
1104 // this method calls the RelationResource to have it create the relations and persist them.
1105 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1106 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1107 for (RelationsCommonList.RelationListItem item : inboundList) {
1108 RelationsCommon rc = new RelationsCommon();
1109 //rc.setCsid(item.getCsid());
1110 //todo: assignTo(item, rc);
1111 RelationsDocListItem itemSubject = item.getSubject();
1112 RelationsDocListItem itemObject = item.getObject();
1114 // Set at least one of CSID and refName for Subject and Object
1115 // Either value might be null for for each of Subject and Object
1116 String subjectCsid = itemSubject.getCsid();
1117 rc.setSubjectCsid(subjectCsid);
1119 String objCsid = itemObject.getCsid();
1120 rc.setObjectCsid(objCsid);
1122 rc.setSubjectRefName(itemSubject.getRefName());
1123 rc.setObjectRefName(itemObject.getRefName());
1125 rc.setRelationshipType(item.getPredicate());
1126 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1127 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1128 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1130 // This is superfluous, since it will be fetched by the Relations Create logic.
1131 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1132 rc.setObjectDocumentType(itemObject.getDocumentType());
1134 // This is superfluous, since it will be fetched by the Relations Create logic.
1135 rc.setSubjectUri(itemSubject.getUri());
1136 rc.setObjectUri(itemObject.getUri());
1137 // May not have the info here. Only really require CSID or refName.
1138 // Rest is handled in the Relation create mechanism
1139 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1141 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1142 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1143 payloadOut.addPart(outputPart);
1144 RelationResource relationResource = new RelationResource();
1145 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1146 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1150 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1151 // But item1 must not be sparse
1152 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1153 if (item1 == null || item2 == null) {
1156 RelationsDocListItem subj1 = item1.getSubject();
1157 RelationsDocListItem subj2 = item2.getSubject();
1158 RelationsDocListItem obj1 = item1.getObject();
1159 RelationsDocListItem obj2 = item2.getObject();
1161 String subj1Csid = subj1.getCsid();
1162 String subj2Csid = subj2.getCsid();
1163 String subj1RefName = subj1.getRefName();
1164 String subj2RefName = subj2.getRefName();
1166 String obj1Csid = obj1.getCsid();
1167 String obj2Csid = obj2.getCsid();
1168 String obj1RefName = obj1.getRefName();
1169 String obj2RefName = obj2.getRefName();
1171 String item1Metatype = item1.getRelationshipMetaType();
1172 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1174 String item2Metatype = item2.getRelationshipMetaType();
1175 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1177 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1178 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1179 // predicate is proper, but still allow relationshipType
1180 && (item1.getPredicate().equals(item2.getPredicate())
1181 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1182 // Allow missing docTypes, so long as they do not conflict
1183 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1184 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1185 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1189 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1190 // But the list items must not be sparse
1191 private RelationsCommonList.RelationListItem findInList(
1192 List<RelationsCommonList.RelationListItem> list,
1193 RelationsCommonList.RelationListItem item) {
1194 RelationsCommonList.RelationListItem foundItem = null;
1195 for (RelationsCommonList.RelationListItem listItem : list) {
1196 if (itemsEqual(listItem, item)) { //equals must be defined, else
1197 foundItem = listItem;
1204 /** updateRelations strategy:
1207 go through inboundList, remove anything from childList that matches from childList
1208 go through inboundList, remove anything from parentList that matches from parentList
1209 go through parentList, delete all remaining
1210 go through childList, delete all remaining
1211 go through actionList, add all remaining.
1212 check for duplicate children
1213 check for more than one parent.
1215 inboundList parentList childList actionList
1216 ---------------- --------------- ---------------- ----------------
1217 child-a parent-c child-a child-b
1218 child-b parent-d child-c
1223 private RelationsCommonList updateRelations(
1224 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate)
1226 if (logger.isTraceEnabled()) {
1227 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1229 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1231 return null; //nothing to do--they didn't send a list of relations.
1233 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1234 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1235 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1236 List<RelationsCommonList.RelationListItem> childList = null;
1237 List<RelationsCommonList.RelationListItem> parentList = null;
1238 DocumentModel docModel = wrapDoc.getWrappedObject();
1239 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1240 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1242 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1243 //Do magic replacement of ${itemCSID} and fix URI's.
1244 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1246 final String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1247 UriInfo uriInfo = ctx.getUriInfo();
1248 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1251 //Run getList() once as sent to get childListOuter:
1252 String predicate = RelationshipType.HAS_BROADER.value();
1253 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1254 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1255 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1256 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1257 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1259 RelationResource relationResource = new RelationResource();
1260 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1262 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1263 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1264 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1265 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1266 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1269 childList = childListOuter.getRelationListItem();
1270 parentList = parentListOuter.getRelationListItem();
1272 if (parentList.size() > 1) {
1273 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1276 if (logger.isTraceEnabled()) {
1277 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1281 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1282 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1283 // and so the CSID for those may be null
1284 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1285 // Look for parents and children
1286 if(itemCSID.equals(inboundItem.getObject().getCsid())
1287 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1288 //then this is an item that says we have a child. That child is inboundItem
1289 RelationsCommonList.RelationListItem childItem =
1290 (childList == null) ? null : findInList(childList, inboundItem);
1291 if (childItem != null) {
1292 if (logger.isTraceEnabled()) {
1293 StringBuilder sb = new StringBuilder();
1294 itemToString(sb, "== Child: ", childItem);
1295 logger.trace("Found inboundChild in current child list: " + sb.toString());
1297 removeFromList(childList, childItem); //exists, just take it off delete list
1299 if (logger.isTraceEnabled()) {
1300 StringBuilder sb = new StringBuilder();
1301 itemToString(sb, "== Child: ", inboundItem);
1302 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1304 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1305 String newChildCsid = inboundItem.getSubject().getCsid();
1306 if(newChildCsid == null) {
1307 String newChildRefName = inboundItem.getSubject().getRefName();
1308 if (newChildRefName == null) {
1309 throw new RuntimeException("Child with no CSID or refName!");
1311 if (logger.isTraceEnabled()) {
1312 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1314 DocumentModel newChildDocModel =
1315 NuxeoBasedResource.getDocModelForRefName(getServiceContext(),
1316 newChildRefName, getServiceContext().getResourceMap());
1317 newChildCsid = getCsid(newChildDocModel);
1319 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1322 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1323 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1324 //then this is an item that says we have a parent. inboundItem is that parent.
1325 RelationsCommonList.RelationListItem parentItem =
1326 (parentList == null) ? null : findInList(parentList, inboundItem);
1327 if (parentItem != null) {
1328 removeFromList(parentList, parentItem); //exists, just take it off delete list
1330 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1333 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1336 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1339 if (logger.isTraceEnabled()) {
1340 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1341 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1344 if (logger.isTraceEnabled()) {
1345 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1346 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1348 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1349 deleteRelations(childList, ctx, "childList");
1351 if (logger.isTraceEnabled()) {
1352 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1353 + actionList.size() + " new parents and children.");
1355 createRelations(actionList, ctx);
1356 if (logger.isTraceEnabled()) {
1357 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1359 // We return all elements on the inbound list, since we have just worked to make them exist in the system
1360 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1361 return relationsCommonListBody;
1364 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1365 * and sets URI correctly for related items.
1366 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1368 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1369 List<RelationsCommonList.RelationListItem> inboundList,
1370 DocumentModel docModel,
1371 String itemCSID) throws Exception {
1372 String thisURI = this.getUri(docModel);
1373 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1374 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1375 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1376 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1377 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1379 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1380 inboundItem.setObjectCsid(itemCSID);
1381 inboundItemObject.setCsid(itemCSID);
1382 //inboundItemObject.setUri(getUri(docModel));
1385 String objectCsid = inboundItemObject.getCsid();
1386 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1387 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1388 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1389 inboundItemObject.setUri(uri); //CSPACE-4037
1392 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1394 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1395 inboundItem.setSubjectCsid(itemCSID);
1396 inboundItemSubject.setCsid(itemCSID);
1397 //inboundItemSubject.setUri(getUri(docModel));
1400 String subjectCsid = inboundItemSubject.getCsid();
1401 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1402 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1403 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1404 inboundItemSubject.setUri(uri); //CSPACE-4037
1407 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1412 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1413 MultivaluedMap<String, String> queryParams, String childCSID) {
1414 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1415 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1416 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1417 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1419 RelationResource relationResource = new RelationResource();
1420 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1421 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1422 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1423 deleteRelations(parentList, ctx, "parentList-delete");
1426 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1427 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1430 for (RelationsCommonList.RelationListItem item : list) {
1431 RelationResource relationResource = new RelationResource();
1432 if(logger.isTraceEnabled()) {
1433 StringBuilder sb = new StringBuilder();
1434 itemToString(sb, "==== TO DELETE: ", item);
1435 logger.trace(sb.toString());
1437 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1438 if (logger.isDebugEnabled()) {
1439 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1442 } catch (Throwable t) {
1443 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1448 // Note that we must do this after we have completed the Update, so that the repository has the
1449 // info for the item itself. The relations code must call into the repo to get info for each end.
1450 // This could be optimized to pass in the parent docModel, since it will often be one end.
1451 // Nevertheless, we should complete the item save before we do work on the relations, especially
1452 // since a save on Create might fail, and we would not want to create relations for something
1453 // that may not be created...
1454 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate) throws Exception {
1455 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1456 PoxPayloadIn input = ctx.getInput();
1457 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1458 String itemCsid = documentModel.getName();
1460 //Updates relations part
1461 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, forUpdate);
1463 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList); //FIXME: REM - We should check for a null relationsCommonList and not create the new common list payload
1464 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1466 //now we add part for relations list
1467 //ServiceContext ctx = getServiceContext();
1468 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1469 ctx.getOutput().addPart(payloadOutputPart);
1473 * Checks to see if the refName has changed, and if so,
1474 * uses utilities to find all references and update them to use the new refName.
1477 protected void handleRefNameReferencesUpdate() throws Exception {
1478 if (hasRefNameUpdate() == true) {
1479 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1480 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1481 CoreSessionInterface repoSession = this.getRepositorySession();
1483 // Update all the relationship records that referred to the old refName
1484 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1485 oldRefNameOnUpdate, newRefNameOnUpdate);
1489 protected String getRefNameUpdate() {
1490 String result = null;
1492 if (hasRefNameUpdate() == true) {
1493 result = newRefNameOnUpdate;
1494 if (logger.isDebugEnabled() == true) {
1495 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1496 newRefNameOnUpdate, oldRefNameOnUpdate));
1504 * Note: The Vocabulary document handler overrides this method.
1506 protected String getRefPropName() {
1507 return ServiceBindingUtils.AUTH_REF_PROP;