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 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
168 if (logger.isWarnEnabled() == true) {
177 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
179 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
183 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
184 super.completeCreate(wrapDoc);
185 if (supportsHierarchy() == true) {
186 handleRelationsPayload(wrapDoc, false);
191 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
194 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
195 DocumentModel docModel = wrapDoc.getWrappedObject();
196 //return at least those document part(s) that were received
197 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
198 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
199 PoxPayloadIn input = ctx.getInput();
201 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
202 for (PayloadInputPart part : inputParts) {
203 String partLabel = part.getLabel();
205 ObjectPartType partMeta = partsMetaMap.get(partLabel);
206 // CSPACE-4030 - generates NPE if the part is missing.
208 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
209 if(unQObjectProperties!=null) {
210 addOutputPart(unQObjectProperties, partLabel, partMeta);
213 } catch (Throwable t){
214 logger.error("Unable to addOutputPart: " + partLabel
215 + " in serviceContextPath: "+this.getServiceContextPath()
216 + " with URI: " + this.getServiceContext().getUriInfo().getPath()
221 if (logger.isWarnEnabled() == true) {
222 logger.warn("MultipartInput part was null for document id = " +
227 if (supportsHierarchy() == true) {
228 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
229 handleItemRefNameReferenceUpdate(); // if our record's refName changed, we need to update all the references -including relations.
234 * Adds the output part.
236 * @param unQObjectProperties the un q object properties
237 * @param schema the schema
238 * @param partMeta the part meta
239 * @throws Exception the exception
240 * MediaType.APPLICATION_XML_TYPE
242 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
244 Element doc = DocumentUtils.buildDocument(partMeta, schema,
245 unQObjectProperties);
246 if (logger.isTraceEnabled() == true) {
247 logger.trace(doc.asXML());
249 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
250 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
254 * Extract paging info.
256 * @param commonsList the commons list
258 * @throws Exception the exception
260 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
262 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
264 DocumentFilter docFilter = this.getDocumentFilter();
265 long pageSize = docFilter.getPageSize();
266 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
267 // set the page size and page number
268 commonList.setPageNum(pageNum);
269 commonList.setPageSize(pageSize);
270 DocumentModelList docList = wrapDoc.getWrappedObject();
271 // Set num of items in list. this is useful to our testing framework.
272 commonList.setItemsInPage(docList.size());
273 // set the total result size
274 commonList.setTotalItems(docList.totalSize());
276 return (TL) commonList;
280 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
283 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
286 DocumentModel docModel = wrapDoc.getWrappedObject();
287 String[] schemas = docModel.getDeclaredSchemas();
288 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
289 for (String schema : schemas) {
290 ObjectPartType partMeta = partsMetaMap.get(schema);
291 if (partMeta == null) {
292 continue; // unknown part, ignore
294 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
295 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
296 addExtraCoreValues(docModel, unQObjectProperties);
298 addOutputPart(unQObjectProperties, schema, partMeta);
301 if (supportsHierarchy() == true) {
302 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
303 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
304 if (Tools.isTrue(showSiblings)) {
305 showSiblings(wrapDoc, ctx);
306 return; // actual result is returned on ctx.addOutputPart();
309 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
310 if (Tools.isTrue(showRelations)) {
311 showRelations(wrapDoc, ctx);
312 return; // actual result is returned on ctx.addOutputPart();
315 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
316 if (Tools.isTrue(showAllRelations)) {
317 showAllRelations(wrapDoc, ctx);
318 return; // actual result is returned on ctx.addOutputPart();
322 addAccountPermissionsPart();
325 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
327 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
330 private void addAccountPermissionsPart() throws Exception {
331 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
334 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
335 String currentServiceName = ctx.getServiceName();
336 String workflowSubResource = "/";
337 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
338 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
339 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
340 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
342 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
344 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
345 currentServiceName, workflowSubResource);
346 org.collectionspace.services.authorization.ObjectFactory objectFactory =
347 new org.collectionspace.services.authorization.ObjectFactory();
348 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
349 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
350 ctx.addOutputPart(accountPermissionPart);
356 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
359 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
361 //TODO filling extension parts should be dynamic
362 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
363 //not an ideal way of populating objects.
364 DocumentModel docModel = wrapDoc.getWrappedObject();
365 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
366 PoxPayloadIn input = ctx.getInput();
367 if (input.getParts().isEmpty()) {
368 String msg = "No payload found!";
369 logger.error(msg + "Ctx=" + getServiceContext().toString());
370 throw new BadRequestException(msg);
373 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
375 //iterate over parts received and fill those parts
376 List<PayloadInputPart> inputParts = input.getParts();
377 for (PayloadInputPart part : inputParts) {
379 String partLabel = part.getLabel();
380 if (partLabel == null) {
381 String msg = "Part label is missing or empty!";
382 logger.error(msg + "Ctx=" + getServiceContext().toString());
383 throw new BadRequestException(msg);
386 //skip if the part is not in metadata
387 ObjectPartType partMeta = partsMetaMap.get(partLabel);
388 if (partMeta == null) {
391 fillPart(part, docModel, partMeta, action, ctx);
397 * fillPart fills an XML part into given document model
398 * @param part to fill
399 * @param docModel for the given object
400 * @param partMeta metadata for the object to fill
403 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
404 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
406 //check if this is an xml part
407 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
408 Element element = part.getElementBody();
409 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
410 if (action == Action.UPDATE) {
411 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
413 docModel.setProperties(partMeta.getLabel(), objectProps);
418 * Filters out read only properties, so they cannot be set on update.
419 * TODO: add configuration support to do this generally
420 * @param objectProps the properties parsed from the update payload
421 * @param partMeta metadata for the object to fill
423 public void filterReadOnlyPropertiesForPart(
424 Map<String, Object> objectProps, ObjectPartType partMeta) {
425 // Should add in logic to filter most of the core items on update
426 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
427 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
428 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
429 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
430 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
431 // Note that the updatedAt/updatedBy fields are set internally
432 // in DocumentModelHandler.handleCoreValues().
437 * extractPart extracts an XML object from given DocumentModel
439 * @param schema of the object to extract
440 * @param partMeta metadata for the object to extract
443 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
445 return extractPart(docModel, schema, (Map<String, Object>)null);
449 * extractPart extracts an XML object from given DocumentModel
451 * @param schema of the object to extract
452 * @param partMeta metadata for the object to extract
456 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
458 return extractPart(docModel, schema, partMeta, null);
462 * extractPart extracts an XML object from given DocumentModel
464 * @param schema of the object to extract
465 * @param partMeta metadata for the object to extract
468 protected Map<String, Object> extractPart(
469 DocumentModel docModel,
471 Map<String, Object> addToMap)
473 Map<String, Object> result = null;
475 Map<String, Object> objectProps = docModel.getProperties(schema);
476 if (objectProps != null) {
477 //unqualify properties before sending the doc over the wire (to save bandwidh)
478 //FIXME: is there a better way to avoid duplication of a Map/Collection?
479 Map<String, Object> unQObjectProperties =
480 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
481 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
482 for (Entry<String, Object> entry : qualifiedEntries) {
483 String unqProp = getUnQProperty(entry.getKey());
484 unQObjectProperties.put(unqProp, entry.getValue());
486 result = unQObjectProperties;
493 * extractPart extracts an XML object from given DocumentModel
495 * @param schema of the object to extract
496 * @param partMeta metadata for the object to extract
500 protected Map<String, Object> extractPart(
501 DocumentModel docModel, String schema, ObjectPartType partMeta,
502 Map<String, Object> addToMap)
504 Map<String, Object> result = null;
506 result = this.extractPart(docModel, schema, addToMap);
512 public String getStringPropertyFromDoc(
515 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
516 RepositoryInstance repoSession = null;
517 boolean releaseRepoSession = false;
518 String returnValue = null;
521 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
522 repoSession = this.getRepositorySession();
523 if (repoSession == null) {
524 repoSession = repoClient.getRepositorySession();
525 releaseRepoSession = true;
529 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
530 DocumentModel docModel = wrapper.getWrappedObject();
531 returnValue = (String) docModel.getPropertyValue(propertyXPath);
532 } catch (PropertyException pe) {
534 } catch (DocumentException de) {
536 } catch (Exception e) {
537 if (logger.isDebugEnabled()) {
538 logger.debug("Caught exception ", e);
540 throw new DocumentException(e);
542 if (releaseRepoSession && repoSession != null) {
543 repoClient.releaseRepositorySession(repoSession);
546 } catch (Exception e) {
547 if (logger.isDebugEnabled()) {
548 logger.debug("Caught exception ", e);
550 throw new DocumentException(e);
554 if (logger.isWarnEnabled() == true) {
555 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
564 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
567 public AuthorityRefList getAuthorityRefs(
569 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
571 AuthorityRefList authRefList = new AuthorityRefList();
572 AbstractCommonList commonList = (AbstractCommonList) authRefList;
574 DocumentFilter docFilter = this.getDocumentFilter();
575 long pageSize = docFilter.getPageSize();
576 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
577 // set the page size and page number
578 commonList.setPageNum(pageNum);
579 commonList.setPageSize(pageSize);
581 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
584 int iFirstToUse = (int)(pageSize*pageNum);
585 int nFoundInPage = 0;
588 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
589 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
591 boolean releaseRepoSession = false;
592 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
593 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
594 RepositoryInstance repoSession = this.getRepositorySession();
595 if (repoSession == null) {
596 repoSession = repoClient.getRepositorySession(ctx);
597 releaseRepoSession = true;
601 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
602 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
603 // Slightly goofy pagination support - how many refs do we expect from one object?
604 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
605 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
606 if(appendToAuthRefsList(ari, list)) {
615 if (releaseRepoSession == true) {
616 repoClient.releaseRepositorySession(ctx, repoSession);
620 // Set num of items in list. this is useful to our testing framework.
621 commonList.setItemsInPage(nFoundInPage);
622 // set the total result size
623 commonList.setTotalItems(nFoundTotal);
625 } catch (PropertyException pe) {
626 String msg = "Attempted to retrieve value for invalid or missing authority field. "
627 + "Check authority field properties in tenant bindings.";
628 logger.warn(msg, pe);
630 } catch (Exception e) {
631 if (logger.isDebugEnabled()) {
632 logger.debug("Caught exception in getAuthorityRefs", e);
634 Response response = Response.status(
635 Response.Status.INTERNAL_SERVER_ERROR).entity(
636 "Failed to retrieve authority references").type(
637 "text/plain").build();
638 throw new WebApplicationException(response);
644 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
645 List<AuthorityRefList.AuthorityRefItem> list)
647 String fieldName = ari.getQualifiedDisplayName();
649 String refNameValue = (String)ari.getProperty().getValue();
650 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
651 if(item!=null) { // ignore garbage values.
655 } catch(PropertyException pe) {
656 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
661 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
663 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
665 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
666 ilistItem.setRefName(refName);
667 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
668 ilistItem.setItemDisplayName(termInfo.displayName);
669 ilistItem.setSourceField(authRefFieldName);
670 ilistItem.setUri(termInfo.getRelativeUri());
671 } catch (Exception e) {
672 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
679 * Returns the primary value from a list of values.
681 * Assumes that the first value is the primary value.
682 * This assumption may change when and if the primary value
683 * is identified explicitly.
685 * @param values a list of values.
686 * @param propertyName the name of a property through
687 * which the value can be extracted.
688 * @return the primary value.
689 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
690 String primaryValue = "";
691 if (values == null || values.size() == 0) {
694 Object value = values.get(0);
695 if (value instanceof String) {
697 primaryValue = (String) value;
699 // Multivalue group of fields
700 } else if (value instanceof Map) {
702 Map map = (Map) value;
703 if (map.values().size() > 0) {
704 if (map.get(propertyName) != null) {
705 primaryValue = (String) map.get(propertyName);
710 logger.warn("Unexpected type for property " + propertyName
711 + " in multivalue list: not String or Map.");
718 * Gets a simple property from the document.
720 * For completeness, as this duplicates DocumentModel method.
722 * @param docModel The document model to get info from
723 * @param schema The name of the schema (part)
724 * @param propertyName The simple scalar property type
725 * @return property value as String
727 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
728 String xpath = "/"+schema+":"+propName;
730 return (String)docModel.getPropertyValue(xpath);
731 } catch(PropertyException pe) {
732 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
733 +pe.getLocalizedMessage());
734 } catch(ClassCastException cce) {
735 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
736 +cce.getLocalizedMessage());
737 } catch(Exception e) {
738 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
739 +e.getLocalizedMessage());
744 * Gets first of a repeating list of scalar values, as a String, from the document.
746 * @param docModel The document model to get info from
747 * @param schema The name of the schema (part)
748 * @param listName The name of the scalar list property
749 * @return first value in list, as a String, or empty string if the list is empty
751 protected String getFirstRepeatingStringProperty(
752 DocumentModel docModel, String schema, String listName) {
753 String xpath = "/"+schema+":"+listName+"/[0]";
755 return (String)docModel.getPropertyValue(xpath);
756 } catch(PropertyException pe) {
757 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
758 +pe.getLocalizedMessage());
759 } catch(IndexOutOfBoundsException ioobe) {
760 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
761 return ""; // gracefully handle missing elements
762 } catch(ClassCastException cce) {
763 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
764 +cce.getLocalizedMessage());
765 } catch(Exception e) {
766 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
767 +e.getLocalizedMessage());
773 * Gets first of a repeating list of scalar values, as a String, from the document.
775 * @param docModel The document model to get info from
776 * @param schema The name of the schema (part)
777 * @param listName The name of the scalar list property
778 * @return first value in list, as a String, or empty string if the list is empty
780 protected String getStringValueInPrimaryRepeatingComplexProperty(
781 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
782 String result = null;
784 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
786 result = (String)docModel.getPropertyValue(xpath);
787 } catch(PropertyException pe) {
788 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
789 +pe.getLocalizedMessage());
790 } catch(IndexOutOfBoundsException ioobe) {
791 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
792 result = ""; // gracefully handle missing elements
793 } catch(ClassCastException cce) {
794 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
795 +cce.getLocalizedMessage());
796 } catch(Exception e) {
797 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
798 +e.getLocalizedMessage());
805 * Gets XPath value from schema. Note that only "/" and "[n]" are
806 * supported for xpath. Can omit grouping elements for repeating complex types,
807 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
808 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
809 * If there are no entries for a list of scalars or for a list of complex types,
810 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
811 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
812 * that many elements in the list.
813 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
815 * @param docModel The document model to get info from
816 * @param schema The name of the schema (part)
817 * @param xpath The XPath expression (without schema prefix)
818 * @return value the indicated property value as a String
820 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
821 String schema, ListResultField field) {
822 Object result = null;
824 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
829 protected String getStringValue(DocumentModel docModel,
830 String schema, ListResultField field) {
831 String result = null;
833 Object value = getListResultValue(docModel, schema, field);
834 if (value != null && value instanceof String) {
835 String strValue = (String) value;
836 if (strValue.trim().isEmpty() == false) {
844 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
848 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
850 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
852 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
854 sb.append(item.getPredicate());
856 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
860 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
861 StringBuilder sb = new StringBuilder();
863 if (list.size() > 0) {
864 sb.append("=========== " + label + " ==========" + CR);
866 for (RelationsCommonList.RelationListItem item : list) {
867 itemToString(sb, "== ", item);
870 return sb.toString();
873 /** @return null on parent not found
875 protected String getParentCSID(String thisCSID) throws Exception {
876 String parentCSID = null;
878 String predicate = RelationshipType.HAS_BROADER.value();
879 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
880 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
881 if (parentList != null) {
882 if (parentList.size() == 0) {
885 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
886 parentCSID = relationListItem.getObjectCsid();
889 } catch (Exception e) {
890 logger.error("Could not find parent for this: " + thisCSID, e);
895 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
896 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
900 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
901 MultipartServiceContext ctx) throws Exception {
902 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
903 String parentCSID = getParentCSID(thisCSID);
904 if (parentCSID == null) {
905 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
909 String predicate = RelationshipType.HAS_BROADER.value();
910 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
911 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
913 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
916 RelationsCommonList.RelationListItem item = null;
917 for (RelationsCommonList.RelationListItem sibling : siblingList) {
918 if (thisCSID.equals(sibling.getSubjectCsid())) {
919 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.
922 //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.
923 for (RelationsCommonList.RelationListItem self : toRemoveList) {
924 removeFromList(siblingList, self);
927 long siblingSize = siblingList.size();
928 siblingListOuter.setTotalItems(siblingSize);
929 siblingListOuter.setItemsInPage(siblingSize);
930 if(logger.isTraceEnabled()) {
931 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
932 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
935 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
936 ctx.addOutputPart(relationsPart);
939 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
940 MultipartServiceContext ctx) throws Exception {
941 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
943 String predicate = RelationshipType.HAS_BROADER.value();
944 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
945 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
947 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
948 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
950 if(logger.isTraceEnabled()) {
951 String dump = dumpLists(thisCSID, parentList, childrenList, null);
952 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
955 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
956 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
957 //Not optimal, but that's the current design spec.
959 for (RelationsCommonList.RelationListItem parent : parentList) {
960 childrenList.add(parent);
963 long childrenSize = childrenList.size();
964 childrenListOuter.setTotalItems(childrenSize);
965 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
967 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
968 ctx.addOutputPart(relationsPart);
971 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
972 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
974 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
975 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
977 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
978 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
980 if(logger.isTraceEnabled()) {
981 String dump = dumpLists(thisCSID, subjectList, objectList, null);
982 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
985 subjectList.addAll(objectList);
987 //now subjectList actually has records BOTH where thisCSID is subject and object.
988 long relatedSize = subjectList.size();
989 subjectListOuter.setTotalItems(relatedSize);
990 subjectListOuter.setItemsInPage(relatedSize);
992 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
993 ctx.addOutputPart(relationsPart);
996 private String dumpLists(String itemCSID,
997 List<RelationsCommonList.RelationListItem> parentList,
998 List<RelationsCommonList.RelationListItem> childList,
999 List<RelationsCommonList.RelationListItem> actionList) {
1000 StringBuilder sb = new StringBuilder();
1001 sb.append("itemCSID: " + itemCSID + CR);
1002 if(parentList!=null) {
1003 sb.append(dumpList(parentList, "parentList"));
1005 if(childList!=null) {
1006 sb.append(dumpList(childList, "childList"));
1008 if(actionList!=null) {
1009 sb.append(dumpList(actionList, "actionList"));
1011 return sb.toString();
1014 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1015 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1016 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1017 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1018 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1019 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1020 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1022 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1023 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1024 return relationsCommonList;
1026 //============================= END TODO refactor ==========================
1028 // this method calls the RelationResource to have it create the relations and persist them.
1029 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1030 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1031 for (RelationsCommonList.RelationListItem item : inboundList) {
1032 RelationsCommon rc = new RelationsCommon();
1033 //rc.setCsid(item.getCsid());
1034 //todo: assignTo(item, rc);
1035 RelationsDocListItem itemSubject = item.getSubject();
1036 RelationsDocListItem itemObject = item.getObject();
1038 // Set at least one of CSID and refName for Subject and Object
1039 // Either value might be null for for each of Subject and Object
1040 String subjectCsid = itemSubject.getCsid();
1041 rc.setSubjectCsid(subjectCsid);
1043 String objCsid = itemObject.getCsid();
1044 rc.setObjectCsid(objCsid);
1046 rc.setSubjectRefName(itemSubject.getRefName());
1047 rc.setObjectRefName(itemObject.getRefName());
1049 rc.setRelationshipType(item.getPredicate());
1050 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1051 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1053 // This is superfluous, since it will be fetched by the Relations Create logic.
1054 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1055 rc.setObjectDocumentType(itemObject.getDocumentType());
1057 // This is superfluous, since it will be fetched by the Relations Create logic.
1058 rc.setSubjectUri(itemSubject.getUri());
1059 rc.setObjectUri(itemObject.getUri());
1060 // May not have the info here. Only really require CSID or refName.
1061 // Rest is handled in the Relation create mechanism
1062 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1064 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1065 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1066 payloadOut.addPart(outputPart);
1067 RelationResource relationResource = new RelationResource();
1068 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1069 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1073 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1074 // But item1 must not be sparse
1075 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1076 if (item1 == null || item2 == null) {
1079 RelationsDocListItem subj1 = item1.getSubject();
1080 RelationsDocListItem subj2 = item2.getSubject();
1081 RelationsDocListItem obj1 = item1.getObject();
1082 RelationsDocListItem obj2 = item2.getObject();
1083 String subj1Csid = subj1.getCsid();
1084 String subj2Csid = subj2.getCsid();
1085 String subj1RefName = subj1.getRefName();
1086 String subj2RefName = subj2.getRefName();
1088 String obj1Csid = obj1.getCsid();
1089 String obj2Csid = obj2.getCsid();
1090 String obj1RefName = obj1.getRefName();
1091 String obj2RefName = obj2.getRefName();
1094 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1095 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1096 // predicate is proper, but still allow relationshipType
1097 && (item1.getPredicate().equals(item2.getPredicate())
1098 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1099 // Allow missing docTypes, so long as they do not conflict
1100 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1101 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1105 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1106 // But the list items must not be sparse
1107 private RelationsCommonList.RelationListItem findInList(
1108 List<RelationsCommonList.RelationListItem> list,
1109 RelationsCommonList.RelationListItem item) {
1110 RelationsCommonList.RelationListItem foundItem = null;
1111 for (RelationsCommonList.RelationListItem listItem : list) {
1112 if (itemsEqual(listItem, item)) { //equals must be defined, else
1113 foundItem = listItem;
1120 /** updateRelations strategy:
1123 go through inboundList, remove anything from childList that matches from childList
1124 go through inboundList, remove anything from parentList that matches from parentList
1125 go through parentList, delete all remaining
1126 go through childList, delete all remaining
1127 go through actionList, add all remaining.
1128 check for duplicate children
1129 check for more than one parent.
1131 inboundList parentList childList actionList
1132 ---------------- --------------- ---------------- ----------------
1133 child-a parent-c child-a child-b
1134 child-b parent-d child-c
1139 private RelationsCommonList updateRelations(
1140 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1142 if (logger.isTraceEnabled()) {
1143 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1145 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1147 return null; //nothing to do--they didn't send a list of relations.
1149 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1150 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1151 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1152 List<RelationsCommonList.RelationListItem> childList = null;
1153 List<RelationsCommonList.RelationListItem> parentList = null;
1154 DocumentModel docModel = wrapDoc.getWrappedObject();
1155 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1156 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1158 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1159 //Do magic replacement of ${itemCSID} and fix URI's.
1160 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1162 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1163 UriInfo uriInfo = ctx.getUriInfo();
1164 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1167 //Run getList() once as sent to get childListOuter:
1168 String predicate = RelationshipType.HAS_BROADER.value();
1169 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1170 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1171 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1172 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1173 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1175 RelationResource relationResource = new RelationResource();
1176 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1178 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1179 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1180 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1181 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1182 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1185 childList = childListOuter.getRelationListItem();
1186 parentList = parentListOuter.getRelationListItem();
1188 if (parentList.size() > 1) {
1189 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1192 if (logger.isTraceEnabled()) {
1193 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1197 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1198 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1199 // and so the CSID for those may be null
1200 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1201 // Look for parents and children
1202 if(itemCSID.equals(inboundItem.getObject().getCsid())
1203 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1204 //then this is an item that says we have a child. That child is inboundItem
1205 RelationsCommonList.RelationListItem childItem =
1206 (childList == null) ? null : findInList(childList, inboundItem);
1207 if (childItem != null) {
1208 if (logger.isTraceEnabled()) {
1209 StringBuilder sb = new StringBuilder();
1210 itemToString(sb, "== Child: ", childItem);
1211 logger.trace("Found inboundChild in current child list: " + sb.toString());
1213 removeFromList(childList, childItem); //exists, just take it off delete list
1215 if (logger.isTraceEnabled()) {
1216 StringBuilder sb = new StringBuilder();
1217 itemToString(sb, "== Child: ", inboundItem);
1218 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1220 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1221 String newChildCsid = inboundItem.getSubject().getCsid();
1222 if(newChildCsid == null) {
1223 String newChildRefName = inboundItem.getSubject().getRefName();
1224 if(newChildRefName==null) {
1225 throw new RuntimeException("Child with no CSID or refName!");
1227 if (logger.isTraceEnabled()) {
1228 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1230 DocumentModel newChildDocModel =
1231 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1232 newChildRefName, getServiceContext().getResourceMap());
1233 newChildCsid = getCsid(newChildDocModel);
1235 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1238 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1239 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1240 //then this is an item that says we have a parent. inboundItem is that parent.
1241 RelationsCommonList.RelationListItem parentItem =
1242 (parentList == null) ? null : findInList(parentList, inboundItem);
1243 if (parentItem != null) {
1244 removeFromList(parentList, parentItem); //exists, just take it off delete list
1246 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1249 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1252 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1255 if (logger.isTraceEnabled()) {
1256 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1257 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1260 if (logger.isTraceEnabled()) {
1261 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1262 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1264 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1265 deleteRelations(childList, ctx, "childList");
1267 if (logger.isTraceEnabled()) {
1268 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1269 + actionList.size() + " new parents and children.");
1271 createRelations(actionList, ctx);
1272 if (logger.isTraceEnabled()) {
1273 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1275 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1276 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1277 return relationsCommonListBody;
1280 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1281 * and sets URI correctly for related items.
1282 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1284 protected void fixupInboundListItems(ServiceContext ctx,
1285 List<RelationsCommonList.RelationListItem> inboundList,
1286 DocumentModel docModel,
1287 String itemCSID) throws Exception {
1288 String thisURI = this.getUri(docModel);
1289 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1290 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1291 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1292 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1293 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1295 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1296 inboundItem.setObjectCsid(itemCSID);
1297 inboundItemObject.setCsid(itemCSID);
1298 //inboundItemObject.setUri(getUri(docModel));
1301 String objectCsid = inboundItemObject.getCsid();
1302 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1303 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1304 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1305 inboundItemObject.setUri(uri); //CSPACE-4037
1308 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1310 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1311 inboundItem.setSubjectCsid(itemCSID);
1312 inboundItemSubject.setCsid(itemCSID);
1313 //inboundItemSubject.setUri(getUri(docModel));
1316 String subjectCsid = inboundItemSubject.getCsid();
1317 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1318 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1319 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1320 inboundItemSubject.setUri(uri); //CSPACE-4037
1323 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1328 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1329 MultivaluedMap<String, String> queryParams, String childCSID) {
1330 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1331 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1332 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1333 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1335 RelationResource relationResource = new RelationResource();
1336 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1337 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1338 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1339 deleteRelations(parentList, ctx, "parentList-delete");
1342 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1343 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1346 for (RelationsCommonList.RelationListItem item : list) {
1347 RelationResource relationResource = new RelationResource();
1348 if(logger.isTraceEnabled()) {
1349 StringBuilder sb = new StringBuilder();
1350 itemToString(sb, "==== TO DELETE: ", item);
1351 logger.trace(sb.toString());
1353 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1354 if (logger.isDebugEnabled()) {
1355 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1358 } catch (Throwable t) {
1359 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1364 // Note that we must do this after we have completed the Update, so that the repository has the
1365 // info for the item itself. The relations code must call into the repo to get info for each end.
1366 // This could be optimized to pass in the parent docModel, since it will often be one end.
1367 // Nevertheless, we should complete the item save before we do work on the relations, especially
1368 // since a save on Create might fail, and we would not want to create relations for something
1369 // that may not be created...
1370 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1371 ServiceContext ctx = getServiceContext();
1372 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1373 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1374 String itemCsid = documentModel.getName();
1376 //Updates relations part
1377 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1379 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
1380 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1382 //now we add part for relations list
1383 //ServiceContext ctx = getServiceContext();
1384 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1385 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1389 * Checks to see if the refName has changed, and if so,
1390 * uses utilities to find all references and update them.
1393 protected void handleItemRefNameReferenceUpdate() throws Exception {
1394 if (hasRefNameUpdate() == true) {
1395 // We have work to do.
1396 if (logger.isDebugEnabled()) {
1397 final String EOL = System.getProperty("line.separator");
1398 logger.debug("Need to find and update references to Item." + EOL
1399 + " Old refName" + oldRefNameOnUpdate + EOL
1400 + " New refName" + newRefNameOnUpdate);
1402 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1403 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1405 RepositoryInstance repoSession = this.getRepositorySession();
1406 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1407 oldRefNameOnUpdate, newRefNameOnUpdate);
1409 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, repoSession,
1410 oldRefNameOnUpdate, newRefNameOnUpdate, getRefPropName());
1412 // Finished so log a message.
1413 if (logger.isDebugEnabled()) {
1414 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
1419 protected boolean hasRefNameUpdate() {
1420 return (newRefNameOnUpdate != null && oldRefNameOnUpdate != null);
1423 protected String getRefNameUpdate() {
1424 String result = null;
1426 if (hasRefNameUpdate() == true) {
1427 result = newRefNameOnUpdate;
1428 if (logger.isDebugEnabled() == true) {
1429 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1430 newRefNameOnUpdate, oldRefNameOnUpdate));
1438 * Note: The Vocabulary document handler overrides this method.
1440 protected String getRefPropName() {
1441 return ServiceBindingUtils.AUTH_REF_PROP;