2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.nuxeo.client.java;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
30 import java.util.Map.Entry;
33 import javax.ws.rs.WebApplicationException;
34 import javax.ws.rs.core.MediaType;
35 import javax.ws.rs.core.MultivaluedMap;
36 import javax.ws.rs.core.Response;
37 import javax.ws.rs.core.UriInfo;
38 import javax.xml.bind.JAXBElement;
40 import org.collectionspace.services.authorization.AccountPermission;
41 import org.collectionspace.services.jaxb.AbstractCommonList;
42 import org.collectionspace.services.lifecycle.TransitionDef;
43 import org.collectionspace.services.client.CollectionSpaceClient;
44 import org.collectionspace.services.client.PayloadInputPart;
45 import org.collectionspace.services.client.PayloadOutputPart;
46 import org.collectionspace.services.client.PoxPayloadIn;
47 import org.collectionspace.services.client.PoxPayloadOut;
48 import org.collectionspace.services.client.Profiler;
49 import org.collectionspace.services.client.RelationClient;
50 import org.collectionspace.services.client.workflow.WorkflowClient;
51 import org.collectionspace.services.common.ResourceBase;
52 import org.collectionspace.services.common.authorityref.AuthorityRefList;
53 import org.collectionspace.services.common.config.ServiceConfigUtils;
54 import org.collectionspace.services.common.context.JaxRsContext;
55 import org.collectionspace.services.common.context.MultipartServiceContext;
56 import org.collectionspace.services.common.context.ServiceBindingUtils;
57 import org.collectionspace.services.common.context.ServiceContext;
58 import org.collectionspace.services.common.document.BadRequestException;
59 import org.collectionspace.services.common.document.DocumentException;
60 import org.collectionspace.services.common.document.DocumentUtils;
61 import org.collectionspace.services.common.document.DocumentWrapper;
62 import org.collectionspace.services.common.document.DocumentFilter;
63 import org.collectionspace.services.client.IRelationsManager;
64 import org.collectionspace.services.common.relation.RelationResource;
65 import org.collectionspace.services.common.repository.RepositoryClient;
66 import org.collectionspace.services.common.security.SecurityUtils;
67 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
68 import org.collectionspace.services.common.api.CommonAPI;
69 import org.collectionspace.services.common.api.RefNameUtils;
70 import org.collectionspace.services.common.api.Tools;
71 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
72 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
73 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
74 import org.collectionspace.services.config.service.DocHandlerParams;
75 import org.collectionspace.services.config.service.ListResultField;
76 import org.collectionspace.services.config.service.ObjectPartType;
77 import org.collectionspace.services.config.service.ServiceBindingType;
78 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
79 import org.collectionspace.services.relation.RelationsCommon;
80 import org.collectionspace.services.relation.RelationsCommonList;
81 import org.collectionspace.services.relation.RelationsDocListItem;
82 import org.collectionspace.services.relation.RelationshipType;
83 import org.dom4j.Element;
85 import org.nuxeo.ecm.core.api.DocumentModel;
86 import org.nuxeo.ecm.core.api.DocumentModelList;
87 import org.nuxeo.ecm.core.api.model.PropertyException;
88 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
90 import org.slf4j.Logger;
91 import org.slf4j.LoggerFactory;
94 * RemoteDocumentModelHandler
96 * $LastChangedRevision: $
101 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
102 extends DocumentModelHandler<T, TL> {
105 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
106 private final static String CR = "\r\n";
108 protected String oldRefNameOnUpdate = null;
109 protected String newRefNameOnUpdate = null;
112 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
115 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
116 if (ctx instanceof MultipartServiceContext) {
117 super.setServiceContext(ctx);
119 throw new IllegalArgumentException("setServiceContext requires instance of "
120 + MultipartServiceContext.class.getName());
125 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
126 return getRefnameDisplayName(docWrapper.getWrappedObject());
129 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
130 String result = null;
131 ServiceContext ctx = this.getServiceContext();
133 DocHandlerParams.Params params = null;
135 params = ServiceConfigUtils.getDocHandlerParams(ctx);
136 ListResultField field = params.getRefnameDisplayNameField();
138 String schema = field.getSchema();
139 if (schema == null || schema.trim().isEmpty()) {
140 schema = ctx.getCommonPartLabel();
143 result = getStringValue(docModel, schema, field);
144 } catch (Exception e) {
145 if (logger.isWarnEnabled()) {
146 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
154 public boolean supportsHierarchy() {
155 boolean result = false;
157 DocHandlerParams.Params params = null;
159 ServiceContext ctx = this.getServiceContext();
160 params = ServiceConfigUtils.getDocHandlerParams(ctx);
161 Boolean bool = params.isSupportsHierarchy();
163 result = bool.booleanValue();
165 } catch (DocumentException e) {
166 // TODO Auto-generated catch block
167 logger.error(String.format("Could not get document handler params for class %s", this.getClass().getName()), e);
174 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
176 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
180 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
181 super.completeCreate(wrapDoc);
182 if (supportsHierarchy() == true) {
183 handleRelationsPayload(wrapDoc, false);
188 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
191 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
192 DocumentModel docModel = wrapDoc.getWrappedObject();
193 //return at least those document part(s) that were received
194 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
195 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
196 PoxPayloadIn input = ctx.getInput();
198 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
199 for (PayloadInputPart part : inputParts) {
200 String partLabel = part.getLabel();
202 ObjectPartType partMeta = partsMetaMap.get(partLabel);
203 // CSPACE-4030 - generates NPE if the part is missing.
205 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
206 if(unQObjectProperties!=null) {
207 addOutputPart(unQObjectProperties, partLabel, partMeta);
210 } catch (Throwable t){
211 logger.error("Unable to addOutputPart: "+partLabel
212 +" in serviceContextPath: "+this.getServiceContextPath()
213 +" with URI: "+this.getServiceContext().getUriInfo().getPath()
218 if (logger.isWarnEnabled() == true) {
219 logger.warn("MultipartInput part was null for document id = " +
224 if (supportsHierarchy() == true) {
225 handleRelationsPayload(wrapDoc, true);
226 handleItemRefNameReferenceUpdate();
231 * Adds the output part.
233 * @param unQObjectProperties the un q object properties
234 * @param schema the schema
235 * @param partMeta the part meta
236 * @throws Exception the exception
237 * MediaType.APPLICATION_XML_TYPE
239 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
241 Element doc = DocumentUtils.buildDocument(partMeta, schema,
242 unQObjectProperties);
243 if (logger.isTraceEnabled() == true) {
244 logger.trace(doc.asXML());
246 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
247 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
251 * Extract paging info.
253 * @param commonsList the commons list
255 * @throws Exception the exception
257 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
259 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
261 DocumentFilter docFilter = this.getDocumentFilter();
262 long pageSize = docFilter.getPageSize();
263 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
264 // set the page size and page number
265 commonList.setPageNum(pageNum);
266 commonList.setPageSize(pageSize);
267 DocumentModelList docList = wrapDoc.getWrappedObject();
268 // Set num of items in list. this is useful to our testing framework.
269 commonList.setItemsInPage(docList.size());
270 // set the total result size
271 commonList.setTotalItems(docList.totalSize());
273 return (TL) commonList;
277 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
280 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
283 DocumentModel docModel = wrapDoc.getWrappedObject();
284 String[] schemas = docModel.getDeclaredSchemas();
285 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
286 for (String schema : schemas) {
287 ObjectPartType partMeta = partsMetaMap.get(schema);
288 if (partMeta == null) {
289 continue; // unknown part, ignore
291 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
292 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
293 addExtraCoreValues(docModel, unQObjectProperties);
295 addOutputPart(unQObjectProperties, schema, partMeta);
298 if (supportsHierarchy() == true) {
299 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
300 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
301 if (Tools.isTrue(showSiblings)) {
302 showSiblings(wrapDoc, ctx);
303 return; // actual result is returned on ctx.addOutputPart();
306 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
307 if (Tools.isTrue(showRelations)) {
308 showRelations(wrapDoc, ctx);
309 return; // actual result is returned on ctx.addOutputPart();
312 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
313 if (Tools.isTrue(showAllRelations)) {
314 showAllRelations(wrapDoc, ctx);
315 return; // actual result is returned on ctx.addOutputPart();
319 addAccountPermissionsPart();
322 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
324 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
327 private void addAccountPermissionsPart() throws Exception {
328 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
331 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
332 String currentServiceName = ctx.getServiceName();
333 String workflowSubResource = "/";
334 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
335 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
336 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
337 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
339 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
341 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
342 currentServiceName, workflowSubResource);
343 org.collectionspace.services.authorization.ObjectFactory objectFactory =
344 new org.collectionspace.services.authorization.ObjectFactory();
345 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
346 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
347 ctx.addOutputPart(accountPermissionPart);
353 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
356 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
358 //TODO filling extension parts should be dynamic
359 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
360 //not an ideal way of populating objects.
361 DocumentModel docModel = wrapDoc.getWrappedObject();
362 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
363 PoxPayloadIn input = ctx.getInput();
364 if (input.getParts().isEmpty()) {
365 String msg = "No payload found!";
366 logger.error(msg + "Ctx=" + getServiceContext().toString());
367 throw new BadRequestException(msg);
370 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
372 //iterate over parts received and fill those parts
373 List<PayloadInputPart> inputParts = input.getParts();
374 for (PayloadInputPart part : inputParts) {
376 String partLabel = part.getLabel();
377 if (partLabel == null) {
378 String msg = "Part label is missing or empty!";
379 logger.error(msg + "Ctx=" + getServiceContext().toString());
380 throw new BadRequestException(msg);
383 //skip if the part is not in metadata
384 ObjectPartType partMeta = partsMetaMap.get(partLabel);
385 if (partMeta == null) {
388 fillPart(part, docModel, partMeta, action, ctx);
394 * fillPart fills an XML part into given document model
395 * @param part to fill
396 * @param docModel for the given object
397 * @param partMeta metadata for the object to fill
400 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
401 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
403 //check if this is an xml part
404 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
405 Element element = part.getElementBody();
406 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
407 if (action == Action.UPDATE) {
408 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
410 docModel.setProperties(partMeta.getLabel(), objectProps);
415 * Filters out read only properties, so they cannot be set on update.
416 * TODO: add configuration support to do this generally
417 * @param objectProps the properties parsed from the update payload
418 * @param partMeta metadata for the object to fill
420 public void filterReadOnlyPropertiesForPart(
421 Map<String, Object> objectProps, ObjectPartType partMeta) {
422 // Should add in logic to filter most of the core items on update
423 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
424 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
425 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
426 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
427 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
428 // Note that the updatedAt/updatedBy fields are set internally
429 // in DocumentModelHandler.handleCoreValues().
434 * extractPart extracts an XML object from given DocumentModel
436 * @param schema of the object to extract
437 * @param partMeta metadata for the object to extract
440 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
442 return extractPart(docModel, schema, (Map<String, Object>)null);
446 * extractPart extracts an XML object from given DocumentModel
448 * @param schema of the object to extract
449 * @param partMeta metadata for the object to extract
453 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
455 return extractPart(docModel, schema, partMeta, null);
459 * extractPart extracts an XML object from given DocumentModel
461 * @param schema of the object to extract
462 * @param partMeta metadata for the object to extract
465 protected Map<String, Object> extractPart(
466 DocumentModel docModel,
468 Map<String, Object> addToMap)
470 Map<String, Object> result = null;
472 Map<String, Object> objectProps = docModel.getProperties(schema);
473 if (objectProps != null) {
474 //unqualify properties before sending the doc over the wire (to save bandwidh)
475 //FIXME: is there a better way to avoid duplication of a Map/Collection?
476 Map<String, Object> unQObjectProperties =
477 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
478 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
479 for (Entry<String, Object> entry : qualifiedEntries) {
480 String unqProp = getUnQProperty(entry.getKey());
481 unQObjectProperties.put(unqProp, entry.getValue());
483 result = unQObjectProperties;
490 * extractPart extracts an XML object from given DocumentModel
492 * @param schema of the object to extract
493 * @param partMeta metadata for the object to extract
497 protected Map<String, Object> extractPart(
498 DocumentModel docModel, String schema, ObjectPartType partMeta,
499 Map<String, Object> addToMap)
501 Map<String, Object> result = null;
503 result = this.extractPart(docModel, schema, addToMap);
509 public String getStringPropertyFromDoc(
512 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
513 RepositoryInstance repoSession = null;
514 boolean releaseRepoSession = false;
515 String returnValue = null;
518 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
519 repoSession = this.getRepositorySession();
520 if (repoSession == null) {
521 repoSession = repoClient.getRepositorySession();
522 releaseRepoSession = true;
526 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
527 DocumentModel docModel = wrapper.getWrappedObject();
528 returnValue = (String) docModel.getPropertyValue(propertyXPath);
529 } catch (PropertyException pe) {
531 } catch (DocumentException de) {
533 } catch (Exception e) {
534 if (logger.isDebugEnabled()) {
535 logger.debug("Caught exception ", e);
537 throw new DocumentException(e);
539 if (releaseRepoSession && repoSession != null) {
540 repoClient.releaseRepositorySession(repoSession);
543 } catch (Exception e) {
544 if (logger.isDebugEnabled()) {
545 logger.debug("Caught exception ", e);
547 throw new DocumentException(e);
551 if (logger.isWarnEnabled() == true) {
552 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
561 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
564 public AuthorityRefList getAuthorityRefs(
566 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
568 AuthorityRefList authRefList = new AuthorityRefList();
569 AbstractCommonList commonList = (AbstractCommonList) authRefList;
571 DocumentFilter docFilter = this.getDocumentFilter();
572 long pageSize = docFilter.getPageSize();
573 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
574 // set the page size and page number
575 commonList.setPageNum(pageNum);
576 commonList.setPageSize(pageSize);
578 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
581 int iFirstToUse = (int)(pageSize*pageNum);
582 int nFoundInPage = 0;
585 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
586 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
588 boolean releaseRepoSession = false;
589 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
590 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
591 RepositoryInstance repoSession = this.getRepositorySession();
592 if (repoSession == null) {
593 repoSession = repoClient.getRepositorySession(ctx);
594 releaseRepoSession = true;
598 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
599 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
600 // Slightly goofy pagination support - how many refs do we expect from one object?
601 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
602 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
603 if(appendToAuthRefsList(ari, list)) {
612 if (releaseRepoSession == true) {
613 repoClient.releaseRepositorySession(ctx, repoSession);
617 // Set num of items in list. this is useful to our testing framework.
618 commonList.setItemsInPage(nFoundInPage);
619 // set the total result size
620 commonList.setTotalItems(nFoundTotal);
622 } catch (PropertyException pe) {
623 String msg = "Attempted to retrieve value for invalid or missing authority field. "
624 + "Check authority field properties in tenant bindings.";
625 logger.warn(msg, pe);
627 } catch (Exception e) {
628 if (logger.isDebugEnabled()) {
629 logger.debug("Caught exception in getAuthorityRefs", e);
631 Response response = Response.status(
632 Response.Status.INTERNAL_SERVER_ERROR).entity(
633 "Failed to retrieve authority references").type(
634 "text/plain").build();
635 throw new WebApplicationException(response);
641 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
642 List<AuthorityRefList.AuthorityRefItem> list)
644 String fieldName = ari.getQualifiedDisplayName();
646 String refNameValue = (String)ari.getProperty().getValue();
647 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
648 if(item!=null) { // ignore garbage values.
652 } catch(PropertyException pe) {
653 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
658 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
660 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
662 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
663 ilistItem.setRefName(refName);
664 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
665 ilistItem.setItemDisplayName(termInfo.displayName);
666 ilistItem.setSourceField(authRefFieldName);
667 ilistItem.setUri(termInfo.getRelativeUri());
668 } catch (Exception e) {
669 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
676 * Returns the primary value from a list of values.
678 * Assumes that the first value is the primary value.
679 * This assumption may change when and if the primary value
680 * is identified explicitly.
682 * @param values a list of values.
683 * @param propertyName the name of a property through
684 * which the value can be extracted.
685 * @return the primary value.
686 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
687 String primaryValue = "";
688 if (values == null || values.size() == 0) {
691 Object value = values.get(0);
692 if (value instanceof String) {
694 primaryValue = (String) value;
696 // Multivalue group of fields
697 } else if (value instanceof Map) {
699 Map map = (Map) value;
700 if (map.values().size() > 0) {
701 if (map.get(propertyName) != null) {
702 primaryValue = (String) map.get(propertyName);
707 logger.warn("Unexpected type for property " + propertyName
708 + " in multivalue list: not String or Map.");
715 * Gets a simple property from the document.
717 * For completeness, as this duplicates DocumentModel method.
719 * @param docModel The document model to get info from
720 * @param schema The name of the schema (part)
721 * @param propertyName The simple scalar property type
722 * @return property value as String
724 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
725 String xpath = "/"+schema+":"+propName;
727 return (String)docModel.getPropertyValue(xpath);
728 } catch(PropertyException pe) {
729 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
730 +pe.getLocalizedMessage());
731 } catch(ClassCastException cce) {
732 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
733 +cce.getLocalizedMessage());
734 } catch(Exception e) {
735 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
736 +e.getLocalizedMessage());
741 * Gets first of a repeating list of scalar values, as a String, from the document.
743 * @param docModel The document model to get info from
744 * @param schema The name of the schema (part)
745 * @param listName The name of the scalar list property
746 * @return first value in list, as a String, or empty string if the list is empty
748 protected String getFirstRepeatingStringProperty(
749 DocumentModel docModel, String schema, String listName) {
750 String xpath = "/"+schema+":"+listName+"/[0]";
752 return (String)docModel.getPropertyValue(xpath);
753 } catch(PropertyException pe) {
754 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
755 +pe.getLocalizedMessage());
756 } catch(IndexOutOfBoundsException ioobe) {
757 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
758 return ""; // gracefully handle missing elements
759 } catch(ClassCastException cce) {
760 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
761 +cce.getLocalizedMessage());
762 } catch(Exception e) {
763 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
764 +e.getLocalizedMessage());
770 * Gets first of a repeating list of scalar values, as a String, from the document.
772 * @param docModel The document model to get info from
773 * @param schema The name of the schema (part)
774 * @param listName The name of the scalar list property
775 * @return first value in list, as a String, or empty string if the list is empty
777 protected String getStringValueInPrimaryRepeatingComplexProperty(
778 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
779 String result = null;
781 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
783 result = (String)docModel.getPropertyValue(xpath);
784 } catch(PropertyException pe) {
785 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
786 +pe.getLocalizedMessage());
787 } catch(IndexOutOfBoundsException ioobe) {
788 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
789 result = ""; // gracefully handle missing elements
790 } catch(ClassCastException cce) {
791 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
792 +cce.getLocalizedMessage());
793 } catch(Exception e) {
794 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
795 +e.getLocalizedMessage());
802 * Gets XPath value from schema. Note that only "/" and "[n]" are
803 * supported for xpath. Can omit grouping elements for repeating complex types,
804 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
805 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
806 * If there are no entries for a list of scalars or for a list of complex types,
807 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
808 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
809 * that many elements in the list.
810 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
812 * @param docModel The document model to get info from
813 * @param schema The name of the schema (part)
814 * @param xpath The XPath expression (without schema prefix)
815 * @return value the indicated property value as a String
817 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
818 String schema, ListResultField field) {
819 Object result = null;
821 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
826 protected String getStringValue(DocumentModel docModel,
827 String schema, ListResultField field) {
828 String result = null;
830 Object value = getListResultValue(docModel, schema, field);
831 if (value != null && value instanceof String) {
832 String strValue = (String) value;
833 if (strValue.trim().isEmpty() == false) {
841 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
845 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
847 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
849 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
851 sb.append(item.getPredicate());
853 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
857 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
858 StringBuilder sb = new StringBuilder();
860 if (list.size() > 0) {
861 sb.append("=========== " + label + " ==========" + CR);
863 for (RelationsCommonList.RelationListItem item : list) {
864 itemToString(sb, "== ", item);
867 return sb.toString();
870 /** @return null on parent not found
872 protected String getParentCSID(String thisCSID) throws Exception {
873 String parentCSID = null;
875 String predicate = RelationshipType.HAS_BROADER.value();
876 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
877 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
878 if (parentList != null) {
879 if (parentList.size() == 0) {
882 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
883 parentCSID = relationListItem.getObjectCsid();
886 } catch (Exception e) {
887 logger.error("Could not find parent for this: " + thisCSID, e);
892 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
893 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
897 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
898 MultipartServiceContext ctx) throws Exception {
899 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
900 String parentCSID = getParentCSID(thisCSID);
901 if (parentCSID == null) {
902 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
906 String predicate = RelationshipType.HAS_BROADER.value();
907 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
908 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
910 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
913 RelationsCommonList.RelationListItem item = null;
914 for (RelationsCommonList.RelationListItem sibling : siblingList) {
915 if (thisCSID.equals(sibling.getSubjectCsid())) {
916 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.
919 //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.
920 for (RelationsCommonList.RelationListItem self : toRemoveList) {
921 removeFromList(siblingList, self);
924 long siblingSize = siblingList.size();
925 siblingListOuter.setTotalItems(siblingSize);
926 siblingListOuter.setItemsInPage(siblingSize);
927 if(logger.isTraceEnabled()) {
928 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
929 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
932 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
933 ctx.addOutputPart(relationsPart);
936 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
937 MultipartServiceContext ctx) throws Exception {
938 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
940 String predicate = RelationshipType.HAS_BROADER.value();
941 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
942 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
944 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
945 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
947 if(logger.isTraceEnabled()) {
948 String dump = dumpLists(thisCSID, parentList, childrenList, null);
949 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
952 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
953 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
954 //Not optimal, but that's the current design spec.
956 for (RelationsCommonList.RelationListItem parent : parentList) {
957 childrenList.add(parent);
960 long childrenSize = childrenList.size();
961 childrenListOuter.setTotalItems(childrenSize);
962 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
964 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
965 ctx.addOutputPart(relationsPart);
968 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
969 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
971 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
972 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
974 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
975 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
977 if(logger.isTraceEnabled()) {
978 String dump = dumpLists(thisCSID, subjectList, objectList, null);
979 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
982 subjectList.addAll(objectList);
984 //now subjectList actually has records BOTH where thisCSID is subject and object.
985 long relatedSize = subjectList.size();
986 subjectListOuter.setTotalItems(relatedSize);
987 subjectListOuter.setItemsInPage(relatedSize);
989 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
990 ctx.addOutputPart(relationsPart);
993 private String dumpLists(String itemCSID,
994 List<RelationsCommonList.RelationListItem> parentList,
995 List<RelationsCommonList.RelationListItem> childList,
996 List<RelationsCommonList.RelationListItem> actionList) {
997 StringBuilder sb = new StringBuilder();
998 sb.append("itemCSID: " + itemCSID + CR);
999 if(parentList!=null) {
1000 sb.append(dumpList(parentList, "parentList"));
1002 if(childList!=null) {
1003 sb.append(dumpList(childList, "childList"));
1005 if(actionList!=null) {
1006 sb.append(dumpList(actionList, "actionList"));
1008 return sb.toString();
1011 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1012 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1013 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1014 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1015 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1016 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1017 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1019 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1020 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1021 return relationsCommonList;
1023 //============================= END TODO refactor ==========================
1025 // this method calls the RelationResource to have it create the relations and persist them.
1026 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1027 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1028 for (RelationsCommonList.RelationListItem item : inboundList) {
1029 RelationsCommon rc = new RelationsCommon();
1030 //rc.setCsid(item.getCsid());
1031 //todo: assignTo(item, rc);
1032 RelationsDocListItem itemSubject = item.getSubject();
1033 RelationsDocListItem itemObject = item.getObject();
1035 // Set at least one of CSID and refName for Subject and Object
1036 // Either value might be null for for each of Subject and Object
1037 String subjectCsid = itemSubject.getCsid();
1038 rc.setSubjectCsid(subjectCsid);
1040 String objCsid = itemObject.getCsid();
1041 rc.setObjectCsid(objCsid);
1043 rc.setSubjectRefName(itemSubject.getRefName());
1044 rc.setObjectRefName(itemObject.getRefName());
1046 rc.setRelationshipType(item.getPredicate());
1047 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1048 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1050 // This is superfluous, since it will be fetched by the Relations Create logic.
1051 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1052 rc.setObjectDocumentType(itemObject.getDocumentType());
1054 // This is superfluous, since it will be fetched by the Relations Create logic.
1055 rc.setSubjectUri(itemSubject.getUri());
1056 rc.setObjectUri(itemObject.getUri());
1057 // May not have the info here. Only really require CSID or refName.
1058 // Rest is handled in the Relation create mechanism
1059 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1061 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1062 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1063 payloadOut.addPart(outputPart);
1064 RelationResource relationResource = new RelationResource();
1065 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1066 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1070 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1071 // But item1 must not be sparse
1072 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1073 if (item1 == null || item2 == null) {
1076 RelationsDocListItem subj1 = item1.getSubject();
1077 RelationsDocListItem subj2 = item2.getSubject();
1078 RelationsDocListItem obj1 = item1.getObject();
1079 RelationsDocListItem obj2 = item2.getObject();
1080 String subj1Csid = subj1.getCsid();
1081 String subj2Csid = subj2.getCsid();
1082 String subj1RefName = subj1.getRefName();
1083 String subj2RefName = subj2.getRefName();
1085 String obj1Csid = obj1.getCsid();
1086 String obj2Csid = obj2.getCsid();
1087 String obj1RefName = obj1.getRefName();
1088 String obj2RefName = obj2.getRefName();
1091 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1092 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1093 // predicate is proper, but still allow relationshipType
1094 && (item1.getPredicate().equals(item2.getPredicate())
1095 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1096 // Allow missing docTypes, so long as they do not conflict
1097 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1098 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1102 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1103 // But the list items must not be sparse
1104 private RelationsCommonList.RelationListItem findInList(
1105 List<RelationsCommonList.RelationListItem> list,
1106 RelationsCommonList.RelationListItem item) {
1107 RelationsCommonList.RelationListItem foundItem = null;
1108 for (RelationsCommonList.RelationListItem listItem : list) {
1109 if (itemsEqual(listItem, item)) { //equals must be defined, else
1110 foundItem = listItem;
1117 /** updateRelations strategy:
1120 go through inboundList, remove anything from childList that matches from childList
1121 go through inboundList, remove anything from parentList that matches from parentList
1122 go through parentList, delete all remaining
1123 go through childList, delete all remaining
1124 go through actionList, add all remaining.
1125 check for duplicate children
1126 check for more than one parent.
1128 inboundList parentList childList actionList
1129 ---------------- --------------- ---------------- ----------------
1130 child-a parent-c child-a child-b
1131 child-b parent-d child-c
1136 private RelationsCommonList updateRelations(
1137 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1139 if (logger.isTraceEnabled()) {
1140 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1142 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1144 return null; //nothing to do--they didn't send a list of relations.
1146 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1147 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1148 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1149 List<RelationsCommonList.RelationListItem> childList = null;
1150 List<RelationsCommonList.RelationListItem> parentList = null;
1151 DocumentModel docModel = wrapDoc.getWrappedObject();
1152 // String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME); cow;
1153 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1154 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1156 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1157 //Do magic replacement of ${itemCSID} and fix URI's.
1158 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1160 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1161 UriInfo uriInfo = ctx.getUriInfo();
1162 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1165 //Run getList() once as sent to get childListOuter:
1166 String predicate = RelationshipType.HAS_BROADER.value();
1167 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1168 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1169 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1170 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1171 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1173 RelationResource relationResource = new RelationResource();
1174 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1176 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1177 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1178 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1179 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1180 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1183 childList = childListOuter.getRelationListItem();
1184 parentList = parentListOuter.getRelationListItem();
1186 if (parentList.size() > 1) {
1187 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1190 if (logger.isTraceEnabled()) {
1191 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1195 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1196 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1197 // and so the CSID for those may be null
1198 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1199 // Look for parents and children
1200 if(itemCSID.equals(inboundItem.getObject().getCsid())
1201 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1202 //then this is an item that says we have a child. That child is inboundItem
1203 RelationsCommonList.RelationListItem childItem =
1204 (childList == null) ? null : findInList(childList, inboundItem);
1205 if (childItem != null) {
1206 if (logger.isTraceEnabled()) {
1207 StringBuilder sb = new StringBuilder();
1208 itemToString(sb, "== Child: ", childItem);
1209 logger.trace("Found inboundChild in current child list: " + sb.toString());
1211 removeFromList(childList, childItem); //exists, just take it off delete list
1213 if (logger.isTraceEnabled()) {
1214 StringBuilder sb = new StringBuilder();
1215 itemToString(sb, "== Child: ", inboundItem);
1216 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1218 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1219 String newChildCsid = inboundItem.getSubject().getCsid();
1220 if(newChildCsid == null) {
1221 String newChildRefName = inboundItem.getSubject().getRefName();
1222 if(newChildRefName==null) {
1223 throw new RuntimeException("Child with no CSID or refName!");
1225 if (logger.isTraceEnabled()) {
1226 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1228 DocumentModel newChildDocModel =
1229 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1230 newChildRefName, getServiceContext().getResourceMap());
1231 newChildCsid = getCsid(newChildDocModel);
1233 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1236 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1237 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1238 //then this is an item that says we have a parent. inboundItem is that parent.
1239 RelationsCommonList.RelationListItem parentItem =
1240 (parentList == null) ? null : findInList(parentList, inboundItem);
1241 if (parentItem != null) {
1242 removeFromList(parentList, parentItem); //exists, just take it off delete list
1244 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1247 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1250 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1253 if (logger.isTraceEnabled()) {
1254 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1255 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1258 if (logger.isTraceEnabled()) {
1259 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1260 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1262 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1263 deleteRelations(childList, ctx, "childList");
1265 if (logger.isTraceEnabled()) {
1266 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1267 + actionList.size() + " new parents and children.");
1269 createRelations(actionList, ctx);
1270 if (logger.isTraceEnabled()) {
1271 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1273 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1274 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1275 return relationsCommonListBody;
1278 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1279 * and sets URI correctly for related items.
1280 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1282 protected void fixupInboundListItems(ServiceContext ctx,
1283 List<RelationsCommonList.RelationListItem> inboundList,
1284 DocumentModel docModel,
1285 String itemCSID) throws Exception {
1286 String thisURI = this.getUri(docModel);
1287 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1288 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1289 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1290 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1291 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1293 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1294 inboundItem.setObjectCsid(itemCSID);
1295 inboundItemObject.setCsid(itemCSID);
1296 //inboundItemObject.setUri(getUri(docModel));
1299 String objectCsid = inboundItemObject.getCsid();
1300 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1301 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1302 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1303 inboundItemObject.setUri(uri); //CSPACE-4037
1306 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1308 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1309 inboundItem.setSubjectCsid(itemCSID);
1310 inboundItemSubject.setCsid(itemCSID);
1311 //inboundItemSubject.setUri(getUri(docModel));
1314 String subjectCsid = inboundItemSubject.getCsid();
1315 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1316 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1317 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1318 inboundItemSubject.setUri(uri); //CSPACE-4037
1321 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1326 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1327 MultivaluedMap<String, String> queryParams, String childCSID) {
1328 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1329 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1330 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1331 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1333 RelationResource relationResource = new RelationResource();
1334 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1335 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1336 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1337 deleteRelations(parentList, ctx, "parentList-delete");
1340 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1341 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1344 for (RelationsCommonList.RelationListItem item : list) {
1345 RelationResource relationResource = new RelationResource();
1346 if(logger.isTraceEnabled()) {
1347 StringBuilder sb = new StringBuilder();
1348 itemToString(sb, "==== TO DELETE: ", item);
1349 logger.trace(sb.toString());
1351 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1352 if (logger.isDebugEnabled()) {
1353 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1356 } catch (Throwable t) {
1357 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1362 // Note that we must do this after we have completed the Update, so that the repository has the
1363 // info for the item itself. The relations code must call into the repo to get info for each end.
1364 // This could be optimized to pass in the parent docModel, since it will often be one end.
1365 // Nevertheless, we should complete the item save before we do work on the relations, especially
1366 // since a save on Create might fail, and we would not want to create relations for something
1367 // that may not be created...
1368 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1369 ServiceContext ctx = getServiceContext();
1370 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1371 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1372 String itemCsid = documentModel.getName();
1374 //Updates relations part
1375 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1377 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
1378 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1380 //now we add part for relations list
1381 //ServiceContext ctx = getServiceContext();
1382 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1383 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1387 * Checks to see if the refName has changed, and if so,
1388 * uses utilities to find all references and update them.
1391 protected void handleItemRefNameReferenceUpdate() throws Exception {
1392 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
1393 // We have work to do.
1394 if (logger.isDebugEnabled()) {
1395 String eol = System.getProperty("line.separator");
1396 logger.debug("Need to find and update references to Item." + eol
1397 + " Old refName" + oldRefNameOnUpdate + eol
1398 + " New refName" + newRefNameOnUpdate);
1400 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1401 RepositoryClient repoClient = getRepositoryClient(ctx);
1402 String refNameProp = getRefPropName();
1404 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
1405 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
1406 if (logger.isDebugEnabled()) {
1407 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
1413 * Note: The Vocabulary document handler overrides this method.
1415 protected String getRefPropName() {
1416 return ServiceBindingUtils.AUTH_REF_PROP;