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.Collection;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
32 import java.util.Map.Entry;
35 import javax.ws.rs.core.MediaType;
36 import javax.ws.rs.core.MultivaluedMap;
37 import javax.ws.rs.core.Response;
38 import javax.ws.rs.core.UriInfo;
39 import javax.xml.bind.JAXBElement;
41 import org.collectionspace.authentication.spi.AuthNContext;
42 import org.collectionspace.services.authorization.AccountPermission;
43 import org.collectionspace.services.jaxb.AbstractCommonList;
44 import org.collectionspace.services.lifecycle.TransitionDef;
45 import org.collectionspace.services.client.AccountClient;
46 import org.collectionspace.services.client.CollectionSpaceClient;
47 import org.collectionspace.services.client.PayloadInputPart;
48 import org.collectionspace.services.client.PayloadOutputPart;
49 import org.collectionspace.services.client.PoxPayloadIn;
50 import org.collectionspace.services.client.PoxPayloadOut;
51 import org.collectionspace.services.client.Profiler;
52 import org.collectionspace.services.client.RelationClient;
53 import org.collectionspace.services.client.workflow.WorkflowClient;
54 import org.collectionspace.services.common.CSWebApplicationException;
55 import org.collectionspace.services.common.NuxeoBasedResource;
56 import org.collectionspace.services.common.authorityref.AuthorityRefList;
57 import org.collectionspace.services.common.config.ServiceConfigUtils;
58 import org.collectionspace.services.common.context.JaxRsContext;
59 import org.collectionspace.services.common.context.MultipartServiceContext;
60 import org.collectionspace.services.common.context.ServiceBindingUtils;
61 import org.collectionspace.services.common.context.ServiceContext;
62 import org.collectionspace.services.common.document.BadRequestException;
63 import org.collectionspace.services.common.document.DocumentException;
64 import org.collectionspace.services.common.document.DocumentUtils;
65 import org.collectionspace.services.common.document.DocumentWrapper;
66 import org.collectionspace.services.common.document.DocumentFilter;
67 import org.collectionspace.services.client.IRelationsManager;
68 import org.collectionspace.services.common.relation.RelationResource;
69 import org.collectionspace.services.common.repository.RepositoryClient;
70 import org.collectionspace.services.common.security.SecurityUtils;
71 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
72 import org.collectionspace.services.common.api.CommonAPI;
73 import org.collectionspace.services.common.api.RefNameUtils;
74 import org.collectionspace.services.common.api.Tools;
75 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
76 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
77 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
78 import org.collectionspace.services.config.service.DocHandlerParams;
79 import org.collectionspace.services.config.service.ListResultField;
80 import org.collectionspace.services.config.service.ObjectPartType;
81 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
82 import org.collectionspace.services.relation.RelationsCommon;
83 import org.collectionspace.services.relation.RelationsCommonList;
84 import org.collectionspace.services.relation.RelationsDocListItem;
85 import org.collectionspace.services.relation.RelationshipType;
86 import org.dom4j.Element;
87 import org.nuxeo.ecm.core.api.DocumentModel;
88 import org.nuxeo.ecm.core.api.DocumentModelList;
89 import org.nuxeo.ecm.core.api.DocumentNotFoundException;
90 import org.nuxeo.ecm.core.api.impl.DataModelImpl;
91 import org.nuxeo.ecm.core.api.model.DocumentPart;
92 import org.nuxeo.ecm.core.api.model.Property;
93 import org.nuxeo.ecm.core.api.model.PropertyException;
94 import org.nuxeo.ecm.core.api.model.impl.ScalarProperty;
95 import org.slf4j.Logger;
96 import org.slf4j.LoggerFactory;
99 * RemoteDocumentModelHandler
101 * $LastChangedRevision: $
102 * $LastChangedDate: $
106 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
107 extends DocumentModelHandler<T, TL> {
110 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
111 private final static String CR = "\r\n";
112 private final static String EMPTYSTR = "";
113 private static final String COLLECTIONSPACE_CORE_SCHEMA = CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA;
114 private static final String ACCOUNT_PERMISSION_COMMON_PART_NAME = AccountClient.SERVICE_COMMON_PART_NAME;
117 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
120 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
121 if (ctx instanceof MultipartServiceContext) {
122 super.setServiceContext(ctx);
124 throw new IllegalArgumentException("setServiceContext requires instance of "
125 + MultipartServiceContext.class.getName());
130 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
131 return getRefnameDisplayName(docWrapper.getWrappedObject());
134 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
135 String result = null;
136 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
138 DocHandlerParams.Params params = null;
140 params = ServiceConfigUtils.getDocHandlerParams(ctx);
141 ListResultField field = params.getRefnameDisplayNameField();
143 String schema = field.getSchema();
144 if (schema == null || schema.trim().isEmpty()) {
145 schema = ctx.getCommonPartLabel();
148 result = getStringValue(docModel, schema, field);
149 } catch (Exception e) {
150 if (logger.isWarnEnabled()) {
151 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
159 public boolean supportsHierarchy() {
160 boolean result = false;
162 DocHandlerParams.Params params = null;
164 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
165 params = ServiceConfigUtils.getDocHandlerParams(ctx);
166 Boolean bool = params.isSupportsHierarchy();
168 result = bool.booleanValue();
170 } catch (DocumentException e) {
171 // TODO Auto-generated catch block
172 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
173 if (logger.isWarnEnabled() == true) {
182 public boolean supportsVersioning() {
183 boolean result = false;
185 DocHandlerParams.Params params = null;
187 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
188 params = ServiceConfigUtils.getDocHandlerParams(ctx);
189 Boolean bool = params.isSupportsVersioning();
191 result = bool.booleanValue();
193 } catch (DocumentException e) {
194 // TODO Auto-generated catch block
195 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
196 if (logger.isWarnEnabled() == true) {
205 public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
207 // Do nothing by default, but children can override if they want. The real workflow transition happens in the WorkflowDocumentModelHandler class
211 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
212 super.completeCreate(wrapDoc);
213 if (supportsHierarchy() == true) {
214 handleRelationsPayload(wrapDoc, false);
218 /* NOTE: The authority item doc handler overrides (after calling) this method. It performs refName updates. In this
219 * method we just update any and all relationship records that use refNames that have changed.
221 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
224 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
225 DocumentModel docModel = wrapDoc.getWrappedObject();
227 String[] schemas = docModel.getDeclaredSchemas();
228 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
229 for (String schema : schemas) {
230 ObjectPartType partMeta = partsMetaMap.get(schema);
231 if (partMeta == null) {
232 continue; // unknown part, ignore
234 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
235 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
236 addExtraCoreValues(docModel, unQObjectProperties);
238 addOutputPart(unQObjectProperties, schema, partMeta);
242 // If the resource's service supports hierarchy then we need to perform a little more work
244 if (supportsHierarchy() == true) {
245 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
246 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
251 * Adds the output part.
253 * @param unQObjectProperties the un q object properties
254 * @param schema the schema
255 * @param partMeta the part meta
256 * @throws Exception the exception
257 * MediaType.APPLICATION_XML_TYPE
259 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
261 Element doc = DocumentUtils.buildDocument(partMeta, schema,
262 unQObjectProperties);
263 if (logger.isTraceEnabled() == true) {
264 logger.trace(doc.asXML());
266 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
267 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
271 * Extract paging info.
273 * @param commonsList the commons list
275 * @throws Exception the exception
277 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
279 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
281 DocumentFilter docFilter = this.getDocumentFilter();
282 long pageSize = docFilter.getPageSize();
283 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
284 // set the page size and page number
285 commonList.setPageNum(pageNum);
286 commonList.setPageSize(pageSize);
287 DocumentModelList docList = wrapDoc.getWrappedObject();
288 // Set num of items in list. this is useful to our testing framework.
289 commonList.setItemsInPage(docList.size());
290 // set the total result size
291 commonList.setTotalItems(docList.totalSize());
293 return (TL) commonList;
297 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
300 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
303 DocumentModel docModel = wrapDoc.getWrappedObject();
304 String[] schemas = docModel.getDeclaredSchemas();
305 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
306 for (String schema : schemas) {
307 ObjectPartType partMeta = partsMetaMap.get(schema);
308 if (partMeta == null) {
309 continue; // unknown part, ignore
311 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
312 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
313 addExtraCoreValues(docModel, unQObjectProperties);
315 addOutputPart(unQObjectProperties, schema, partMeta);
318 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
320 if (supportsHierarchy() == true) {
321 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
322 if (Tools.isTrue(showSiblings)) {
323 showSiblings(wrapDoc, ctx);
324 return; // actual result is returned on ctx.addOutputPart();
327 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
328 if (Tools.isTrue(showRelations)) {
329 showRelations(wrapDoc, ctx);
330 return; // actual result is returned on ctx.addOutputPart();
333 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
334 if (Tools.isTrue(showAllRelations)) {
335 showAllRelations(wrapDoc, ctx);
336 return; // actual result is returned on ctx.addOutputPart();
340 String currentUser = ctx.getUserId();
341 if (currentUser.equalsIgnoreCase(AuthNContext.ANONYMOUS_USER) == false) {
342 addAccountPermissionsPart();
346 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
348 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
351 private void addAccountPermissionsPart() throws Exception {
352 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
355 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
356 String currentServiceName = ctx.getServiceName();
357 String workflowSubResource = "/";
358 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
359 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
360 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
361 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
363 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
365 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
366 currentServiceName, workflowSubResource);
367 org.collectionspace.services.authorization.ObjectFactory objectFactory =
368 new org.collectionspace.services.authorization.ObjectFactory();
369 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
370 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
371 ctx.addOutputPart(accountPermissionPart);
377 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
380 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
382 //TODO filling extension parts should be dynamic
383 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
384 //not an ideal way of populating objects.
385 DocumentModel docModel = wrapDoc.getWrappedObject();
386 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
387 PoxPayloadIn input = ctx.getInput();
388 if (input == null || input.getParts().isEmpty()) {
389 String msg = String.format("No payload found for '%s' action.", action);
390 logger.error(msg + "Ctx=" + getServiceContext().toString());
391 throw new BadRequestException(msg);
394 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
396 //iterate over parts received and fill those parts
397 boolean werePartsFilled = false;
398 List<PayloadInputPart> inputParts = input.getParts();
399 for (PayloadInputPart part : inputParts) {
401 String partLabel = part.getLabel();
402 if (partLabel == null) {
403 String msg = "Part label is missing or empty!";
404 logger.error(msg + "Ctx=" + getServiceContext().toString());
405 throw new BadRequestException(msg);
408 //skip if the part is not in metadata or if it is a system part
409 ObjectPartType partMeta = partsMetaMap.get(partLabel);
410 if (partMeta == null || isSystemPart(partLabel)) {
413 fillPart(part, docModel, partMeta, action, ctx);
414 werePartsFilled = true;
417 if (logger.isTraceEnabled() && werePartsFilled == false) {
418 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.",
424 private boolean isSystemPart(String partLabel) {
425 boolean result = false;
427 if (partLabel != null && (partLabel.equalsIgnoreCase(COLLECTIONSPACE_CORE_SCHEMA) ||
428 partLabel.equalsIgnoreCase(ACCOUNT_PERMISSION_COMMON_PART_NAME))) {
436 * fillPart fills an XML part into given document model
437 * @param part to fill
438 * @param docModel for the given object
439 * @param partMeta metadata for the object to fill
442 protected void fillPart(PayloadInputPart part, DocumentModel docModel, ObjectPartType partMeta, Action action,
443 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
444 // check if this is an xml part
445 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
446 Element element = part.getElementBody();
447 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
448 if (action == Action.UPDATE) {
449 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
451 String schemaName = partMeta.getLabel();
452 docModel.setProperties(schemaName, objectProps);
453 Set<String> fieldNameSet = getFieldNamesSet(objectProps);
454 setFieldsDirty(docModel, schemaName, fieldNameSet); // Force Nuxeo to update EVERY field by marking them dirty/modified
460 * Due to an apparent Nuxeo bug, we need to explicitly mark all fields as dirty for updates
461 * to take place properly. See https://issues.collectionspace.org/browse/CSPACE-7124
463 * We should improve this code by marking dirty/modified only the fields that were part of the UPDATE/PUT request
464 * payload. The current code will mark any field with a name in the 'fieldNameSet' set. Since field names do not
465 * have to be unique in document/record, we might end up unnecessarily marking some fields as modified -not ideal but
466 * better than brute-force marking ALL fields as modified.
471 private void setFieldsDirty(DocumentModel docModel, String schemaName, Set<String> fieldNameSet) {
472 DataModelImpl dataModel = (DataModelImpl) docModel.getDataModel(schemaName);
473 DocumentPart documentPart = dataModel.getDocumentPart();
474 setFieldsDirty(documentPart.getChildren(), fieldNameSet);
478 * Recursively step through all the children and sub-children setting their dirty flags.
479 * See https://issues.collectionspace.org/browse/CSPACE-7124
482 private void setFieldsDirty(Collection<Property> children, Set<String> fieldNameSet) {
483 if (children != null && (children.size() > 0)) {
484 for (Property prop : children ) {
485 String propName = prop.getName();
486 logger.debug(propName);
487 if (prop.isPhantom() == false) {
488 if (prop.isScalar() == false) {
489 setFieldsDirty(prop.getChildren(), fieldNameSet);
490 } else if (fieldNameSet.contains(propName)) {
491 ScalarProperty scalarProp = (ScalarProperty)prop;
492 scalarProp.setIsModified();
500 * Gets the set of field names used in a map of properties. We'll use this make our workaround to a
501 * bug (See https://issues.collectionspace.org/browse/CSPACE-7124) more efficient by only marking fields
502 * with names in this set as modified -ie, needing persisting.
504 * Since the map of properties can contain other maps of properties as values, there can be duplicate
505 * field names in the 'objectProps' map. And since we're creating a set, there can be no duplicates in the result.
508 private Set<String> getFieldNamesSet(Map<String, Object> objectProps) {
509 HashSet<String> result = new HashSet<String>();
511 addFieldNamesToSet(result, objectProps);
516 private void addFieldNamesToSet(Set<String> fieldNameSet, List<Object> valueList) {
517 for (Object value : valueList) {
518 if (value instanceof List) {
519 addFieldNamesToSet(fieldNameSet, (List<Object>)value);
520 } else if (value instanceof Map) {
521 addFieldNamesToSet(fieldNameSet, (Map<String, Object>)value);
523 logger.debug(value.toString());
528 private void addFieldNamesToSet(Set<String> fieldNameSet, Map<String, Object> objectProps) {
529 for (String key : objectProps.keySet()) {
530 Object value = objectProps.get(key);
531 if (value instanceof Map) {
532 addFieldNamesToSet(fieldNameSet, (Map<String, Object>)value);
533 } else if (value instanceof List) {
534 addFieldNamesToSet(fieldNameSet, (List)value);
536 fieldNameSet.add(key);
542 * Filters out read only properties, so they cannot be set on update.
543 * TODO: add configuration support to do this generally
544 * @param objectProps the properties parsed from the update payload
545 * @param partMeta metadata for the object to fill
547 public void filterReadOnlyPropertiesForPart(
548 Map<String, Object> objectProps, ObjectPartType partMeta) {
549 // Should add in logic to filter most of the core items on update
550 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
551 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
552 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
553 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
554 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
555 // Note that the updatedAt/updatedBy fields are set internally
556 // in DocumentModelHandler.handleCoreValues().
561 * extractPart extracts an XML object from given DocumentModel
563 * @param schema of the object to extract
564 * @param partMeta metadata for the object to extract
567 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
569 return extractPart(docModel, schema, (Map<String, Object>)null);
573 * extractPart extracts an XML object from given DocumentModel
575 * @param schema of the object to extract
576 * @param partMeta metadata for the object to extract
580 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
582 return extractPart(docModel, schema, partMeta, null);
586 * extractPart extracts an XML object from given DocumentModel
588 * @param schema of the object to extract
589 * @param partMeta metadata for the object to extract
592 protected Map<String, Object> extractPart(
593 DocumentModel docModel,
595 Map<String, Object> addToMap)
597 Map<String, Object> result = null;
599 Map<String, Object> objectProps = docModel.getProperties(schema);
600 if (objectProps != null) {
601 //unqualify properties before sending the doc over the wire (to save bandwidh)
602 //FIXME: is there a better way to avoid duplication of a Map/Collection?
603 Map<String, Object> unQObjectProperties =
604 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
605 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
606 for (Entry<String, Object> entry : qualifiedEntries) {
607 String unqProp = getUnQProperty(entry.getKey());
608 unQObjectProperties.put(unqProp, entry.getValue());
610 result = unQObjectProperties;
617 * extractPart extracts an XML object from given DocumentModel
619 * @param schema of the object to extract
620 * @param partMeta metadata for the object to extract
624 protected Map<String, Object> extractPart(
625 DocumentModel docModel, String schema, ObjectPartType partMeta,
626 Map<String, Object> addToMap)
628 Map<String, Object> result = null;
630 result = this.extractPart(docModel, schema, addToMap);
636 public String getStringPropertyFromDoc(
639 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
640 RepositoryInstance repoSession = null;
641 boolean releaseRepoSession = false;
642 String returnValue = null;
645 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
646 repoSession = this.getRepositorySession();
647 if (repoSession == null) {
648 repoSession = repoClient.getRepositorySession();
649 releaseRepoSession = true;
653 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
654 DocumentModel docModel = wrapper.getWrappedObject();
655 returnValue = (String) docModel.getPropertyValue(propertyXPath);
656 } catch (PropertyException pe) {
658 } catch (DocumentException de) {
660 } catch (Exception e) {
661 if (logger.isDebugEnabled()) {
662 logger.debug("Caught exception ", e);
664 throw new DocumentException(e);
666 if (releaseRepoSession && repoSession != null) {
667 repoClient.releaseRepositorySession(repoSession);
670 } catch (Exception e) {
671 if (logger.isDebugEnabled()) {
672 logger.debug("Caught exception ", e);
674 throw new DocumentException(e);
678 if (logger.isWarnEnabled() == true) {
679 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
688 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
691 public AuthorityRefList getAuthorityRefs(
693 List<AuthRefConfigInfo> authRefConfigInfoList) throws PropertyException, Exception {
695 AuthorityRefList authRefList = new AuthorityRefList();
696 AbstractCommonList commonList = (AbstractCommonList) authRefList;
698 DocumentFilter docFilter = this.getDocumentFilter();
699 long pageSize = docFilter.getPageSize();
700 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
701 // set the page size and page number
702 commonList.setPageNum(pageNum);
703 commonList.setPageSize(pageSize);
705 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
708 int iFirstToUse = (int)(pageSize*pageNum);
709 int nFoundInPage = 0;
712 ArrayList<RefNameServiceUtils.AuthRefInfo> foundReferences
713 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
715 boolean releaseRepoSession = false;
716 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
717 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
718 CoreSessionInterface repoSession = this.getRepositorySession();
719 if (repoSession == null) {
720 repoSession = repoClient.getRepositorySession(ctx);
721 releaseRepoSession = true;
722 this.setRepositorySession(repoSession); // we (the doc handler) should keep track of this repository session in case we need it
726 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
727 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefConfigInfoList, null, foundReferences);
728 // Slightly goofy pagination support - how many refs do we expect from one object?
729 for(RefNameServiceUtils.AuthRefInfo ari:foundReferences) {
730 if ((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
731 if(appendToAuthRefsList(ari, list)) {
740 if (releaseRepoSession == true) {
741 repoClient.releaseRepositorySession(ctx, repoSession);
745 // Set num of items in list. this is useful to our testing framework.
746 commonList.setItemsInPage(nFoundInPage);
747 // set the total result size
748 commonList.setTotalItems(nFoundTotal);
750 } catch (PropertyException pe) {
751 String msg = "Attempted to retrieve value for invalid or missing authority field. "
752 + "Check authority field properties in tenant bindings.";
753 logger.warn(msg, pe);
755 } catch (Exception e) {
756 if (logger.isDebugEnabled()) {
757 logger.debug("Caught exception in getAuthorityRefs", e);
759 Response response = Response.status(
760 Response.Status.INTERNAL_SERVER_ERROR).entity(
761 "Failed to retrieve authority references").type(
762 "text/plain").build();
763 throw new CSWebApplicationException(e, response);
769 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
770 List<AuthorityRefList.AuthorityRefItem> list)
772 String fieldName = ari.getQualifiedDisplayName();
774 String refNameValue = (String)ari.getProperty().getValue();
775 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
776 if(item!=null) { // ignore garbage values.
780 } catch(PropertyException pe) {
781 String msg = "PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage();
782 if (logger.isDebugEnabled()) {
783 logger.debug(msg, pe);
792 * Fill in all the values to be returned in the authrefs payload for this item.
794 * @param authRefFieldName
798 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
800 // Find the CSID for the authority item
804 DocumentModel docModel = NuxeoUtils.getDocModelForRefName(getServiceContext(), refName, getServiceContext().getResourceMap());
805 csid = NuxeoUtils.getCsid(docModel);
806 } catch (Exception e1) {
807 String msg = String.format("Could not find CSID for authority reference with refname = %s.", refName);
808 if (logger.isDebugEnabled()) {
809 logger.debug(msg, e1);
815 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
817 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
818 if (Tools.isEmpty(csid) == false) {
819 ilistItem.setCsid(csid);
821 ilistItem.setRefName(refName);
822 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
823 ilistItem.setItemDisplayName(termInfo.displayName);
824 ilistItem.setSourceField(authRefFieldName);
825 ilistItem.setUri(termInfo.getRelativeUri());
826 } catch (Exception e) {
828 String msg = String.format("Trouble parsing refName from value: %s in field: %s. Error message: %s.",
829 refName, authRefFieldName, e.getLocalizedMessage());
830 if (logger.isDebugEnabled()) {
831 logger.debug(msg, e);
841 * Returns the primary value from a list of values.
843 * Assumes that the first value is the primary value.
844 * This assumption may change when and if the primary value
845 * is identified explicitly.
847 * @param values a list of values.
848 * @param propertyName the name of a property through
849 * which the value can be extracted.
850 * @return the primary value.
851 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
852 String primaryValue = "";
853 if (values == null || values.size() == 0) {
856 Object value = values.get(0);
857 if (value instanceof String) {
859 primaryValue = (String) value;
861 // Multivalue group of fields
862 } else if (value instanceof Map) {
864 Map map = (Map) value;
865 if (map.values().size() > 0) {
866 if (map.get(propertyName) != null) {
867 primaryValue = (String) map.get(propertyName);
872 logger.warn("Unexpected type for property " + propertyName
873 + " in multivalue list: not String or Map.");
880 * Gets a simple property from the document.
882 * For completeness, as this duplicates DocumentModel method.
884 * @param docModel The document model to get info from
885 * @param schema The name of the schema (part)
886 * @param propertyName The simple scalar property type
887 * @return property value as String
889 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
890 String xpath = "/"+schema+":"+propName;
892 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
893 } catch(PropertyException pe) {
894 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
895 +pe.getLocalizedMessage());
896 } catch(ClassCastException cce) {
897 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
898 +cce.getLocalizedMessage());
899 } catch(Exception e) {
900 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
901 +e.getLocalizedMessage());
906 * Gets first of a repeating list of scalar values, as a String, from the document.
908 * @param docModel The document model to get info from
909 * @param schema The name of the schema (part)
910 * @param listName The name of the scalar list property
911 * @return first value in list, as a String, or empty string if the list is empty
913 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
914 String schema, String listName) {
915 String xpath = "/" + schema + ":" + listName + "/[0]";
917 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
918 } catch (PropertyException pe) {
919 throw new RuntimeException("Problem retrieving property {" + xpath
920 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
921 } catch (IndexOutOfBoundsException ioobe) {
922 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
923 return ""; // gracefully handle missing elements
924 } catch (ClassCastException cce) {
925 throw new RuntimeException("Problem retrieving property {" + xpath
926 + "} as String. Not a repeating String property?"
927 + cce.getLocalizedMessage());
928 } catch (Exception e) {
929 throw new RuntimeException("Unknown problem retrieving property {"
930 + xpath + "}." + e.getLocalizedMessage());
935 * Gets first of a repeating list of scalar values, as a String, from the document.
937 * @param docModel The document model to get info from
938 * @param schema The name of the schema (part)
939 * @param listName The name of the scalar list property
940 * @return first value in list, as a String, or empty string if the list is empty
942 protected String getStringValueInPrimaryRepeatingComplexProperty(
943 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
944 String result = null;
946 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
948 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
949 } catch(PropertyException pe) {
950 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
951 +pe.getLocalizedMessage());
952 } catch(IndexOutOfBoundsException ioobe) {
953 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
954 result = ""; // gracefully handle missing elements
955 } catch(ClassCastException cce) {
956 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
957 +cce.getLocalizedMessage());
958 } catch(NullPointerException npe) {
959 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
960 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
962 } catch(Exception e) {
963 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
964 +e.getLocalizedMessage());
971 * Gets XPath value from schema. Note that only "/" and "[n]" are
972 * supported for xpath. Can omit grouping elements for repeating complex types,
973 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
974 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
975 * If there are no entries for a list of scalars or for a list of complex types,
976 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
977 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
978 * that many elements in the list.
979 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
981 * @param docModel The document model to get info from
982 * @param schema The name of the schema (part)
983 * @param xpath The XPath expression (without schema prefix)
984 * @return value the indicated property value as a String
985 * @throws DocumentException
987 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
988 String schema, ListResultField field) throws DocumentException {
989 Object result = null;
991 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
996 protected String getStringValue(DocumentModel docModel,
997 String schema, ListResultField field) throws DocumentException {
998 String result = null;
1000 Object value = getListResultValue(docModel, schema, field);
1001 if (value != null && value instanceof String) {
1002 String strValue = (String) value;
1003 if (strValue.trim().isEmpty() == false) {
1011 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1015 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
1017 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
1019 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
1021 sb.append(item.getPredicate());
1023 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
1027 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
1028 StringBuilder sb = new StringBuilder();
1030 if (list.size() > 0) {
1031 sb.append("=========== " + label + " ==========" + CR);
1033 for (RelationsCommonList.RelationListItem item : list) {
1034 itemToString(sb, "== ", item);
1037 return sb.toString();
1040 /** @return null on parent not found
1042 protected String getParentCSID(String thisCSID) throws Exception {
1043 String parentCSID = null;
1045 String predicate = RelationshipType.HAS_BROADER.value();
1046 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1047 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1048 if (parentList != null) {
1049 if (parentList.size() == 0) {
1052 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
1053 parentCSID = relationListItem.getObjectCsid();
1056 } catch (Exception e) {
1057 logger.error("Could not find parent for this: " + thisCSID, e);
1062 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
1063 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
1067 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
1068 MultipartServiceContext ctx) throws Exception {
1069 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1070 String parentCSID = getParentCSID(thisCSID);
1071 if (parentCSID == null) {
1072 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
1076 String predicate = RelationshipType.HAS_BROADER.value();
1077 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
1078 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
1080 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
1083 RelationsCommonList.RelationListItem item = null;
1084 for (RelationsCommonList.RelationListItem sibling : siblingList) {
1085 if (thisCSID.equals(sibling.getSubjectCsid())) {
1086 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.
1089 //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.
1090 for (RelationsCommonList.RelationListItem self : toRemoveList) {
1091 removeFromList(siblingList, self);
1094 long siblingSize = siblingList.size();
1095 siblingListOuter.setTotalItems(siblingSize);
1096 siblingListOuter.setItemsInPage(siblingSize);
1097 if(logger.isTraceEnabled()) {
1098 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
1099 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1102 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
1103 ctx.addOutputPart(relationsPart);
1106 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
1107 MultipartServiceContext ctx) throws Exception {
1108 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1110 String predicate = RelationshipType.HAS_BROADER.value();
1111 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1112 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1114 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
1115 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
1117 if(logger.isTraceEnabled()) {
1118 String dump = dumpLists(thisCSID, parentList, childrenList, null);
1119 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1122 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
1123 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
1124 //Not optimal, but that's the current design spec.
1126 for (RelationsCommonList.RelationListItem parent : parentList) {
1127 childrenList.add(parent);
1130 long childrenSize = childrenList.size();
1131 childrenListOuter.setTotalItems(childrenSize);
1132 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1134 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1135 ctx.addOutputPart(relationsPart);
1138 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1139 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1141 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1142 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1144 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1145 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1147 if(logger.isTraceEnabled()) {
1148 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1149 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1152 subjectList.addAll(objectList);
1154 //now subjectList actually has records BOTH where thisCSID is subject and object.
1155 long relatedSize = subjectList.size();
1156 subjectListOuter.setTotalItems(relatedSize);
1157 subjectListOuter.setItemsInPage(relatedSize);
1159 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1160 ctx.addOutputPart(relationsPart);
1163 private String dumpLists(String itemCSID,
1164 List<RelationsCommonList.RelationListItem> parentList,
1165 List<RelationsCommonList.RelationListItem> childList,
1166 List<RelationsCommonList.RelationListItem> actionList) {
1167 StringBuilder sb = new StringBuilder();
1168 sb.append("itemCSID: " + itemCSID + CR);
1169 if(parentList!=null) {
1170 sb.append(dumpList(parentList, "parentList"));
1172 if(childList!=null) {
1173 sb.append(dumpList(childList, "childList"));
1175 if(actionList!=null) {
1176 sb.append(dumpList(actionList, "actionList"));
1178 return sb.toString();
1181 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1182 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1183 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1184 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1185 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1186 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1187 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1189 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1190 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1191 return relationsCommonList;
1193 //============================= END TODO refactor ==========================
1195 // this method calls the RelationResource to have it create the relations and persist them.
1196 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1197 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1198 for (RelationsCommonList.RelationListItem item : inboundList) {
1199 RelationsCommon rc = new RelationsCommon();
1200 //rc.setCsid(item.getCsid());
1201 //todo: assignTo(item, rc);
1202 RelationsDocListItem itemSubject = item.getSubject();
1203 RelationsDocListItem itemObject = item.getObject();
1205 // Set at least one of CSID and refName for Subject and Object
1206 // Either value might be null for for each of Subject and Object
1207 String subjectCsid = itemSubject.getCsid();
1208 rc.setSubjectCsid(subjectCsid);
1210 String objCsid = itemObject.getCsid();
1211 rc.setObjectCsid(objCsid);
1213 rc.setSubjectRefName(itemSubject.getRefName());
1214 rc.setObjectRefName(itemObject.getRefName());
1216 rc.setRelationshipType(item.getPredicate());
1217 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1218 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1219 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1221 // This is superfluous, since it will be fetched by the Relations Create logic.
1222 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1223 rc.setObjectDocumentType(itemObject.getDocumentType());
1225 // This is superfluous, since it will be fetched by the Relations Create logic.
1226 rc.setSubjectUri(itemSubject.getUri());
1227 rc.setObjectUri(itemObject.getUri());
1228 // May not have the info here. Only really require CSID or refName.
1229 // Rest is handled in the Relation create mechanism
1230 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1232 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1233 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1234 payloadOut.addPart(outputPart);
1235 RelationResource relationResource = new RelationResource();
1236 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1237 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1241 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1242 // But item1 must not be sparse
1243 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1244 if (item1 == null || item2 == null) {
1247 RelationsDocListItem subj1 = item1.getSubject();
1248 RelationsDocListItem subj2 = item2.getSubject();
1249 RelationsDocListItem obj1 = item1.getObject();
1250 RelationsDocListItem obj2 = item2.getObject();
1252 String subj1Csid = subj1.getCsid();
1253 String subj2Csid = subj2.getCsid();
1254 String subj1RefName = subj1.getRefName();
1255 String subj2RefName = subj2.getRefName();
1257 String obj1Csid = obj1.getCsid();
1258 String obj2Csid = obj2.getCsid();
1259 String obj1RefName = obj1.getRefName();
1260 String obj2RefName = obj2.getRefName();
1262 String item1Metatype = item1.getRelationshipMetaType();
1263 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1265 String item2Metatype = item2.getRelationshipMetaType();
1266 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1268 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1269 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1270 // predicate is proper, but still allow relationshipType
1271 && (item1.getPredicate().equals(item2.getPredicate())
1272 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1273 // Allow missing docTypes, so long as they do not conflict
1274 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1275 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1276 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1280 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1281 // But the list items must not be sparse
1282 private RelationsCommonList.RelationListItem findInList(
1283 List<RelationsCommonList.RelationListItem> list,
1284 RelationsCommonList.RelationListItem item) {
1285 RelationsCommonList.RelationListItem foundItem = null;
1286 for (RelationsCommonList.RelationListItem listItem : list) {
1287 if (itemsEqual(listItem, item)) { //equals must be defined, else
1288 foundItem = listItem;
1295 /** updateRelations strategy:
1298 go through inboundList, remove anything from childList that matches from childList
1299 go through inboundList, remove anything from parentList that matches from parentList
1300 go through parentList, delete all remaining
1301 go through childList, delete all remaining
1302 go through actionList, add all remaining.
1303 check for duplicate children
1304 check for more than one parent.
1306 inboundList parentList childList actionList
1307 ---------------- --------------- ---------------- ----------------
1308 child-a parent-c child-a child-b
1309 child-b parent-d child-c
1314 private RelationsCommonList updateRelations(
1315 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate)
1317 if (logger.isTraceEnabled()) {
1318 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1320 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1322 return null; //nothing to do--they didn't send a list of relations.
1324 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1325 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1326 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1327 List<RelationsCommonList.RelationListItem> childList = null;
1328 List<RelationsCommonList.RelationListItem> parentList = null;
1329 DocumentModel docModel = wrapDoc.getWrappedObject();
1330 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1331 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1333 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1334 //Do magic replacement of ${itemCSID} and fix URI's.
1335 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1337 final String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1338 UriInfo uriInfo = ctx.getUriInfo();
1339 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1342 //Run getList() once as sent to get childListOuter:
1343 String predicate = RelationshipType.HAS_BROADER.value();
1344 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1345 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1346 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1347 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1348 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1350 RelationResource relationResource = new RelationResource();
1351 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1353 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1354 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1355 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1356 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1357 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1360 childList = childListOuter.getRelationListItem();
1361 parentList = parentListOuter.getRelationListItem();
1363 if (parentList.size() > 1) {
1364 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1367 if (logger.isTraceEnabled()) {
1368 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1372 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1373 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1374 // and so the CSID for those may be null
1375 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1376 // Look for parents and children
1377 if(itemCSID.equals(inboundItem.getObject().getCsid())
1378 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1379 //then this is an item that says we have a child. That child is inboundItem
1380 RelationsCommonList.RelationListItem childItem =
1381 (childList == null) ? null : findInList(childList, inboundItem);
1382 if (childItem != null) {
1383 if (logger.isTraceEnabled()) {
1384 StringBuilder sb = new StringBuilder();
1385 itemToString(sb, "== Child: ", childItem);
1386 logger.trace("Found inboundChild in current child list: " + sb.toString());
1388 removeFromList(childList, childItem); //exists, just take it off delete list
1390 if (logger.isTraceEnabled()) {
1391 StringBuilder sb = new StringBuilder();
1392 itemToString(sb, "== Child: ", inboundItem);
1393 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1395 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1396 String newChildCsid = inboundItem.getSubject().getCsid();
1397 if(newChildCsid == null) {
1398 String newChildRefName = inboundItem.getSubject().getRefName();
1399 if (newChildRefName == null) {
1400 throw new RuntimeException("Child with no CSID or refName!");
1402 if (logger.isTraceEnabled()) {
1403 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1405 DocumentModel newChildDocModel =
1406 NuxeoBasedResource.getDocModelForRefName(getServiceContext(),
1407 newChildRefName, getServiceContext().getResourceMap());
1408 newChildCsid = getCsid(newChildDocModel);
1410 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1413 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1414 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1415 //then this is an item that says we have a parent. inboundItem is that parent.
1416 RelationsCommonList.RelationListItem parentItem =
1417 (parentList == null) ? null : findInList(parentList, inboundItem);
1418 if (parentItem != null) {
1419 removeFromList(parentList, parentItem); //exists, just take it off delete list
1421 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1424 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1427 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1430 if (logger.isTraceEnabled()) {
1431 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1432 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1435 if (logger.isTraceEnabled()) {
1436 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1437 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1439 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1440 deleteRelations(childList, ctx, "childList");
1442 if (logger.isTraceEnabled()) {
1443 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1444 + actionList.size() + " new parents and children.");
1446 createRelations(actionList, ctx);
1447 if (logger.isTraceEnabled()) {
1448 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1450 // We return all elements on the inbound list, since we have just worked to make them exist in the system
1451 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1452 return relationsCommonListBody;
1455 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1456 * and sets URI correctly for related items.
1457 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1459 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1460 List<RelationsCommonList.RelationListItem> inboundList,
1461 DocumentModel docModel,
1462 String itemCSID) throws Exception {
1463 String thisURI = this.getUri(docModel);
1464 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1465 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1466 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1467 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1468 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1470 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1471 inboundItem.setObjectCsid(itemCSID);
1472 inboundItemObject.setCsid(itemCSID);
1473 //inboundItemObject.setUri(getUri(docModel));
1476 String objectCsid = inboundItemObject.getCsid();
1477 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1478 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1479 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1480 inboundItemObject.setUri(uri); //CSPACE-4037
1483 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1485 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1486 inboundItem.setSubjectCsid(itemCSID);
1487 inboundItemSubject.setCsid(itemCSID);
1488 //inboundItemSubject.setUri(getUri(docModel));
1491 String subjectCsid = inboundItemSubject.getCsid();
1492 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1493 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1494 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1495 inboundItemSubject.setUri(uri); //CSPACE-4037
1498 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1503 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1504 MultivaluedMap<String, String> queryParams, String childCSID) {
1505 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1506 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1507 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1508 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1510 RelationResource relationResource = new RelationResource();
1511 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1512 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1513 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1514 deleteRelations(parentList, ctx, "parentList-delete");
1517 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1518 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1521 for (RelationsCommonList.RelationListItem item : list) {
1522 RelationResource relationResource = new RelationResource();
1523 if(logger.isTraceEnabled()) {
1524 StringBuilder sb = new StringBuilder();
1525 itemToString(sb, "==== TO DELETE: ", item);
1526 logger.trace(sb.toString());
1528 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1529 if (logger.isDebugEnabled()) {
1530 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1533 } catch (Throwable t) {
1534 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1539 // Note that we must do this after we have completed the Update, so that the repository has the
1540 // info for the item itself. The relations code must call into the repo to get info for each end.
1541 // This could be optimized to pass in the parent docModel, since it will often be one end.
1542 // Nevertheless, we should complete the item save before we do work on the relations, especially
1543 // since a save on Create might fail, and we would not want to create relations for something
1544 // that may not be created...
1545 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate) throws Exception {
1546 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1547 PoxPayloadIn input = ctx.getInput();
1548 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1549 String itemCsid = documentModel.getName();
1551 //Updates relations part
1552 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, forUpdate);
1554 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
1555 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1557 //now we add part for relations list
1558 //ServiceContext ctx = getServiceContext();
1559 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1560 ctx.getOutput().addPart(payloadOutputPart);
1564 * Checks to see if the refName has changed, and if so,
1565 * uses utilities to find all references and update them to use the new refName.
1568 protected void handleRefNameReferencesUpdate() throws Exception {
1569 if (hasRefNameUpdate() == true) {
1570 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1571 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1572 CoreSessionInterface repoSession = this.getRepositorySession();
1574 // Update all the relationship records that referred to the old refName
1575 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1576 oldRefNameOnUpdate, newRefNameOnUpdate);
1580 protected String getRefNameUpdate() {
1581 String result = null;
1583 if (hasRefNameUpdate() == true) {
1584 result = newRefNameOnUpdate;
1585 if (logger.isDebugEnabled() == true) {
1586 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1587 newRefNameOnUpdate, oldRefNameOnUpdate));
1595 * Note: The Vocabulary document handler overrides this method.
1597 protected String getRefPropName() {
1598 return ServiceBindingUtils.AUTH_REF_PROP;