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.trace(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(DocumentModel docModel,
679 ObjectPartType partMeta,
680 Map<String, Object> addToMap)
682 Map<String, Object> result = null;
684 result = this.extractPart(docModel, schema, addToMap);
690 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
693 public AuthorityRefList getAuthorityRefs(
695 List<AuthRefConfigInfo> authRefConfigInfoList) throws PropertyException, Exception {
697 AuthorityRefList authRefList = new AuthorityRefList();
698 AbstractCommonList commonList = (AbstractCommonList) authRefList;
700 DocumentFilter docFilter = this.getDocumentFilter();
701 long pageSize = docFilter.getPageSize();
702 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
703 // set the page size and page number
704 commonList.setPageNum(pageNum);
705 commonList.setPageSize(pageSize);
707 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
710 int iFirstToUse = (int)(pageSize*pageNum);
711 int nFoundInPage = 0;
714 ArrayList<RefNameServiceUtils.AuthRefInfo> foundReferences
715 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
717 boolean releaseRepoSession = false;
718 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
719 NuxeoRepositoryClientImpl repoClient = (NuxeoRepositoryClientImpl)this.getRepositoryClient(ctx);
720 CoreSessionInterface repoSession = this.getRepositorySession();
721 if (repoSession == null) {
722 repoSession = repoClient.getRepositorySession(ctx);
723 releaseRepoSession = true;
724 this.setRepositorySession(repoSession); // we (the doc handler) should keep track of this repository session in case we need it
728 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
729 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefConfigInfoList, null, foundReferences);
730 // Slightly goofy pagination support - how many refs do we expect from one object?
731 for(RefNameServiceUtils.AuthRefInfo ari:foundReferences) {
732 if ((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
733 if(appendToAuthRefsList(ari, list)) {
742 if (releaseRepoSession == true) {
743 repoClient.releaseRepositorySession(ctx, repoSession);
747 // Set num of items in list. this is useful to our testing framework.
748 commonList.setItemsInPage(nFoundInPage);
749 // set the total result size
750 commonList.setTotalItems(nFoundTotal);
752 } catch (PropertyException pe) {
753 String msg = "Attempted to retrieve value for invalid or missing authority field. "
754 + "Check authority field properties in tenant bindings.";
755 logger.warn(msg, pe);
757 } catch (Exception e) {
758 if (logger.isDebugEnabled()) {
759 logger.debug("Caught exception in getAuthorityRefs", e);
761 Response response = Response.status(
762 Response.Status.INTERNAL_SERVER_ERROR).entity(
763 "Failed to retrieve authority references").type(
764 "text/plain").build();
765 throw new CSWebApplicationException(e, response);
771 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
772 List<AuthorityRefList.AuthorityRefItem> list)
774 String fieldName = ari.getQualifiedDisplayName();
776 String refNameValue = (String)ari.getProperty().getValue();
777 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
778 if(item!=null) { // ignore garbage values.
782 } catch(PropertyException pe) {
783 String msg = "PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage();
784 if (logger.isDebugEnabled()) {
785 logger.debug(msg, pe);
794 * Fill in all the values to be returned in the authrefs payload for this item.
796 * @param authRefFieldName
800 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
802 // Find the CSID for the authority item
806 DocumentModel docModel = NuxeoUtils.getDocModelForRefName(getServiceContext(), refName, getServiceContext().getResourceMap());
807 csid = NuxeoUtils.getCsid(docModel);
808 } catch (Exception e1) {
809 String msg = String.format("Could not find CSID for authority reference with refname = %s.", refName);
810 if (logger.isDebugEnabled()) {
811 logger.debug(msg, e1);
817 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
819 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
820 if (Tools.isEmpty(csid) == false) {
821 ilistItem.setCsid(csid);
823 ilistItem.setRefName(refName);
824 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
825 ilistItem.setItemDisplayName(termInfo.displayName);
826 ilistItem.setSourceField(authRefFieldName);
827 ilistItem.setUri(termInfo.getRelativeUri());
828 } catch (Exception e) {
830 String msg = String.format("Trouble parsing refName from value: %s in field: %s. Error message: %s.",
831 refName, authRefFieldName, e.getLocalizedMessage());
832 if (logger.isDebugEnabled()) {
833 logger.debug(msg, e);
843 * Returns the primary value from a list of values.
845 * Assumes that the first value is the primary value.
846 * This assumption may change when and if the primary value
847 * is identified explicitly.
849 * @param values a list of values.
850 * @param propertyName the name of a property through
851 * which the value can be extracted.
852 * @return the primary value.
853 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
854 String primaryValue = "";
855 if (values == null || values.size() == 0) {
858 Object value = values.get(0);
859 if (value instanceof String) {
861 primaryValue = (String) value;
863 // Multivalue group of fields
864 } else if (value instanceof Map) {
866 Map map = (Map) value;
867 if (map.values().size() > 0) {
868 if (map.get(propertyName) != null) {
869 primaryValue = (String) map.get(propertyName);
874 logger.warn("Unexpected type for property " + propertyName
875 + " in multivalue list: not String or Map.");
882 * Gets a simple property from the document.
884 * For completeness, as this duplicates DocumentModel method.
886 * @param docModel The document model to get info from
887 * @param schema The name of the schema (part)
888 * @param propertyName The simple scalar property type
889 * @return property value as String
891 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
892 String xpath = "/"+schema+":"+propName;
894 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
895 } catch(PropertyException pe) {
896 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
897 +pe.getLocalizedMessage());
898 } catch(ClassCastException cce) {
899 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
900 +cce.getLocalizedMessage());
901 } catch(Exception e) {
902 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
903 +e.getLocalizedMessage());
908 * Gets first of a repeating list of scalar values, as a String, from the document.
910 * @param docModel The document model to get info from
911 * @param schema The name of the schema (part)
912 * @param listName The name of the scalar list property
913 * @return first value in list, as a String, or empty string if the list is empty
915 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
916 String schema, String listName) {
917 String xpath = "/" + schema + ":" + listName + "/[0]";
919 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
920 } catch (PropertyException pe) {
921 throw new RuntimeException("Problem retrieving property {" + xpath
922 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
923 } catch (IndexOutOfBoundsException ioobe) {
924 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
925 return ""; // gracefully handle missing elements
926 } catch (ClassCastException cce) {
927 throw new RuntimeException("Problem retrieving property {" + xpath
928 + "} as String. Not a repeating String property?"
929 + cce.getLocalizedMessage());
930 } catch (Exception e) {
931 throw new RuntimeException("Unknown problem retrieving property {"
932 + xpath + "}." + e.getLocalizedMessage());
937 * Gets first of a repeating list of scalar values, as a String, from the document.
939 * @param docModel The document model to get info from
940 * @param schema The name of the schema (part)
941 * @param listName The name of the scalar list property
942 * @return first value in list, as a String, or empty string if the list is empty
944 protected String getStringValueInPrimaryRepeatingComplexProperty(
945 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
946 String result = null;
948 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
950 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
951 } catch(PropertyException pe) {
952 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
953 +pe.getLocalizedMessage());
954 } catch(IndexOutOfBoundsException ioobe) {
955 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
956 result = ""; // gracefully handle missing elements
957 } catch(ClassCastException cce) {
958 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
959 +cce.getLocalizedMessage());
960 } catch(NullPointerException npe) {
961 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
962 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
964 } catch(Exception e) {
965 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
966 +e.getLocalizedMessage());
973 * Gets XPath value from schema. Note that only "/" and "[n]" are
974 * supported for xpath. Can omit grouping elements for repeating complex types,
975 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
976 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
977 * If there are no entries for a list of scalars or for a list of complex types,
978 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
979 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
980 * that many elements in the list.
981 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
983 * @param docModel The document model to get info from
984 * @param schema The name of the schema (part)
985 * @param xpath The XPath expression (without schema prefix)
986 * @return value the indicated property value as a String
987 * @throws DocumentException
989 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
990 String schema, ListResultField field) throws DocumentException {
991 Object result = null;
993 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
998 protected String getStringValue(DocumentModel docModel,
999 String schema, ListResultField field) throws DocumentException {
1000 String result = null;
1002 Object value = getListResultValue(docModel, schema, field);
1003 if (value != null && value instanceof String) {
1004 String strValue = (String) value;
1005 if (strValue.trim().isEmpty() == false) {
1013 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1017 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
1019 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
1021 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
1023 sb.append(item.getPredicate());
1025 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
1029 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
1030 StringBuilder sb = new StringBuilder();
1032 if (list.size() > 0) {
1033 sb.append("=========== " + label + " ==========" + CR);
1035 for (RelationsCommonList.RelationListItem item : list) {
1036 itemToString(sb, "== ", item);
1039 return sb.toString();
1042 /** @return null on parent not found
1044 protected String getParentCSID(String thisCSID) throws Exception {
1045 String parentCSID = null;
1047 String predicate = RelationshipType.HAS_BROADER.value();
1048 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1049 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1050 if (parentList != null) {
1051 if (parentList.size() == 0) {
1054 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
1055 parentCSID = relationListItem.getObjectCsid();
1058 } catch (Exception e) {
1059 logger.error("Could not find parent for this: " + thisCSID, e);
1064 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
1065 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
1069 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
1070 MultipartServiceContext ctx) throws Exception {
1071 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1072 String parentCSID = getParentCSID(thisCSID);
1073 if (parentCSID == null) {
1074 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
1078 String predicate = RelationshipType.HAS_BROADER.value();
1079 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
1080 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
1082 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
1085 RelationsCommonList.RelationListItem item = null;
1086 for (RelationsCommonList.RelationListItem sibling : siblingList) {
1087 if (thisCSID.equals(sibling.getSubjectCsid())) {
1088 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.
1091 //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.
1092 for (RelationsCommonList.RelationListItem self : toRemoveList) {
1093 removeFromList(siblingList, self);
1096 long siblingSize = siblingList.size();
1097 siblingListOuter.setTotalItems(siblingSize);
1098 siblingListOuter.setItemsInPage(siblingSize);
1099 if(logger.isTraceEnabled()) {
1100 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
1101 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1104 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
1105 ctx.addOutputPart(relationsPart);
1108 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
1109 MultipartServiceContext ctx) throws Exception {
1110 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1112 String predicate = RelationshipType.HAS_BROADER.value();
1113 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1114 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1116 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
1117 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
1119 if(logger.isTraceEnabled()) {
1120 String dump = dumpLists(thisCSID, parentList, childrenList, null);
1121 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1124 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
1125 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
1126 //Not optimal, but that's the current design spec.
1128 for (RelationsCommonList.RelationListItem parent : parentList) {
1129 childrenList.add(parent);
1132 long childrenSize = childrenList.size();
1133 childrenListOuter.setTotalItems(childrenSize);
1134 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1136 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1137 ctx.addOutputPart(relationsPart);
1140 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1141 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1143 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1144 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1146 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1147 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1149 if(logger.isTraceEnabled()) {
1150 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1151 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1154 subjectList.addAll(objectList);
1156 //now subjectList actually has records BOTH where thisCSID is subject and object.
1157 long relatedSize = subjectList.size();
1158 subjectListOuter.setTotalItems(relatedSize);
1159 subjectListOuter.setItemsInPage(relatedSize);
1161 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1162 ctx.addOutputPart(relationsPart);
1165 private String dumpLists(String itemCSID,
1166 List<RelationsCommonList.RelationListItem> parentList,
1167 List<RelationsCommonList.RelationListItem> childList,
1168 List<RelationsCommonList.RelationListItem> actionList) {
1169 StringBuilder sb = new StringBuilder();
1170 sb.append("itemCSID: " + itemCSID + CR);
1171 if(parentList!=null) {
1172 sb.append(dumpList(parentList, "parentList"));
1174 if(childList!=null) {
1175 sb.append(dumpList(childList, "childList"));
1177 if(actionList!=null) {
1178 sb.append(dumpList(actionList, "actionList"));
1180 return sb.toString();
1183 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1184 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1185 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1186 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1187 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1188 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1189 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1191 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1192 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1193 return relationsCommonList;
1195 //============================= END TODO refactor ==========================
1197 // this method calls the RelationResource to have it create the relations and persist them.
1198 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1199 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1200 for (RelationsCommonList.RelationListItem item : inboundList) {
1201 RelationsCommon rc = new RelationsCommon();
1202 //rc.setCsid(item.getCsid());
1203 //todo: assignTo(item, rc);
1204 RelationsDocListItem itemSubject = item.getSubject();
1205 RelationsDocListItem itemObject = item.getObject();
1207 // Set at least one of CSID and refName for Subject and Object
1208 // Either value might be null for for each of Subject and Object
1209 String subjectCsid = itemSubject.getCsid();
1210 rc.setSubjectCsid(subjectCsid);
1212 String objCsid = itemObject.getCsid();
1213 rc.setObjectCsid(objCsid);
1215 rc.setSubjectRefName(itemSubject.getRefName());
1216 rc.setObjectRefName(itemObject.getRefName());
1218 rc.setRelationshipType(item.getPredicate());
1219 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1220 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1221 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1223 // This is superfluous, since it will be fetched by the Relations Create logic.
1224 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1225 rc.setObjectDocumentType(itemObject.getDocumentType());
1227 // This is superfluous, since it will be fetched by the Relations Create logic.
1228 rc.setSubjectUri(itemSubject.getUri());
1229 rc.setObjectUri(itemObject.getUri());
1230 // May not have the info here. Only really require CSID or refName.
1231 // Rest is handled in the Relation create mechanism
1232 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1234 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1235 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1236 payloadOut.addPart(outputPart);
1237 RelationResource relationResource = new RelationResource();
1238 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1239 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1243 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1244 // But item1 must not be sparse
1245 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1246 if (item1 == null || item2 == null) {
1249 RelationsDocListItem subj1 = item1.getSubject();
1250 RelationsDocListItem subj2 = item2.getSubject();
1251 RelationsDocListItem obj1 = item1.getObject();
1252 RelationsDocListItem obj2 = item2.getObject();
1254 String subj1Csid = subj1.getCsid();
1255 String subj2Csid = subj2.getCsid();
1256 String subj1RefName = subj1.getRefName();
1257 String subj2RefName = subj2.getRefName();
1259 String obj1Csid = obj1.getCsid();
1260 String obj2Csid = obj2.getCsid();
1261 String obj1RefName = obj1.getRefName();
1262 String obj2RefName = obj2.getRefName();
1264 String item1Metatype = item1.getRelationshipMetaType();
1265 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1267 String item2Metatype = item2.getRelationshipMetaType();
1268 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1270 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1271 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1272 // predicate is proper, but still allow relationshipType
1273 && (item1.getPredicate().equals(item2.getPredicate())
1274 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1275 // Allow missing docTypes, so long as they do not conflict
1276 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1277 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1278 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1282 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1283 // But the list items must not be sparse
1284 private RelationsCommonList.RelationListItem findInList(
1285 List<RelationsCommonList.RelationListItem> list,
1286 RelationsCommonList.RelationListItem item) {
1287 RelationsCommonList.RelationListItem foundItem = null;
1288 for (RelationsCommonList.RelationListItem listItem : list) {
1289 if (itemsEqual(listItem, item)) { //equals must be defined, else
1290 foundItem = listItem;
1297 /** updateRelations strategy:
1300 go through inboundList, remove anything from childList that matches from childList
1301 go through inboundList, remove anything from parentList that matches from parentList
1302 go through parentList, delete all remaining
1303 go through childList, delete all remaining
1304 go through actionList, add all remaining.
1305 check for duplicate children
1306 check for more than one parent.
1308 inboundList parentList childList actionList
1309 ---------------- --------------- ---------------- ----------------
1310 child-a parent-c child-a child-b
1311 child-b parent-d child-c
1316 private RelationsCommonList updateRelations(
1317 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate)
1319 if (logger.isTraceEnabled()) {
1320 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1322 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1324 return null; //nothing to do--they didn't send a list of relations.
1326 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1327 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1328 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1329 List<RelationsCommonList.RelationListItem> childList = null;
1330 List<RelationsCommonList.RelationListItem> parentList = null;
1331 DocumentModel docModel = wrapDoc.getWrappedObject();
1332 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1333 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1335 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1336 //Do magic replacement of ${itemCSID} and fix URI's.
1337 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1339 final String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1340 UriInfo uriInfo = ctx.getUriInfo();
1341 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1344 //Run getList() once as sent to get childListOuter:
1345 String predicate = RelationshipType.HAS_BROADER.value();
1346 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1347 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1348 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1349 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1350 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1352 RelationResource relationResource = new RelationResource();
1353 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1355 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1356 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1357 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1358 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1359 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1362 childList = childListOuter.getRelationListItem();
1363 parentList = parentListOuter.getRelationListItem();
1365 if (parentList.size() > 1) {
1366 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1369 if (logger.isTraceEnabled()) {
1370 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1374 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1375 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1376 // and so the CSID for those may be null
1377 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1378 // Look for parents and children
1379 if(itemCSID.equals(inboundItem.getObject().getCsid())
1380 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1381 //then this is an item that says we have a child. That child is inboundItem
1382 RelationsCommonList.RelationListItem childItem =
1383 (childList == null) ? null : findInList(childList, inboundItem);
1384 if (childItem != null) {
1385 if (logger.isTraceEnabled()) {
1386 StringBuilder sb = new StringBuilder();
1387 itemToString(sb, "== Child: ", childItem);
1388 logger.trace("Found inboundChild in current child list: " + sb.toString());
1390 removeFromList(childList, childItem); //exists, just take it off delete list
1392 if (logger.isTraceEnabled()) {
1393 StringBuilder sb = new StringBuilder();
1394 itemToString(sb, "== Child: ", inboundItem);
1395 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1397 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1398 String newChildCsid = inboundItem.getSubject().getCsid();
1399 if(newChildCsid == null) {
1400 String newChildRefName = inboundItem.getSubject().getRefName();
1401 if (newChildRefName == null) {
1402 throw new RuntimeException("Child with no CSID or refName!");
1404 if (logger.isTraceEnabled()) {
1405 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1407 DocumentModel newChildDocModel =
1408 NuxeoBasedResource.getDocModelForRefName(getServiceContext(),
1409 newChildRefName, getServiceContext().getResourceMap());
1410 newChildCsid = getCsid(newChildDocModel);
1412 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1415 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1416 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1417 //then this is an item that says we have a parent. inboundItem is that parent.
1418 RelationsCommonList.RelationListItem parentItem =
1419 (parentList == null) ? null : findInList(parentList, inboundItem);
1420 if (parentItem != null) {
1421 removeFromList(parentList, parentItem); //exists, just take it off delete list
1423 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1426 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1429 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1432 if (logger.isTraceEnabled()) {
1433 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1434 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1437 if (logger.isTraceEnabled()) {
1438 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1439 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1441 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1442 deleteRelations(childList, ctx, "childList");
1444 if (logger.isTraceEnabled()) {
1445 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1446 + actionList.size() + " new parents and children.");
1448 createRelations(actionList, ctx);
1449 if (logger.isTraceEnabled()) {
1450 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1452 // We return all elements on the inbound list, since we have just worked to make them exist in the system
1453 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1454 return relationsCommonListBody;
1457 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1458 * and sets URI correctly for related items.
1459 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1461 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1462 List<RelationsCommonList.RelationListItem> inboundList,
1463 DocumentModel docModel,
1464 String itemCSID) throws Exception {
1465 String thisURI = this.getUri(docModel);
1466 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1467 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1468 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1469 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1470 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1472 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1473 inboundItem.setObjectCsid(itemCSID);
1474 inboundItemObject.setCsid(itemCSID);
1475 //inboundItemObject.setUri(getUri(docModel));
1478 String objectCsid = inboundItemObject.getCsid();
1479 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1480 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1481 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1482 inboundItemObject.setUri(uri); //CSPACE-4037
1485 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1487 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1488 inboundItem.setSubjectCsid(itemCSID);
1489 inboundItemSubject.setCsid(itemCSID);
1490 //inboundItemSubject.setUri(getUri(docModel));
1493 String subjectCsid = inboundItemSubject.getCsid();
1494 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1495 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1496 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1497 inboundItemSubject.setUri(uri); //CSPACE-4037
1500 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1505 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1506 MultivaluedMap<String, String> queryParams, String childCSID) {
1507 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1508 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1509 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1510 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1512 RelationResource relationResource = new RelationResource();
1513 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1514 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1515 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1516 deleteRelations(parentList, ctx, "parentList-delete");
1519 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1520 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1523 for (RelationsCommonList.RelationListItem item : list) {
1524 RelationResource relationResource = new RelationResource();
1525 if(logger.isTraceEnabled()) {
1526 StringBuilder sb = new StringBuilder();
1527 itemToString(sb, "==== TO DELETE: ", item);
1528 logger.trace(sb.toString());
1530 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1531 if (logger.isDebugEnabled()) {
1532 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1535 } catch (Throwable t) {
1536 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1541 // Note that we must do this after we have completed the Update, so that the repository has the
1542 // info for the item itself. The relations code must call into the repo to get info for each end.
1543 // This could be optimized to pass in the parent docModel, since it will often be one end.
1544 // Nevertheless, we should complete the item save before we do work on the relations, especially
1545 // since a save on Create might fail, and we would not want to create relations for something
1546 // that may not be created...
1547 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate) throws Exception {
1548 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1549 PoxPayloadIn input = ctx.getInput();
1550 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1551 String itemCsid = documentModel.getName();
1553 //Updates relations part
1554 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, forUpdate);
1556 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
1557 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1559 //now we add part for relations list
1560 //ServiceContext ctx = getServiceContext();
1561 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1562 ctx.getOutput().addPart(payloadOutputPart);
1566 * Checks to see if the refName has changed, and if so,
1567 * uses utilities to find all references and update them to use the new refName.
1570 protected void handleRefNameReferencesUpdate() throws Exception {
1571 if (hasRefNameUpdate() == true) {
1572 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1573 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1574 CoreSessionInterface repoSession = this.getRepositorySession();
1576 // Update all the relationship records that referred to the old refName
1577 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1578 oldRefNameOnUpdate, newRefNameOnUpdate);
1582 protected String getRefNameUpdate() {
1583 String result = null;
1585 if (hasRefNameUpdate() == true) {
1586 result = newRefNameOnUpdate;
1587 if (logger.isDebugEnabled() == true) {
1588 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1589 newRefNameOnUpdate, oldRefNameOnUpdate));
1597 * Note: The Vocabulary document handler overrides this method.
1599 protected String getRefPropName() {
1600 return ServiceBindingUtils.AUTH_REF_PROP;