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.net.HttpURLConnection;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
33 import java.util.Map.Entry;
36 import javax.ws.rs.core.MediaType;
37 import javax.ws.rs.core.MultivaluedMap;
38 import javax.ws.rs.core.Response;
39 import javax.ws.rs.core.UriInfo;
40 import javax.xml.bind.JAXBElement;
42 import org.collectionspace.authentication.AuthN;
43 import org.collectionspace.services.authorization.AccountPermission;
44 import org.collectionspace.services.jaxb.AbstractCommonList;
45 import org.collectionspace.services.lifecycle.TransitionDef;
46 import org.collectionspace.services.client.AccountClient;
47 import org.collectionspace.services.client.CollectionSpaceClient;
48 import org.collectionspace.services.client.PayloadInputPart;
49 import org.collectionspace.services.client.PayloadOutputPart;
50 import org.collectionspace.services.client.PoxPayloadIn;
51 import org.collectionspace.services.client.PoxPayloadOut;
52 import org.collectionspace.services.client.Profiler;
53 import org.collectionspace.services.client.RelationClient;
54 import org.collectionspace.services.client.workflow.WorkflowClient;
55 import org.collectionspace.services.common.CSWebApplicationException;
56 import org.collectionspace.services.common.NuxeoBasedResource;
57 import org.collectionspace.services.common.ServiceException;
58 import org.collectionspace.services.common.authorityref.AuthorityRefList;
59 import org.collectionspace.services.common.config.ServiceConfigUtils;
60 import org.collectionspace.services.common.context.JaxRsContext;
61 import org.collectionspace.services.common.context.MultipartServiceContext;
62 import org.collectionspace.services.common.context.ServiceBindingUtils;
63 import org.collectionspace.services.common.context.ServiceContext;
64 import org.collectionspace.services.common.document.BadRequestException;
65 import org.collectionspace.services.common.document.DocumentException;
66 import org.collectionspace.services.common.document.DocumentUtils;
67 import org.collectionspace.services.common.document.DocumentWrapper;
68 import org.collectionspace.services.common.document.DocumentFilter;
69 import org.collectionspace.services.client.IRelationsManager;
70 import org.collectionspace.services.common.relation.RelationResource;
71 import org.collectionspace.services.common.repository.RepositoryClient;
72 import org.collectionspace.services.common.security.SecurityUtils;
73 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
74 import org.collectionspace.services.common.api.CommonAPI;
75 import org.collectionspace.services.common.api.RefNameUtils;
76 import org.collectionspace.services.common.api.Tools;
77 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
78 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
79 import org.collectionspace.services.config.service.DocHandlerParams;
80 import org.collectionspace.services.config.service.ListResultField;
81 import org.collectionspace.services.config.service.ObjectPartType;
82 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
83 import org.collectionspace.services.relation.RelationsCommon;
84 import org.collectionspace.services.relation.RelationsCommonList;
85 import org.collectionspace.services.relation.RelationsDocListItem;
86 import org.collectionspace.services.relation.RelationshipType;
88 import org.dom4j.Element;
89 import org.nuxeo.ecm.core.api.DocumentModel;
90 import org.nuxeo.ecm.core.api.DocumentModelList;
91 import org.nuxeo.ecm.core.api.impl.DataModelImpl;
92 import org.nuxeo.ecm.core.api.model.DocumentPart;
93 import org.nuxeo.ecm.core.api.model.Property;
94 import org.nuxeo.ecm.core.api.model.PropertyException;
95 import org.nuxeo.ecm.core.api.model.impl.ScalarProperty;
96 import org.slf4j.Logger;
97 import org.slf4j.LoggerFactory;
100 * RemoteDocumentModelHandler
102 * $LastChangedRevision: $
103 * $LastChangedDate: $
107 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
108 extends DocumentModelHandler<T, TL> {
111 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
112 private final static String CR = "\r\n";
113 private final static String EMPTYSTR = "";
114 private static final String COLLECTIONSPACE_CORE_SCHEMA = CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA;
115 private static final String ACCOUNT_PERMISSION_COMMON_PART_NAME = AccountClient.SERVICE_COMMON_PART_NAME;
118 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
121 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
122 if (ctx instanceof MultipartServiceContext) {
123 super.setServiceContext(ctx);
125 throw new IllegalArgumentException("setServiceContext requires instance of "
126 + MultipartServiceContext.class.getName());
131 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
132 return getRefnameDisplayName(docWrapper.getWrappedObject());
135 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
136 String result = null;
137 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
139 DocHandlerParams.Params params = null;
141 params = ServiceConfigUtils.getDocHandlerParams(ctx);
142 ListResultField field = params.getRefnameDisplayNameField();
144 String schema = field.getSchema();
145 if (schema == null || schema.trim().isEmpty()) {
146 schema = ctx.getCommonPartLabel();
149 result = getStringValue(docModel, schema, field);
150 } catch (Exception e) {
151 if (logger.isWarnEnabled()) {
152 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
160 public boolean supportsHierarchy() {
161 boolean result = false;
163 DocHandlerParams.Params params = null;
165 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
166 params = ServiceConfigUtils.getDocHandlerParams(ctx);
167 Boolean bool = params.isSupportsHierarchy();
169 result = bool.booleanValue();
171 } catch (DocumentException e) {
172 // TODO Auto-generated catch block
173 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
174 if (logger.isWarnEnabled() == true) {
183 public boolean supportsVersioning() {
184 boolean result = false;
186 DocHandlerParams.Params params = null;
188 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
189 params = ServiceConfigUtils.getDocHandlerParams(ctx);
190 Boolean bool = params.isSupportsVersioning();
192 result = bool.booleanValue();
194 } catch (DocumentException e) {
195 // TODO Auto-generated catch block
196 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
197 if (logger.isWarnEnabled() == true) {
206 public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
208 // Do nothing by default, but children can override if they want. The real workflow transition happens in the WorkflowDocumentModelHandler class
212 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
213 super.completeCreate(wrapDoc);
214 if (supportsHierarchy() == true) {
215 handleRelationsPayload(wrapDoc, false);
220 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
221 DocumentModel docModel = wrapDoc.getWrappedObject();
222 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
224 if (docModel.getCurrentLifeCycleState().contains(workflowState) == true) {
225 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
226 "Cannot UPDATE a resource/record if it is in the workflow state: " + workflowState);
228 super.handleUpdate(wrapDoc);
233 public boolean handleDelete(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
234 DocumentModel docModel = wrapDoc.getWrappedObject();
235 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
237 if (docModel.getCurrentLifeCycleState().contains(workflowState) == true) {
238 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
239 "Cannot DELETE a resource/record if it is in the workflow state: " + workflowState);
241 return super.handleDelete(wrapDoc);
245 /* NOTE: The authority item doc handler overrides (after calling) this method. It performs refName updates. In this
246 * method we just update any and all relationship records that use refNames that have changed.
248 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
251 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
252 DocumentModel docModel = wrapDoc.getWrappedObject();
254 String[] schemas = docModel.getDeclaredSchemas();
255 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
256 for (String schema : schemas) {
257 ObjectPartType partMeta = partsMetaMap.get(schema);
258 if (partMeta == null) {
259 continue; // unknown part, ignore
261 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
262 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
263 addExtraCoreValues(docModel, unQObjectProperties);
265 addOutputPart(unQObjectProperties, schema, partMeta);
269 // If the resource's service supports hierarchy then we need to perform a little more work
271 if (supportsHierarchy() == true) {
272 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
273 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
278 * Adds the output part.
280 * @param unQObjectProperties the un q object properties
281 * @param schema the schema
282 * @param partMeta the part meta
283 * @throws Exception the exception
284 * MediaType.APPLICATION_XML_TYPE
286 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
288 Element doc = DocumentUtils.buildDocument(partMeta, schema,
289 unQObjectProperties);
290 if (logger.isTraceEnabled() == true) {
291 logger.trace(doc.asXML());
293 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
294 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
298 * Extract paging info.
300 * @param commonsList the commons list
302 * @throws Exception the exception
304 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
306 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
308 DocumentFilter docFilter = this.getDocumentFilter();
309 long pageSize = docFilter.getPageSize();
310 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
311 // set the page size and page number
312 commonList.setPageNum(pageNum);
313 commonList.setPageSize(pageSize);
314 DocumentModelList docList = wrapDoc.getWrappedObject();
315 // Set num of items in list. this is useful to our testing framework.
316 commonList.setItemsInPage(docList.size());
317 // set the total result size
318 commonList.setTotalItems(docList.totalSize());
320 return (TL) commonList;
324 * Extract paging info.
326 * @param commonsList the commons list
328 * @throws Exception the exception
330 public TL extractPagingInfo(TL theCommonList, DocumentModelList docList)
332 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
334 DocumentFilter docFilter = this.getDocumentFilter();
335 long pageSize = docFilter.getPageSize();
336 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
337 // set the page size and page number
338 commonList.setPageNum(pageNum);
339 commonList.setPageSize(pageSize);
340 // Set num of items in list. this is useful to our testing framework.
341 commonList.setItemsInPage(docList.size());
342 // set the total result size
343 commonList.setTotalItems(docList.totalSize());
345 return (TL) commonList;
349 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
352 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
355 DocumentModel docModel = wrapDoc.getWrappedObject();
356 String[] schemas = docModel.getDeclaredSchemas();
357 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
358 for (String schema : schemas) {
359 ObjectPartType partMeta = partsMetaMap.get(schema);
360 if (partMeta == null) {
361 continue; // unknown part, ignore
363 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
364 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
365 addExtraCoreValues(docModel, unQObjectProperties);
367 addOutputPart(unQObjectProperties, schema, partMeta);
370 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
372 if (supportsHierarchy() == true) {
373 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
374 if (Tools.isTrue(showSiblings)) {
375 showSiblings(wrapDoc, ctx);
376 return; // actual result is returned on ctx.addOutputPart();
379 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
380 if (Tools.isTrue(showRelations)) {
381 showRelations(wrapDoc, ctx);
382 return; // actual result is returned on ctx.addOutputPart();
385 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
386 if (Tools.isTrue(showAllRelations)) {
387 showAllRelations(wrapDoc, ctx);
388 return; // actual result is returned on ctx.addOutputPart();
392 String currentUser = ctx.getUserId();
393 if (currentUser.equalsIgnoreCase(AuthN.ANONYMOUS_USER) == false &&
394 currentUser.equalsIgnoreCase(AuthN.SPRING_ADMIN_USER) == false) {
395 addAccountPermissionsPart();
399 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
401 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
404 private void addAccountPermissionsPart() throws Exception {
405 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
408 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
409 String currentServiceName = ctx.getServiceName();
410 String workflowSubResource = "/";
411 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
412 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
413 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
414 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
416 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
418 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
419 currentServiceName, workflowSubResource);
420 org.collectionspace.services.authorization.ObjectFactory objectFactory =
421 new org.collectionspace.services.authorization.ObjectFactory();
422 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
423 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
424 ctx.addOutputPart(accountPermissionPart);
430 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
433 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
435 //TODO filling extension parts should be dynamic
436 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
437 //not an ideal way of populating objects.
438 DocumentModel docModel = wrapDoc.getWrappedObject();
439 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
440 PoxPayloadIn input = ctx.getInput();
441 if (input == null || input.getParts().isEmpty()) {
442 String msg = String.format("No payload found for '%s' action.", action);
443 logger.error(msg + "Ctx=" + getServiceContext().toString());
444 throw new BadRequestException(msg);
447 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
449 //iterate over parts received and fill those parts
450 boolean werePartsFilled = false;
451 List<PayloadInputPart> inputParts = input.getParts();
452 for (PayloadInputPart part : inputParts) {
454 String partLabel = part.getLabel();
455 if (partLabel == null) {
456 String msg = "Part label is missing or empty!";
457 logger.error(msg + "Ctx=" + getServiceContext().toString());
458 throw new BadRequestException(msg);
461 //skip if the part is not in metadata or if it is a system part
462 ObjectPartType partMeta = partsMetaMap.get(partLabel);
463 if (partMeta == null || isSystemPart(partLabel)) {
466 fillPart(part, docModel, partMeta, action, ctx);
467 werePartsFilled = true;
470 if (logger.isTraceEnabled() && werePartsFilled == false) {
471 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.",
477 private boolean isSystemPart(String partLabel) {
478 boolean result = false;
480 if (partLabel != null && (partLabel.equalsIgnoreCase(COLLECTIONSPACE_CORE_SCHEMA) ||
481 partLabel.equalsIgnoreCase(ACCOUNT_PERMISSION_COMMON_PART_NAME))) {
489 * fillPart fills an XML part into given document model
490 * @param part to fill
491 * @param docModel for the given object
492 * @param partMeta metadata for the object to fill
495 protected void fillPart(PayloadInputPart part, DocumentModel docModel, ObjectPartType partMeta, Action action,
496 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
497 // check if this is an xml part
498 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
499 Element element = part.getElementBody();
500 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
501 if (action == Action.UPDATE) {
502 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
504 String schemaName = partMeta.getLabel();
505 docModel.setProperties(schemaName, objectProps);
506 Set<String> fieldNameSet = getFieldNamesSet(objectProps);
507 setFieldsDirty(docModel, schemaName, fieldNameSet); // Force Nuxeo to update EVERY field by marking them dirty/modified
513 * Due to an apparent Nuxeo bug, we need to explicitly mark all fields as dirty for updates
514 * to take place properly. See https://issues.collectionspace.org/browse/CSPACE-7124
516 * We should improve this code by marking dirty/modified only the fields that were part of the UPDATE/PUT request
517 * payload. The current code will mark any field with a name in the 'fieldNameSet' set. Since field names do not
518 * have to be unique in document/record, we might end up unnecessarily marking some fields as modified -not ideal but
519 * better than brute-force marking ALL fields as modified.
524 private void setFieldsDirty(DocumentModel docModel, String schemaName, Set<String> fieldNameSet) {
525 DataModelImpl dataModel = (DataModelImpl) docModel.getDataModel(schemaName);
526 DocumentPart documentPart = dataModel.getDocumentPart();
527 setFieldsDirty(documentPart.getChildren(), fieldNameSet);
531 * Recursively step through all the children and sub-children setting their dirty flags.
532 * See https://issues.collectionspace.org/browse/CSPACE-7124
535 private void setFieldsDirty(Collection<Property> children, Set<String> fieldNameSet) {
536 if (children != null && (children.size() > 0)) {
537 for (Property prop : children ) {
538 String propName = prop.getName();
539 logger.debug(propName);
540 if (prop.isPhantom() == false) {
541 if (prop.isScalar() == false) {
542 setFieldsDirty(prop.getChildren(), fieldNameSet);
543 } else if (fieldNameSet.contains(propName)) {
544 ScalarProperty scalarProp = (ScalarProperty)prop;
545 scalarProp.setIsModified();
553 * Gets the set of field names used in a map of properties. We'll use this make our workaround to a
554 * bug (See https://issues.collectionspace.org/browse/CSPACE-7124) more efficient by only marking fields
555 * with names in this set as modified -ie, needing persisting.
557 * Since the map of properties can contain other maps of properties as values, there can be duplicate
558 * field names in the 'objectProps' map. And since we're creating a set, there can be no duplicates in the result.
561 private Set<String> getFieldNamesSet(Map<String, Object> objectProps) {
562 HashSet<String> result = new HashSet<String>();
564 addFieldNamesToSet(result, objectProps);
569 private void addFieldNamesToSet(Set<String> fieldNameSet, List<Object> valueList) {
570 for (Object value : valueList) {
571 if (value instanceof List) {
572 addFieldNamesToSet(fieldNameSet, (List<Object>)value);
573 } else if (value instanceof Map) {
574 addFieldNamesToSet(fieldNameSet, (Map<String, Object>)value);
576 logger.debug(value.toString());
581 private void addFieldNamesToSet(Set<String> fieldNameSet, Map<String, Object> objectProps) {
582 for (String key : objectProps.keySet()) {
583 Object value = objectProps.get(key);
584 if (value instanceof Map) {
585 addFieldNamesToSet(fieldNameSet, (Map<String, Object>)value);
586 } else if (value instanceof List) {
587 addFieldNamesToSet(fieldNameSet, (List)value);
589 fieldNameSet.add(key);
595 * Filters out read only properties, so they cannot be set on update.
596 * TODO: add configuration support to do this generally
597 * @param objectProps the properties parsed from the update payload
598 * @param partMeta metadata for the object to fill
600 public void filterReadOnlyPropertiesForPart(
601 Map<String, Object> objectProps, ObjectPartType partMeta) {
602 // Should add in logic to filter most of the core items on update
603 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
604 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
605 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
606 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
607 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
608 // Note that the updatedAt/updatedBy fields are set internally
609 // in DocumentModelHandler.handleCoreValues().
614 * extractPart extracts an XML object from given DocumentModel
616 * @param schema of the object to extract
617 * @param partMeta metadata for the object to extract
620 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
622 return extractPart(docModel, schema, (Map<String, Object>)null);
626 * extractPart extracts an XML object from given DocumentModel
628 * @param schema of the object to extract
629 * @param partMeta metadata for the object to extract
633 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
635 return extractPart(docModel, schema, partMeta, null);
639 * extractPart extracts an XML object from given DocumentModel
641 * @param schema of the object to extract
642 * @param partMeta metadata for the object to extract
645 protected Map<String, Object> extractPart(
646 DocumentModel docModel,
648 Map<String, Object> addToMap)
650 Map<String, Object> result = null;
652 Map<String, Object> objectProps = docModel.getProperties(schema);
653 if (objectProps != null) {
654 //unqualify properties before sending the doc over the wire (to save bandwidh)
655 //FIXME: is there a better way to avoid duplication of a Map/Collection?
656 Map<String, Object> unQObjectProperties =
657 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
658 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
659 for (Entry<String, Object> entry : qualifiedEntries) {
660 String unqProp = getUnQProperty(entry.getKey());
661 unQObjectProperties.put(unqProp, entry.getValue());
663 result = unQObjectProperties;
670 * extractPart extracts an XML object from given DocumentModel
672 * @param schema of the object to extract
673 * @param partMeta metadata for the object to extract
677 protected Map<String, Object> extractPart(
678 DocumentModel docModel, String schema, ObjectPartType partMeta,
679 Map<String, Object> addToMap)
681 Map<String, Object> result = null;
683 result = this.extractPart(docModel, schema, addToMap);
689 public String getStringPropertyFromDoc(
692 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
693 RepositoryInstance repoSession = null;
694 boolean releaseRepoSession = false;
695 String returnValue = null;
698 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
699 repoSession = this.getRepositorySession();
700 if (repoSession == null) {
701 repoSession = repoClient.getRepositorySession();
702 releaseRepoSession = true;
706 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
707 DocumentModel docModel = wrapper.getWrappedObject();
708 returnValue = (String) docModel.getPropertyValue(propertyXPath);
709 } catch (PropertyException pe) {
711 } catch (DocumentException de) {
713 } catch (Exception e) {
714 if (logger.isDebugEnabled()) {
715 logger.debug("Caught exception ", e);
717 throw new DocumentException(e);
719 if (releaseRepoSession && repoSession != null) {
720 repoClient.releaseRepositorySession(repoSession);
723 } catch (Exception e) {
724 if (logger.isDebugEnabled()) {
725 logger.debug("Caught exception ", e);
727 throw new DocumentException(e);
731 if (logger.isWarnEnabled() == true) {
732 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
741 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
744 public AuthorityRefList getAuthorityRefs(
746 List<AuthRefConfigInfo> authRefConfigInfoList) throws PropertyException, Exception {
748 AuthorityRefList authRefList = new AuthorityRefList();
749 AbstractCommonList commonList = (AbstractCommonList) authRefList;
751 DocumentFilter docFilter = this.getDocumentFilter();
752 long pageSize = docFilter.getPageSize();
753 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
754 // set the page size and page number
755 commonList.setPageNum(pageNum);
756 commonList.setPageSize(pageSize);
758 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
761 int iFirstToUse = (int)(pageSize*pageNum);
762 int nFoundInPage = 0;
765 ArrayList<RefNameServiceUtils.AuthRefInfo> foundReferences
766 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
768 boolean releaseRepoSession = false;
769 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
770 NuxeoRepositoryClientImpl repoClient = (NuxeoRepositoryClientImpl)this.getRepositoryClient(ctx);
771 CoreSessionInterface repoSession = this.getRepositorySession();
772 if (repoSession == null) {
773 repoSession = repoClient.getRepositorySession(ctx);
774 releaseRepoSession = true;
775 this.setRepositorySession(repoSession); // we (the doc handler) should keep track of this repository session in case we need it
779 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
780 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefConfigInfoList, null, foundReferences);
781 // Slightly goofy pagination support - how many refs do we expect from one object?
782 for(RefNameServiceUtils.AuthRefInfo ari:foundReferences) {
783 if ((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
784 if(appendToAuthRefsList(ari, list)) {
793 if (releaseRepoSession == true) {
794 repoClient.releaseRepositorySession(ctx, repoSession);
798 // Set num of items in list. this is useful to our testing framework.
799 commonList.setItemsInPage(nFoundInPage);
800 // set the total result size
801 commonList.setTotalItems(nFoundTotal);
803 } catch (PropertyException pe) {
804 String msg = "Attempted to retrieve value for invalid or missing authority field. "
805 + "Check authority field properties in tenant bindings.";
806 logger.warn(msg, pe);
808 } catch (Exception e) {
809 if (logger.isDebugEnabled()) {
810 logger.debug("Caught exception in getAuthorityRefs", e);
812 Response response = Response.status(
813 Response.Status.INTERNAL_SERVER_ERROR).entity(
814 "Failed to retrieve authority references").type(
815 "text/plain").build();
816 throw new CSWebApplicationException(e, response);
822 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
823 List<AuthorityRefList.AuthorityRefItem> list)
825 String fieldName = ari.getQualifiedDisplayName();
827 String refNameValue = (String)ari.getProperty().getValue();
828 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
829 if(item!=null) { // ignore garbage values.
833 } catch(PropertyException pe) {
834 String msg = "PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage();
835 if (logger.isDebugEnabled()) {
836 logger.debug(msg, pe);
845 * Fill in all the values to be returned in the authrefs payload for this item.
847 * @param authRefFieldName
851 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
853 // Find the CSID for the authority item
857 DocumentModel docModel = NuxeoUtils.getDocModelForRefName(getServiceContext(), refName, getServiceContext().getResourceMap());
858 csid = NuxeoUtils.getCsid(docModel);
859 } catch (Exception e1) {
860 String msg = String.format("Could not find CSID for authority reference with refname = %s.", refName);
861 if (logger.isDebugEnabled()) {
862 logger.debug(msg, e1);
868 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
870 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
871 if (Tools.isEmpty(csid) == false) {
872 ilistItem.setCsid(csid);
874 ilistItem.setRefName(refName);
875 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
876 ilistItem.setItemDisplayName(termInfo.displayName);
877 ilistItem.setSourceField(authRefFieldName);
878 ilistItem.setUri(termInfo.getRelativeUri());
879 } catch (Exception e) {
881 String msg = String.format("Trouble parsing refName from value: %s in field: %s. Error message: %s.",
882 refName, authRefFieldName, e.getLocalizedMessage());
883 if (logger.isDebugEnabled()) {
884 logger.debug(msg, e);
894 * Returns the primary value from a list of values.
896 * Assumes that the first value is the primary value.
897 * This assumption may change when and if the primary value
898 * is identified explicitly.
900 * @param values a list of values.
901 * @param propertyName the name of a property through
902 * which the value can be extracted.
903 * @return the primary value.
904 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
905 String primaryValue = "";
906 if (values == null || values.size() == 0) {
909 Object value = values.get(0);
910 if (value instanceof String) {
912 primaryValue = (String) value;
914 // Multivalue group of fields
915 } else if (value instanceof Map) {
917 Map map = (Map) value;
918 if (map.values().size() > 0) {
919 if (map.get(propertyName) != null) {
920 primaryValue = (String) map.get(propertyName);
925 logger.warn("Unexpected type for property " + propertyName
926 + " in multivalue list: not String or Map.");
933 * Gets a simple property from the document.
935 * For completeness, as this duplicates DocumentModel method.
937 * @param docModel The document model to get info from
938 * @param schema The name of the schema (part)
939 * @param propertyName The simple scalar property type
940 * @return property value as String
942 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
943 String xpath = "/"+schema+":"+propName;
945 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
946 } catch(PropertyException pe) {
947 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
948 +pe.getLocalizedMessage());
949 } catch(ClassCastException cce) {
950 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
951 +cce.getLocalizedMessage());
952 } catch(Exception e) {
953 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
954 +e.getLocalizedMessage());
959 * Gets first of a repeating list of scalar values, as a String, from the document.
961 * @param docModel The document model to get info from
962 * @param schema The name of the schema (part)
963 * @param listName The name of the scalar list property
964 * @return first value in list, as a String, or empty string if the list is empty
966 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
967 String schema, String listName) {
968 String xpath = "/" + schema + ":" + listName + "/[0]";
970 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
971 } catch (PropertyException pe) {
972 throw new RuntimeException("Problem retrieving property {" + xpath
973 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
974 } catch (IndexOutOfBoundsException ioobe) {
975 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
976 return ""; // gracefully handle missing elements
977 } catch (ClassCastException cce) {
978 throw new RuntimeException("Problem retrieving property {" + xpath
979 + "} as String. Not a repeating String property?"
980 + cce.getLocalizedMessage());
981 } catch (Exception e) {
982 throw new RuntimeException("Unknown problem retrieving property {"
983 + xpath + "}." + e.getLocalizedMessage());
988 * Gets first of a repeating list of scalar values, as a String, from the document.
990 * @param docModel The document model to get info from
991 * @param schema The name of the schema (part)
992 * @param listName The name of the scalar list property
993 * @return first value in list, as a String, or empty string if the list is empty
995 protected String getStringValueInPrimaryRepeatingComplexProperty(
996 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
997 String result = null;
999 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
1001 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
1002 } catch(PropertyException pe) {
1003 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
1004 +pe.getLocalizedMessage());
1005 } catch(IndexOutOfBoundsException ioobe) {
1006 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
1007 result = ""; // gracefully handle missing elements
1008 } catch(ClassCastException cce) {
1009 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
1010 +cce.getLocalizedMessage());
1011 } catch(NullPointerException npe) {
1012 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
1013 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
1015 } catch(Exception e) {
1016 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
1017 +e.getLocalizedMessage());
1024 * Gets XPath value from schema. Note that only "/" and "[n]" are
1025 * supported for xpath. Can omit grouping elements for repeating complex types,
1026 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
1027 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
1028 * If there are no entries for a list of scalars or for a list of complex types,
1029 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
1030 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
1031 * that many elements in the list.
1032 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
1034 * @param docModel The document model to get info from
1035 * @param schema The name of the schema (part)
1036 * @param xpath The XPath expression (without schema prefix)
1037 * @return value the indicated property value as a String
1038 * @throws DocumentException
1040 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
1041 String schema, ListResultField field) throws DocumentException {
1042 Object result = null;
1044 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
1049 protected String getStringValue(DocumentModel docModel,
1050 String schema, ListResultField field) throws DocumentException {
1051 String result = null;
1053 Object value = getListResultValue(docModel, schema, field);
1054 if (value != null && value instanceof String) {
1055 String strValue = (String) value;
1056 if (strValue.trim().isEmpty() == false) {
1064 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1068 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
1070 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
1072 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
1074 sb.append(item.getPredicate());
1076 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
1080 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
1081 StringBuilder sb = new StringBuilder();
1083 if (list.size() > 0) {
1084 sb.append("=========== " + label + " ==========" + CR);
1086 for (RelationsCommonList.RelationListItem item : list) {
1087 itemToString(sb, "== ", item);
1090 return sb.toString();
1093 /** @return null on parent not found
1095 protected String getParentCSID(String thisCSID) throws Exception {
1096 String parentCSID = null;
1098 String predicate = RelationshipType.HAS_BROADER.value();
1099 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1100 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1101 if (parentList != null) {
1102 if (parentList.size() == 0) {
1105 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
1106 parentCSID = relationListItem.getObjectCsid();
1109 } catch (Exception e) {
1110 logger.error("Could not find parent for this: " + thisCSID, e);
1115 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
1116 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
1120 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
1121 MultipartServiceContext ctx) throws Exception {
1122 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1123 String parentCSID = getParentCSID(thisCSID);
1124 if (parentCSID == null) {
1125 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
1129 String predicate = RelationshipType.HAS_BROADER.value();
1130 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
1131 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
1133 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
1136 RelationsCommonList.RelationListItem item = null;
1137 for (RelationsCommonList.RelationListItem sibling : siblingList) {
1138 if (thisCSID.equals(sibling.getSubjectCsid())) {
1139 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.
1142 //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.
1143 for (RelationsCommonList.RelationListItem self : toRemoveList) {
1144 removeFromList(siblingList, self);
1147 long siblingSize = siblingList.size();
1148 siblingListOuter.setTotalItems(siblingSize);
1149 siblingListOuter.setItemsInPage(siblingSize);
1150 if(logger.isTraceEnabled()) {
1151 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
1152 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1155 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
1156 ctx.addOutputPart(relationsPart);
1159 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
1160 MultipartServiceContext ctx) throws Exception {
1161 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1163 String predicate = RelationshipType.HAS_BROADER.value();
1164 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1165 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1167 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
1168 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
1170 if(logger.isTraceEnabled()) {
1171 String dump = dumpLists(thisCSID, parentList, childrenList, null);
1172 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1175 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
1176 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
1177 //Not optimal, but that's the current design spec.
1179 for (RelationsCommonList.RelationListItem parent : parentList) {
1180 childrenList.add(parent);
1183 long childrenSize = childrenList.size();
1184 childrenListOuter.setTotalItems(childrenSize);
1185 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1187 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1188 ctx.addOutputPart(relationsPart);
1191 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1192 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1194 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1195 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1197 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1198 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1200 if(logger.isTraceEnabled()) {
1201 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1202 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1205 subjectList.addAll(objectList);
1207 //now subjectList actually has records BOTH where thisCSID is subject and object.
1208 long relatedSize = subjectList.size();
1209 subjectListOuter.setTotalItems(relatedSize);
1210 subjectListOuter.setItemsInPage(relatedSize);
1212 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1213 ctx.addOutputPart(relationsPart);
1216 private String dumpLists(String itemCSID,
1217 List<RelationsCommonList.RelationListItem> parentList,
1218 List<RelationsCommonList.RelationListItem> childList,
1219 List<RelationsCommonList.RelationListItem> actionList) {
1220 StringBuilder sb = new StringBuilder();
1221 sb.append("itemCSID: " + itemCSID + CR);
1222 if(parentList!=null) {
1223 sb.append(dumpList(parentList, "parentList"));
1225 if(childList!=null) {
1226 sb.append(dumpList(childList, "childList"));
1228 if(actionList!=null) {
1229 sb.append(dumpList(actionList, "actionList"));
1231 return sb.toString();
1234 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1235 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1236 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1237 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1238 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1239 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1240 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1242 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1243 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1244 return relationsCommonList;
1246 //============================= END TODO refactor ==========================
1248 // this method calls the RelationResource to have it create the relations and persist them.
1249 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1250 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1251 for (RelationsCommonList.RelationListItem item : inboundList) {
1252 RelationsCommon rc = new RelationsCommon();
1253 //rc.setCsid(item.getCsid());
1254 //todo: assignTo(item, rc);
1255 RelationsDocListItem itemSubject = item.getSubject();
1256 RelationsDocListItem itemObject = item.getObject();
1258 // Set at least one of CSID and refName for Subject and Object
1259 // Either value might be null for for each of Subject and Object
1260 String subjectCsid = itemSubject.getCsid();
1261 rc.setSubjectCsid(subjectCsid);
1263 String objCsid = itemObject.getCsid();
1264 rc.setObjectCsid(objCsid);
1266 rc.setSubjectRefName(itemSubject.getRefName());
1267 rc.setObjectRefName(itemObject.getRefName());
1269 rc.setRelationshipType(item.getPredicate());
1270 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1271 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1272 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1274 // This is superfluous, since it will be fetched by the Relations Create logic.
1275 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1276 rc.setObjectDocumentType(itemObject.getDocumentType());
1278 // This is superfluous, since it will be fetched by the Relations Create logic.
1279 rc.setSubjectUri(itemSubject.getUri());
1280 rc.setObjectUri(itemObject.getUri());
1281 // May not have the info here. Only really require CSID or refName.
1282 // Rest is handled in the Relation create mechanism
1283 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1285 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1286 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1287 payloadOut.addPart(outputPart);
1288 RelationResource relationResource = new RelationResource();
1289 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1290 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1294 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1295 // But item1 must not be sparse
1296 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1297 if (item1 == null || item2 == null) {
1300 RelationsDocListItem subj1 = item1.getSubject();
1301 RelationsDocListItem subj2 = item2.getSubject();
1302 RelationsDocListItem obj1 = item1.getObject();
1303 RelationsDocListItem obj2 = item2.getObject();
1305 String subj1Csid = subj1.getCsid();
1306 String subj2Csid = subj2.getCsid();
1307 String subj1RefName = subj1.getRefName();
1308 String subj2RefName = subj2.getRefName();
1310 String obj1Csid = obj1.getCsid();
1311 String obj2Csid = obj2.getCsid();
1312 String obj1RefName = obj1.getRefName();
1313 String obj2RefName = obj2.getRefName();
1315 String item1Metatype = item1.getRelationshipMetaType();
1316 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1318 String item2Metatype = item2.getRelationshipMetaType();
1319 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1321 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1322 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1323 // predicate is proper, but still allow relationshipType
1324 && (item1.getPredicate().equals(item2.getPredicate())
1325 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1326 // Allow missing docTypes, so long as they do not conflict
1327 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1328 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1329 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1333 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1334 // But the list items must not be sparse
1335 private RelationsCommonList.RelationListItem findInList(
1336 List<RelationsCommonList.RelationListItem> list,
1337 RelationsCommonList.RelationListItem item) {
1338 RelationsCommonList.RelationListItem foundItem = null;
1339 for (RelationsCommonList.RelationListItem listItem : list) {
1340 if (itemsEqual(listItem, item)) { //equals must be defined, else
1341 foundItem = listItem;
1348 /** updateRelations strategy:
1351 go through inboundList, remove anything from childList that matches from childList
1352 go through inboundList, remove anything from parentList that matches from parentList
1353 go through parentList, delete all remaining
1354 go through childList, delete all remaining
1355 go through actionList, add all remaining.
1356 check for duplicate children
1357 check for more than one parent.
1359 inboundList parentList childList actionList
1360 ---------------- --------------- ---------------- ----------------
1361 child-a parent-c child-a child-b
1362 child-b parent-d child-c
1367 private RelationsCommonList updateRelations(
1368 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate)
1370 if (logger.isTraceEnabled()) {
1371 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1373 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1375 return null; //nothing to do--they didn't send a list of relations.
1377 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1378 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1379 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1380 List<RelationsCommonList.RelationListItem> childList = null;
1381 List<RelationsCommonList.RelationListItem> parentList = null;
1382 DocumentModel docModel = wrapDoc.getWrappedObject();
1383 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1384 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1386 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1387 //Do magic replacement of ${itemCSID} and fix URI's.
1388 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1390 final String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1391 UriInfo uriInfo = ctx.getUriInfo();
1392 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1395 //Run getList() once as sent to get childListOuter:
1396 String predicate = RelationshipType.HAS_BROADER.value();
1397 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1398 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1399 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1400 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1401 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1403 RelationResource relationResource = new RelationResource();
1404 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1406 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1407 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1408 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1409 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1410 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1413 childList = childListOuter.getRelationListItem();
1414 parentList = parentListOuter.getRelationListItem();
1416 if (parentList.size() > 1) {
1417 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1420 if (logger.isTraceEnabled()) {
1421 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1425 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1426 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1427 // and so the CSID for those may be null
1428 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1429 // Look for parents and children
1430 if(itemCSID.equals(inboundItem.getObject().getCsid())
1431 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1432 //then this is an item that says we have a child. That child is inboundItem
1433 RelationsCommonList.RelationListItem childItem =
1434 (childList == null) ? null : findInList(childList, inboundItem);
1435 if (childItem != null) {
1436 if (logger.isTraceEnabled()) {
1437 StringBuilder sb = new StringBuilder();
1438 itemToString(sb, "== Child: ", childItem);
1439 logger.trace("Found inboundChild in current child list: " + sb.toString());
1441 removeFromList(childList, childItem); //exists, just take it off delete list
1443 if (logger.isTraceEnabled()) {
1444 StringBuilder sb = new StringBuilder();
1445 itemToString(sb, "== Child: ", inboundItem);
1446 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1448 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1449 String newChildCsid = inboundItem.getSubject().getCsid();
1450 if(newChildCsid == null) {
1451 String newChildRefName = inboundItem.getSubject().getRefName();
1452 if (newChildRefName == null) {
1453 throw new RuntimeException("Child with no CSID or refName!");
1455 if (logger.isTraceEnabled()) {
1456 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1458 DocumentModel newChildDocModel =
1459 NuxeoBasedResource.getDocModelForRefName(getServiceContext(),
1460 newChildRefName, getServiceContext().getResourceMap());
1461 newChildCsid = getCsid(newChildDocModel);
1463 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1466 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1467 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1468 //then this is an item that says we have a parent. inboundItem is that parent.
1469 RelationsCommonList.RelationListItem parentItem =
1470 (parentList == null) ? null : findInList(parentList, inboundItem);
1471 if (parentItem != null) {
1472 removeFromList(parentList, parentItem); //exists, just take it off delete list
1474 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1477 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1480 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1483 if (logger.isTraceEnabled()) {
1484 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1485 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1488 if (logger.isTraceEnabled()) {
1489 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1490 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1492 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1493 deleteRelations(childList, ctx, "childList");
1495 if (logger.isTraceEnabled()) {
1496 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1497 + actionList.size() + " new parents and children.");
1499 createRelations(actionList, ctx);
1500 if (logger.isTraceEnabled()) {
1501 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1503 // We return all elements on the inbound list, since we have just worked to make them exist in the system
1504 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1505 return relationsCommonListBody;
1508 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1509 * and sets URI correctly for related items.
1510 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1512 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1513 List<RelationsCommonList.RelationListItem> inboundList,
1514 DocumentModel docModel,
1515 String itemCSID) throws Exception {
1516 String thisURI = this.getUri(docModel);
1517 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1518 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1519 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1520 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1521 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1523 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1524 inboundItem.setObjectCsid(itemCSID);
1525 inboundItemObject.setCsid(itemCSID);
1526 //inboundItemObject.setUri(getUri(docModel));
1529 String objectCsid = inboundItemObject.getCsid();
1530 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1531 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1532 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1533 inboundItemObject.setUri(uri); //CSPACE-4037
1536 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1538 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1539 inboundItem.setSubjectCsid(itemCSID);
1540 inboundItemSubject.setCsid(itemCSID);
1541 //inboundItemSubject.setUri(getUri(docModel));
1544 String subjectCsid = inboundItemSubject.getCsid();
1545 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1546 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1547 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1548 inboundItemSubject.setUri(uri); //CSPACE-4037
1551 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1556 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1557 MultivaluedMap<String, String> queryParams, String childCSID) {
1558 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1559 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1560 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1561 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1563 RelationResource relationResource = new RelationResource();
1564 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1565 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1566 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1567 deleteRelations(parentList, ctx, "parentList-delete");
1570 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1571 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1574 for (RelationsCommonList.RelationListItem item : list) {
1575 RelationResource relationResource = new RelationResource();
1576 if(logger.isTraceEnabled()) {
1577 StringBuilder sb = new StringBuilder();
1578 itemToString(sb, "==== TO DELETE: ", item);
1579 logger.trace(sb.toString());
1581 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1582 if (logger.isDebugEnabled()) {
1583 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1586 } catch (Throwable t) {
1587 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1592 // Note that we must do this after we have completed the Update, so that the repository has the
1593 // info for the item itself. The relations code must call into the repo to get info for each end.
1594 // This could be optimized to pass in the parent docModel, since it will often be one end.
1595 // Nevertheless, we should complete the item save before we do work on the relations, especially
1596 // since a save on Create might fail, and we would not want to create relations for something
1597 // that may not be created...
1598 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate) throws Exception {
1599 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1600 PoxPayloadIn input = ctx.getInput();
1601 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1602 String itemCsid = documentModel.getName();
1604 //Updates relations part
1605 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, forUpdate);
1607 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
1608 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1610 //now we add part for relations list
1611 //ServiceContext ctx = getServiceContext();
1612 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1613 ctx.getOutput().addPart(payloadOutputPart);
1617 * Checks to see if the refName has changed, and if so,
1618 * uses utilities to find all references and update them to use the new refName.
1621 protected void handleRefNameReferencesUpdate() throws Exception {
1622 if (hasRefNameUpdate() == true) {
1623 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1624 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1625 CoreSessionInterface repoSession = this.getRepositorySession();
1627 // Update all the relationship records that referred to the old refName
1628 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1629 oldRefNameOnUpdate, newRefNameOnUpdate);
1633 protected String getRefNameUpdate() {
1634 String result = null;
1636 if (hasRefNameUpdate() == true) {
1637 result = newRefNameOnUpdate;
1638 if (logger.isDebugEnabled() == true) {
1639 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1640 newRefNameOnUpdate, oldRefNameOnUpdate));
1648 * Note: The Vocabulary document handler overrides this method.
1650 protected String getRefPropName() {
1651 return ServiceBindingUtils.AUTH_REF_PROP;