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.context.JaxRsContext;
54 import org.collectionspace.services.common.context.MultipartServiceContext;
55 import org.collectionspace.services.common.context.ServiceBindingUtils;
56 import org.collectionspace.services.common.context.ServiceContext;
57 import org.collectionspace.services.common.document.BadRequestException;
58 import org.collectionspace.services.common.document.DocumentException;
59 import org.collectionspace.services.common.document.DocumentUtils;
60 import org.collectionspace.services.common.document.DocumentWrapper;
61 import org.collectionspace.services.common.document.DocumentFilter;
62 import org.collectionspace.services.client.IRelationsManager;
63 import org.collectionspace.services.common.relation.RelationResource;
64 import org.collectionspace.services.common.repository.RepositoryClient;
65 import org.collectionspace.services.common.security.SecurityUtils;
66 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
67 import org.collectionspace.services.common.api.CommonAPI;
68 import org.collectionspace.services.common.api.RefNameUtils;
69 import org.collectionspace.services.common.api.Tools;
70 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
71 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
72 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
73 import org.collectionspace.services.config.service.DocHandlerParams;
74 import org.collectionspace.services.config.service.ListResultField;
75 import org.collectionspace.services.config.service.ObjectPartType;
76 import org.collectionspace.services.config.service.ServiceBindingType;
77 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
78 import org.collectionspace.services.relation.RelationsCommon;
79 import org.collectionspace.services.relation.RelationsCommonList;
80 import org.collectionspace.services.relation.RelationsDocListItem;
81 import org.collectionspace.services.relation.RelationshipType;
82 import org.dom4j.Element;
84 import org.nuxeo.ecm.core.api.DocumentModel;
85 import org.nuxeo.ecm.core.api.DocumentModelList;
86 import org.nuxeo.ecm.core.api.model.PropertyException;
87 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
93 * RemoteDocumentModelHandler
95 * $LastChangedRevision: $
100 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
101 extends DocumentModelHandler<T, TL> {
104 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
105 private final static String CR = "\r\n";
107 protected String oldRefNameOnUpdate = null;
108 protected String newRefNameOnUpdate = null;
111 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
114 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
115 if (ctx instanceof MultipartServiceContext) {
116 super.setServiceContext(ctx);
118 throw new IllegalArgumentException("setServiceContext requires instance of "
119 + MultipartServiceContext.class.getName());
124 * Returns the document handler parameters that were loaded at startup from the
125 * tenant bindings config file.
127 public DocHandlerParams.Params getDocHandlerParams() throws DocumentException {
128 MultipartServiceContext sc = (MultipartServiceContext) getServiceContext();
129 ServiceBindingType sb = sc.getServiceBinding();
130 DocHandlerParams dhb = sb.getDocHandlerParams();
131 if (dhb != null && dhb.getParams() != null) {
132 return dhb.getParams();
134 throw new DocumentException("No DocHandlerParams configured for: "
139 public boolean supportsHierarchy() {
142 DocHandlerParams.Params params = null;
144 params = getDocHandlerParams();
145 } catch (DocumentException e) {
146 // TODO Auto-generated catch block
147 logger.error(String.format("Could not get document handler params for class %s", this.getClass().getName()), e);
149 result = params.isSupportsHierarchy();
155 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
157 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
161 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
162 super.completeCreate(wrapDoc);
163 if (supportsHierarchy() == true) {
164 handleRelationsPayload(wrapDoc, false);
169 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
172 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
173 DocumentModel docModel = wrapDoc.getWrappedObject();
174 //return at least those document part(s) that were received
175 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
176 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
177 PoxPayloadIn input = ctx.getInput();
179 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
180 for (PayloadInputPart part : inputParts) {
181 String partLabel = part.getLabel();
183 ObjectPartType partMeta = partsMetaMap.get(partLabel);
184 // CSPACE-4030 - generates NPE if the part is missing.
186 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
187 if(unQObjectProperties!=null) {
188 addOutputPart(unQObjectProperties, partLabel, partMeta);
191 } catch (Throwable t){
192 logger.error("Unable to addOutputPart: "+partLabel
193 +" in serviceContextPath: "+this.getServiceContextPath()
194 +" with URI: "+this.getServiceContext().getUriInfo().getPath()
199 if (logger.isWarnEnabled() == true) {
200 logger.warn("MultipartInput part was null for document id = " +
205 if (supportsHierarchy() == true) {
206 handleRelationsPayload(wrapDoc, true);
207 handleItemRefNameReferenceUpdate();
212 * Adds the output part.
214 * @param unQObjectProperties the un q object properties
215 * @param schema the schema
216 * @param partMeta the part meta
217 * @throws Exception the exception
218 * MediaType.APPLICATION_XML_TYPE
220 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
222 Element doc = DocumentUtils.buildDocument(partMeta, schema,
223 unQObjectProperties);
224 if (logger.isTraceEnabled() == true) {
225 logger.trace(doc.asXML());
227 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
228 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
232 * Extract paging info.
234 * @param commonsList the commons list
236 * @throws Exception the exception
238 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
240 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
242 DocumentFilter docFilter = this.getDocumentFilter();
243 long pageSize = docFilter.getPageSize();
244 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
245 // set the page size and page number
246 commonList.setPageNum(pageNum);
247 commonList.setPageSize(pageSize);
248 DocumentModelList docList = wrapDoc.getWrappedObject();
249 // Set num of items in list. this is useful to our testing framework.
250 commonList.setItemsInPage(docList.size());
251 // set the total result size
252 commonList.setTotalItems(docList.totalSize());
254 return (TL) commonList;
258 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
261 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
264 DocumentModel docModel = wrapDoc.getWrappedObject();
265 String[] schemas = docModel.getDeclaredSchemas();
266 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
267 for (String schema : schemas) {
268 ObjectPartType partMeta = partsMetaMap.get(schema);
269 if (partMeta == null) {
270 continue; // unknown part, ignore
272 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
273 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
274 addExtraCoreValues(docModel, unQObjectProperties);
276 addOutputPart(unQObjectProperties, schema, partMeta);
279 if (supportsHierarchy() == true) {
280 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
281 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
282 if (Tools.isTrue(showSiblings)) {
283 showSiblings(wrapDoc, ctx);
284 return; // actual result is returned on ctx.addOutputPart();
287 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
288 if (Tools.isTrue(showRelations)) {
289 showRelations(wrapDoc, ctx);
290 return; // actual result is returned on ctx.addOutputPart();
293 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
294 if (Tools.isTrue(showAllRelations)) {
295 showAllRelations(wrapDoc, ctx);
296 return; // actual result is returned on ctx.addOutputPart();
300 addAccountPermissionsPart();
303 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
305 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
308 private void addAccountPermissionsPart() throws Exception {
309 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
312 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
313 String currentServiceName = ctx.getServiceName();
314 String workflowSubResource = "/";
315 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
316 if (jaxRsContext != null) {
317 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
318 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
320 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
322 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
323 currentServiceName, workflowSubResource);
324 org.collectionspace.services.authorization.ObjectFactory objectFactory =
325 new org.collectionspace.services.authorization.ObjectFactory();
326 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
327 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap);
328 ctx.addOutputPart(accountPermissionPart);
334 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
337 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
339 //TODO filling extension parts should be dynamic
340 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
341 //not an ideal way of populating objects.
342 DocumentModel docModel = wrapDoc.getWrappedObject();
343 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
344 PoxPayloadIn input = ctx.getInput();
345 if (input.getParts().isEmpty()) {
346 String msg = "No payload found!";
347 logger.error(msg + "Ctx=" + getServiceContext().toString());
348 throw new BadRequestException(msg);
351 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
353 //iterate over parts received and fill those parts
354 List<PayloadInputPart> inputParts = input.getParts();
355 for (PayloadInputPart part : inputParts) {
357 String partLabel = part.getLabel();
358 if (partLabel == null) {
359 String msg = "Part label is missing or empty!";
360 logger.error(msg + "Ctx=" + getServiceContext().toString());
361 throw new BadRequestException(msg);
364 //skip if the part is not in metadata
365 ObjectPartType partMeta = partsMetaMap.get(partLabel);
366 if (partMeta == null) {
369 fillPart(part, docModel, partMeta, action, ctx);
375 * fillPart fills an XML part into given document model
376 * @param part to fill
377 * @param docModel for the given object
378 * @param partMeta metadata for the object to fill
381 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
382 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
384 //check if this is an xml part
385 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
386 Element element = part.getElementBody();
387 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
388 if (action == Action.UPDATE) {
389 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
391 docModel.setProperties(partMeta.getLabel(), objectProps);
396 * Filters out read only properties, so they cannot be set on update.
397 * TODO: add configuration support to do this generally
398 * @param objectProps the properties parsed from the update payload
399 * @param partMeta metadata for the object to fill
401 public void filterReadOnlyPropertiesForPart(
402 Map<String, Object> objectProps, ObjectPartType partMeta) {
403 // Should add in logic to filter most of the core items on update
404 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
405 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
406 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
407 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
408 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
409 // Note that the updatedAt/updatedBy fields are set internally
410 // in DocumentModelHandler.handleCoreValues().
415 * extractPart extracts an XML object from given DocumentModel
417 * @param schema of the object to extract
418 * @param partMeta metadata for the object to extract
421 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
423 return extractPart(docModel, schema, (Map<String, Object>)null);
427 * extractPart extracts an XML object from given DocumentModel
429 * @param schema of the object to extract
430 * @param partMeta metadata for the object to extract
434 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
436 return extractPart(docModel, schema, partMeta, null);
440 * extractPart extracts an XML object from given DocumentModel
442 * @param schema of the object to extract
443 * @param partMeta metadata for the object to extract
446 protected Map<String, Object> extractPart(
447 DocumentModel docModel,
449 Map<String, Object> addToMap)
451 Map<String, Object> result = null;
453 Map<String, Object> objectProps = docModel.getProperties(schema);
454 if (objectProps != null) {
455 //unqualify properties before sending the doc over the wire (to save bandwidh)
456 //FIXME: is there a better way to avoid duplication of a Map/Collection?
457 Map<String, Object> unQObjectProperties =
458 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
459 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
460 for (Entry<String, Object> entry : qualifiedEntries) {
461 String unqProp = getUnQProperty(entry.getKey());
462 unQObjectProperties.put(unqProp, entry.getValue());
464 result = unQObjectProperties;
471 * extractPart extracts an XML object from given DocumentModel
473 * @param schema of the object to extract
474 * @param partMeta metadata for the object to extract
478 protected Map<String, Object> extractPart(
479 DocumentModel docModel, String schema, ObjectPartType partMeta,
480 Map<String, Object> addToMap)
482 Map<String, Object> result = null;
484 result = this.extractPart(docModel, schema, addToMap);
490 public String getStringPropertyFromDoc(
493 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
494 RepositoryInstance repoSession = null;
495 boolean releaseRepoSession = false;
496 String returnValue = null;
499 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
500 repoSession = this.getRepositorySession();
501 if (repoSession == null) {
502 repoSession = repoClient.getRepositorySession();
503 releaseRepoSession = true;
507 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
508 DocumentModel docModel = wrapper.getWrappedObject();
509 returnValue = (String) docModel.getPropertyValue(propertyXPath);
510 } catch (PropertyException pe) {
512 } catch (DocumentException de) {
514 } catch (Exception e) {
515 if (logger.isDebugEnabled()) {
516 logger.debug("Caught exception ", e);
518 throw new DocumentException(e);
520 if (releaseRepoSession && repoSession != null) {
521 repoClient.releaseRepositorySession(repoSession);
524 } catch (Exception e) {
525 if (logger.isDebugEnabled()) {
526 logger.debug("Caught exception ", e);
528 throw new DocumentException(e);
532 if (logger.isWarnEnabled() == true) {
533 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
542 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
545 public AuthorityRefList getAuthorityRefs(
547 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
549 AuthorityRefList authRefList = new AuthorityRefList();
550 AbstractCommonList commonList = (AbstractCommonList) authRefList;
552 DocumentFilter docFilter = this.getDocumentFilter();
553 long pageSize = docFilter.getPageSize();
554 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
555 // set the page size and page number
556 commonList.setPageNum(pageNum);
557 commonList.setPageSize(pageSize);
559 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
562 int iFirstToUse = (int)(pageSize*pageNum);
563 int nFoundInPage = 0;
566 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
567 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
569 boolean releaseRepoSession = false;
570 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
571 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
572 RepositoryInstance repoSession = this.getRepositorySession();
573 if (repoSession == null) {
574 repoSession = repoClient.getRepositorySession(ctx);
575 releaseRepoSession = true;
579 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
580 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
581 // Slightly goofy pagination support - how many refs do we expect from one object?
582 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
583 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
584 if(appendToAuthRefsList(ari, list)) {
593 if (releaseRepoSession == true) {
594 repoClient.releaseRepositorySession(ctx, repoSession);
598 // Set num of items in list. this is useful to our testing framework.
599 commonList.setItemsInPage(nFoundInPage);
600 // set the total result size
601 commonList.setTotalItems(nFoundTotal);
603 } catch (PropertyException pe) {
604 String msg = "Attempted to retrieve value for invalid or missing authority field. "
605 + "Check authority field properties in tenant bindings.";
606 logger.warn(msg, pe);
608 } catch (Exception e) {
609 if (logger.isDebugEnabled()) {
610 logger.debug("Caught exception in getAuthorityRefs", e);
612 Response response = Response.status(
613 Response.Status.INTERNAL_SERVER_ERROR).entity(
614 "Failed to retrieve authority references").type(
615 "text/plain").build();
616 throw new WebApplicationException(response);
622 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
623 List<AuthorityRefList.AuthorityRefItem> list)
625 String fieldName = ari.getQualifiedDisplayName();
627 String refNameValue = (String)ari.getProperty().getValue();
628 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
629 if(item!=null) { // ignore garbage values.
633 } catch(PropertyException pe) {
634 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
639 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
641 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
643 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
644 ilistItem.setRefName(refName);
645 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
646 ilistItem.setItemDisplayName(termInfo.displayName);
647 ilistItem.setSourceField(authRefFieldName);
648 ilistItem.setUri(termInfo.getRelativeUri());
649 } catch (Exception e) {
650 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
657 * Returns the primary value from a list of values.
659 * Assumes that the first value is the primary value.
660 * This assumption may change when and if the primary value
661 * is identified explicitly.
663 * @param values a list of values.
664 * @param propertyName the name of a property through
665 * which the value can be extracted.
666 * @return the primary value.
667 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
668 String primaryValue = "";
669 if (values == null || values.size() == 0) {
672 Object value = values.get(0);
673 if (value instanceof String) {
675 primaryValue = (String) value;
677 // Multivalue group of fields
678 } else if (value instanceof Map) {
680 Map map = (Map) value;
681 if (map.values().size() > 0) {
682 if (map.get(propertyName) != null) {
683 primaryValue = (String) map.get(propertyName);
688 logger.warn("Unexpected type for property " + propertyName
689 + " in multivalue list: not String or Map.");
696 * Gets a simple property from the document.
698 * For completeness, as this duplicates DocumentModel method.
700 * @param docModel The document model to get info from
701 * @param schema The name of the schema (part)
702 * @param propertyName The simple scalar property type
703 * @return property value as String
705 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
706 String xpath = "/"+schema+":"+propName;
708 return (String)docModel.getPropertyValue(xpath);
709 } catch(PropertyException pe) {
710 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
711 +pe.getLocalizedMessage());
712 } catch(ClassCastException cce) {
713 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
714 +cce.getLocalizedMessage());
715 } catch(Exception e) {
716 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
717 +e.getLocalizedMessage());
722 * Gets first of a repeating list of scalar values, as a String, from the document.
724 * @param docModel The document model to get info from
725 * @param schema The name of the schema (part)
726 * @param listName The name of the scalar list property
727 * @return first value in list, as a String, or empty string if the list is empty
729 protected String getFirstRepeatingStringProperty(
730 DocumentModel docModel, String schema, String listName) {
731 String xpath = "/"+schema+":"+listName+"/[0]";
733 return (String)docModel.getPropertyValue(xpath);
734 } catch(PropertyException pe) {
735 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
736 +pe.getLocalizedMessage());
737 } catch(IndexOutOfBoundsException ioobe) {
738 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
739 return ""; // gracefully handle missing elements
740 } catch(ClassCastException cce) {
741 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
742 +cce.getLocalizedMessage());
743 } catch(Exception e) {
744 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
745 +e.getLocalizedMessage());
751 * Gets first of a repeating list of scalar values, as a String, from the document.
753 * @param docModel The document model to get info from
754 * @param schema The name of the schema (part)
755 * @param listName The name of the scalar list property
756 * @return first value in list, as a String, or empty string if the list is empty
758 protected String getStringValueInPrimaryRepeatingComplexProperty(
759 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
760 String result = null;
762 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
764 result = (String)docModel.getPropertyValue(xpath);
765 } catch(PropertyException pe) {
766 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
767 +pe.getLocalizedMessage());
768 } catch(IndexOutOfBoundsException ioobe) {
769 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
770 result = ""; // gracefully handle missing elements
771 } catch(ClassCastException cce) {
772 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
773 +cce.getLocalizedMessage());
774 } catch(Exception e) {
775 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
776 +e.getLocalizedMessage());
783 * Gets XPath value from schema. Note that only "/" and "[n]" are
784 * supported for xpath. Can omit grouping elements for repeating complex types,
785 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
786 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
787 * If there are no entries for a list of scalars or for a list of complex types,
788 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
789 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
790 * that many elements in the list.
791 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
793 * @param docModel The document model to get info from
794 * @param schema The name of the schema (part)
795 * @param xpath The XPath expression (without schema prefix)
796 * @return value the indicated property value as a String
798 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
799 String schema, ListResultField field) {
800 Object result = null;
802 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
808 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
812 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
814 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
816 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
818 sb.append(item.getPredicate());
820 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
824 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
825 StringBuilder sb = new StringBuilder();
827 if (list.size() > 0) {
828 sb.append("=========== " + label + " ==========" + CR);
830 for (RelationsCommonList.RelationListItem item : list) {
831 itemToString(sb, "== ", item);
834 return sb.toString();
837 /** @return null on parent not found
839 protected String getParentCSID(String thisCSID) throws Exception {
840 String parentCSID = null;
842 String predicate = RelationshipType.HAS_BROADER.value();
843 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
844 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
845 if (parentList != null) {
846 if (parentList.size() == 0) {
849 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
850 parentCSID = relationListItem.getObjectCsid();
853 } catch (Exception e) {
854 logger.error("Could not find parent for this: " + thisCSID, e);
859 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
860 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
864 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
865 MultipartServiceContext ctx) throws Exception {
866 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
867 String parentCSID = getParentCSID(thisCSID);
868 if (parentCSID == null) {
869 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
873 String predicate = RelationshipType.HAS_BROADER.value();
874 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
875 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
877 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
880 RelationsCommonList.RelationListItem item = null;
881 for (RelationsCommonList.RelationListItem sibling : siblingList) {
882 if (thisCSID.equals(sibling.getSubjectCsid())) {
883 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.
886 //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.
887 for (RelationsCommonList.RelationListItem self : toRemoveList) {
888 removeFromList(siblingList, self);
891 long siblingSize = siblingList.size();
892 siblingListOuter.setTotalItems(siblingSize);
893 siblingListOuter.setItemsInPage(siblingSize);
894 if(logger.isTraceEnabled()) {
895 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
896 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
899 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
900 ctx.addOutputPart(relationsPart);
903 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
904 MultipartServiceContext ctx) throws Exception {
905 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
907 String predicate = RelationshipType.HAS_BROADER.value();
908 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
909 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
911 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
912 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
914 if(logger.isTraceEnabled()) {
915 String dump = dumpLists(thisCSID, parentList, childrenList, null);
916 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
919 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
920 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
921 //Not optimal, but that's the current design spec.
923 for (RelationsCommonList.RelationListItem parent : parentList) {
924 childrenList.add(parent);
927 long childrenSize = childrenList.size();
928 childrenListOuter.setTotalItems(childrenSize);
929 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
931 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
932 ctx.addOutputPart(relationsPart);
935 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
936 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
938 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
939 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
941 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
942 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
944 if(logger.isTraceEnabled()) {
945 String dump = dumpLists(thisCSID, subjectList, objectList, null);
946 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
949 subjectList.addAll(objectList);
951 //now subjectList actually has records BOTH where thisCSID is subject and object.
952 long relatedSize = subjectList.size();
953 subjectListOuter.setTotalItems(relatedSize);
954 subjectListOuter.setItemsInPage(relatedSize);
956 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
957 ctx.addOutputPart(relationsPart);
960 private String dumpLists(String itemCSID,
961 List<RelationsCommonList.RelationListItem> parentList,
962 List<RelationsCommonList.RelationListItem> childList,
963 List<RelationsCommonList.RelationListItem> actionList) {
964 StringBuilder sb = new StringBuilder();
965 sb.append("itemCSID: " + itemCSID + CR);
966 if(parentList!=null) {
967 sb.append(dumpList(parentList, "parentList"));
969 if(childList!=null) {
970 sb.append(dumpList(childList, "childList"));
972 if(actionList!=null) {
973 sb.append(dumpList(actionList, "actionList"));
975 return sb.toString();
978 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
979 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
980 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
981 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
982 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
983 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
984 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
986 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
987 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
988 return relationsCommonList;
990 //============================= END TODO refactor ==========================
992 // this method calls the RelationResource to have it create the relations and persist them.
993 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
994 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
995 for (RelationsCommonList.RelationListItem item : inboundList) {
996 RelationsCommon rc = new RelationsCommon();
997 //rc.setCsid(item.getCsid());
998 //todo: assignTo(item, rc);
999 RelationsDocListItem itemSubject = item.getSubject();
1000 RelationsDocListItem itemObject = item.getObject();
1002 // Set at least one of CSID and refName for Subject and Object
1003 // Either value might be null for for each of Subject and Object
1004 String subjectCsid = itemSubject.getCsid();
1005 rc.setSubjectCsid(subjectCsid);
1007 String objCsid = itemObject.getCsid();
1008 rc.setObjectCsid(objCsid);
1010 rc.setSubjectRefName(itemSubject.getRefName());
1011 rc.setObjectRefName(itemObject.getRefName());
1013 rc.setRelationshipType(item.getPredicate());
1014 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1015 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1017 // This is superfluous, since it will be fetched by the Relations Create logic.
1018 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1019 rc.setObjectDocumentType(itemObject.getDocumentType());
1021 // This is superfluous, since it will be fetched by the Relations Create logic.
1022 rc.setSubjectUri(itemSubject.getUri());
1023 rc.setObjectUri(itemObject.getUri());
1024 // May not have the info here. Only really require CSID or refName.
1025 // Rest is handled in the Relation create mechanism
1026 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1028 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1029 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1030 payloadOut.addPart(outputPart);
1031 RelationResource relationResource = new RelationResource();
1032 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1033 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1037 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1038 // But item1 must not be sparse
1039 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1040 if (item1 == null || item2 == null) {
1043 RelationsDocListItem subj1 = item1.getSubject();
1044 RelationsDocListItem subj2 = item2.getSubject();
1045 RelationsDocListItem obj1 = item1.getObject();
1046 RelationsDocListItem obj2 = item2.getObject();
1047 String subj1Csid = subj1.getCsid();
1048 String subj2Csid = subj2.getCsid();
1049 String subj1RefName = subj1.getRefName();
1050 String subj2RefName = subj2.getRefName();
1052 String obj1Csid = obj1.getCsid();
1053 String obj2Csid = obj2.getCsid();
1054 String obj1RefName = obj1.getRefName();
1055 String obj2RefName = obj2.getRefName();
1058 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1059 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1060 // predicate is proper, but still allow relationshipType
1061 && (item1.getPredicate().equals(item2.getPredicate())
1062 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1063 // Allow missing docTypes, so long as they do not conflict
1064 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1065 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1069 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1070 // But the list items must not be sparse
1071 private RelationsCommonList.RelationListItem findInList(
1072 List<RelationsCommonList.RelationListItem> list,
1073 RelationsCommonList.RelationListItem item) {
1074 RelationsCommonList.RelationListItem foundItem = null;
1075 for (RelationsCommonList.RelationListItem listItem : list) {
1076 if (itemsEqual(listItem, item)) { //equals must be defined, else
1077 foundItem = listItem;
1084 /** updateRelations strategy:
1087 go through inboundList, remove anything from childList that matches from childList
1088 go through inboundList, remove anything from parentList that matches from parentList
1089 go through parentList, delete all remaining
1090 go through childList, delete all remaining
1091 go through actionList, add all remaining.
1092 check for duplicate children
1093 check for more than one parent.
1095 inboundList parentList childList actionList
1096 ---------------- --------------- ---------------- ----------------
1097 child-a parent-c child-a child-b
1098 child-b parent-d child-c
1103 private RelationsCommonList updateRelations(
1104 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1106 if (logger.isTraceEnabled()) {
1107 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1109 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1111 return null; //nothing to do--they didn't send a list of relations.
1113 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1114 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1115 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1116 List<RelationsCommonList.RelationListItem> childList = null;
1117 List<RelationsCommonList.RelationListItem> parentList = null;
1118 DocumentModel docModel = wrapDoc.getWrappedObject();
1119 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
1121 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1122 //Do magic replacement of ${itemCSID} and fix URI's.
1123 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1125 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1126 UriInfo uriInfo = ctx.getUriInfo();
1127 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1130 //Run getList() once as sent to get childListOuter:
1131 String predicate = RelationshipType.HAS_BROADER.value();
1132 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1133 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1134 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1135 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1136 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1138 RelationResource relationResource = new RelationResource();
1139 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1141 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1142 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1143 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1144 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1145 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1148 childList = childListOuter.getRelationListItem();
1149 parentList = parentListOuter.getRelationListItem();
1151 if (parentList.size() > 1) {
1152 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1155 if (logger.isTraceEnabled()) {
1156 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1160 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1161 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1162 // and so the CSID for those may be null
1163 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1164 // Look for parents and children
1165 if(itemCSID.equals(inboundItem.getObject().getCsid())
1166 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1167 //then this is an item that says we have a child. That child is inboundItem
1168 RelationsCommonList.RelationListItem childItem =
1169 (childList == null) ? null : findInList(childList, inboundItem);
1170 if (childItem != null) {
1171 if (logger.isTraceEnabled()) {
1172 StringBuilder sb = new StringBuilder();
1173 itemToString(sb, "== Child: ", childItem);
1174 logger.trace("Found inboundChild in current child list: " + sb.toString());
1176 removeFromList(childList, childItem); //exists, just take it off delete list
1178 if (logger.isTraceEnabled()) {
1179 StringBuilder sb = new StringBuilder();
1180 itemToString(sb, "== Child: ", inboundItem);
1181 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1183 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1184 String newChildCsid = inboundItem.getSubject().getCsid();
1185 if(newChildCsid == null) {
1186 String newChildRefName = inboundItem.getSubject().getRefName();
1187 if(newChildRefName==null) {
1188 throw new RuntimeException("Child with no CSID or refName!");
1190 if (logger.isTraceEnabled()) {
1191 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1193 DocumentModel newChildDocModel =
1194 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1195 newChildRefName, getServiceContext().getResourceMap());
1196 newChildCsid = getCsid(newChildDocModel);
1198 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1201 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1202 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1203 //then this is an item that says we have a parent. inboundItem is that parent.
1204 RelationsCommonList.RelationListItem parentItem =
1205 (parentList == null) ? null : findInList(parentList, inboundItem);
1206 if (parentItem != null) {
1207 removeFromList(parentList, parentItem); //exists, just take it off delete list
1209 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1212 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1215 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1218 if (logger.isTraceEnabled()) {
1219 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1220 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1223 if (logger.isTraceEnabled()) {
1224 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1225 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1227 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1228 deleteRelations(childList, ctx, "childList");
1230 if (logger.isTraceEnabled()) {
1231 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1232 + actionList.size() + " new parents and children.");
1234 createRelations(actionList, ctx);
1235 if (logger.isTraceEnabled()) {
1236 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1238 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1239 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1240 return relationsCommonListBody;
1243 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1244 * and sets URI correctly for related items.
1245 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1247 protected void fixupInboundListItems(ServiceContext ctx,
1248 List<RelationsCommonList.RelationListItem> inboundList,
1249 DocumentModel docModel,
1250 String itemCSID) throws Exception {
1251 String thisURI = this.getUri(docModel);
1252 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1253 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1254 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1255 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1256 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1258 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1259 inboundItem.setObjectCsid(itemCSID);
1260 inboundItemObject.setCsid(itemCSID);
1261 //inboundItemObject.setUri(getUri(docModel));
1264 String objectCsid = inboundItemObject.getCsid();
1265 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1266 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1267 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1268 inboundItemObject.setUri(uri); //CSPACE-4037
1271 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1273 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1274 inboundItem.setSubjectCsid(itemCSID);
1275 inboundItemSubject.setCsid(itemCSID);
1276 //inboundItemSubject.setUri(getUri(docModel));
1279 String subjectCsid = inboundItemSubject.getCsid();
1280 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1281 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1282 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1283 inboundItemSubject.setUri(uri); //CSPACE-4037
1286 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1291 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1292 MultivaluedMap<String, String> queryParams, String childCSID) {
1293 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1294 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1295 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1296 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1298 RelationResource relationResource = new RelationResource();
1299 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1300 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1301 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1302 deleteRelations(parentList, ctx, "parentList-delete");
1305 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1306 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1309 for (RelationsCommonList.RelationListItem item : list) {
1310 RelationResource relationResource = new RelationResource();
1311 if(logger.isTraceEnabled()) {
1312 StringBuilder sb = new StringBuilder();
1313 itemToString(sb, "==== TO DELETE: ", item);
1314 logger.trace(sb.toString());
1316 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1317 if (logger.isDebugEnabled()) {
1318 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1321 } catch (Throwable t) {
1322 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1327 // Note that we must do this after we have completed the Update, so that the repository has the
1328 // info for the item itself. The relations code must call into the repo to get info for each end.
1329 // This could be optimized to pass in the parent docModel, since it will often be one end.
1330 // Nevertheless, we should complete the item save before we do work on the relations, especially
1331 // since a save on Create might fail, and we would not want to create relations for something
1332 // that may not be created...
1333 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1334 ServiceContext ctx = getServiceContext();
1335 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1336 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1337 String itemCsid = documentModel.getName();
1339 //Updates relations part
1340 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1342 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
1343 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1345 //now we add part for relations list
1346 //ServiceContext ctx = getServiceContext();
1347 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1348 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1352 * Checks to see if the refName has changed, and if so,
1353 * uses utilities to find all references and update them.
1356 protected void handleItemRefNameReferenceUpdate() throws Exception {
1357 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
1358 // We have work to do.
1359 if (logger.isDebugEnabled()) {
1360 String eol = System.getProperty("line.separator");
1361 logger.debug("Need to find and update references to Item." + eol
1362 + " Old refName" + oldRefNameOnUpdate + eol
1363 + " New refName" + newRefNameOnUpdate);
1365 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1366 RepositoryClient repoClient = getRepositoryClient(ctx);
1367 String refNameProp = getRefPropName();
1369 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
1370 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
1371 if (logger.isDebugEnabled()) {
1372 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
1378 * Note: The Vocabulary document handler overrides this method.
1380 protected String getRefPropName() {
1381 return ServiceBindingUtils.AUTH_REF_PROP;