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 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
140 return getRefnameDisplayName(docWrapper.getWrappedObject());
144 * Used get the order by field for list results if one is not specified with an HTTP query param.
147 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#getOrderByField()
150 protected String getOrderByField() {
151 String result = null;
153 DocHandlerParams.Params params = null;
155 params = getDocHandlerParams();
156 ListResultField field = params.getRefnameDisplayNameField();
157 result = field.getSchema() + ":" + field.getXpath();
158 } catch (Exception e) {
159 if (logger.isWarnEnabled()) {
160 logger.warn(String.format("Call failed to getOrderByField() for class %s", this.getClass().getName()));
167 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
168 String result = null;
170 DocHandlerParams.Params params = null;
172 params = getDocHandlerParams();
173 ListResultField field = params.getRefnameDisplayNameField();
175 String schema = field.getSchema();
176 if (schema == null || schema.trim().isEmpty()) {
177 schema = getServiceContext().getCommonPartLabel();
180 result = getStringValue(docModel, schema, field);
181 } catch (Exception e) {
182 if (logger.isWarnEnabled()) {
183 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
191 public boolean supportsHierarchy() {
192 boolean result = false;
194 DocHandlerParams.Params params = null;
196 params = getDocHandlerParams();
197 Boolean bool = params.isSupportsHierarchy();
199 result = bool.booleanValue();
201 } catch (DocumentException e) {
202 // TODO Auto-generated catch block
203 logger.error(String.format("Could not get document handler params for class %s", this.getClass().getName()), e);
210 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
212 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
216 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
217 super.completeCreate(wrapDoc);
218 if (supportsHierarchy() == true) {
219 handleRelationsPayload(wrapDoc, false);
224 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
227 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
228 DocumentModel docModel = wrapDoc.getWrappedObject();
229 //return at least those document part(s) that were received
230 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
231 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
232 PoxPayloadIn input = ctx.getInput();
234 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
235 for (PayloadInputPart part : inputParts) {
236 String partLabel = part.getLabel();
238 ObjectPartType partMeta = partsMetaMap.get(partLabel);
239 // CSPACE-4030 - generates NPE if the part is missing.
241 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
242 if(unQObjectProperties!=null) {
243 addOutputPart(unQObjectProperties, partLabel, partMeta);
246 } catch (Throwable t){
247 logger.error("Unable to addOutputPart: "+partLabel
248 +" in serviceContextPath: "+this.getServiceContextPath()
249 +" with URI: "+this.getServiceContext().getUriInfo().getPath()
254 if (logger.isWarnEnabled() == true) {
255 logger.warn("MultipartInput part was null for document id = " +
260 if (supportsHierarchy() == true) {
261 handleRelationsPayload(wrapDoc, true);
262 handleItemRefNameReferenceUpdate();
267 * Adds the output part.
269 * @param unQObjectProperties the un q object properties
270 * @param schema the schema
271 * @param partMeta the part meta
272 * @throws Exception the exception
273 * MediaType.APPLICATION_XML_TYPE
275 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
277 Element doc = DocumentUtils.buildDocument(partMeta, schema,
278 unQObjectProperties);
279 if (logger.isTraceEnabled() == true) {
280 logger.trace(doc.asXML());
282 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
283 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
287 * Extract paging info.
289 * @param commonsList the commons list
291 * @throws Exception the exception
293 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
295 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
297 DocumentFilter docFilter = this.getDocumentFilter();
298 long pageSize = docFilter.getPageSize();
299 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
300 // set the page size and page number
301 commonList.setPageNum(pageNum);
302 commonList.setPageSize(pageSize);
303 DocumentModelList docList = wrapDoc.getWrappedObject();
304 // Set num of items in list. this is useful to our testing framework.
305 commonList.setItemsInPage(docList.size());
306 // set the total result size
307 commonList.setTotalItems(docList.totalSize());
309 return (TL) commonList;
313 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
316 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
319 DocumentModel docModel = wrapDoc.getWrappedObject();
320 String[] schemas = docModel.getDeclaredSchemas();
321 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
322 for (String schema : schemas) {
323 ObjectPartType partMeta = partsMetaMap.get(schema);
324 if (partMeta == null) {
325 continue; // unknown part, ignore
327 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
328 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
329 addExtraCoreValues(docModel, unQObjectProperties);
331 addOutputPart(unQObjectProperties, schema, partMeta);
334 if (supportsHierarchy() == true) {
335 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
336 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
337 if (Tools.isTrue(showSiblings)) {
338 showSiblings(wrapDoc, ctx);
339 return; // actual result is returned on ctx.addOutputPart();
342 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
343 if (Tools.isTrue(showRelations)) {
344 showRelations(wrapDoc, ctx);
345 return; // actual result is returned on ctx.addOutputPart();
348 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
349 if (Tools.isTrue(showAllRelations)) {
350 showAllRelations(wrapDoc, ctx);
351 return; // actual result is returned on ctx.addOutputPart();
355 addAccountPermissionsPart();
358 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
360 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
363 private void addAccountPermissionsPart() throws Exception {
364 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
367 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
368 String currentServiceName = ctx.getServiceName();
369 String workflowSubResource = "/";
370 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
371 if (jaxRsContext != null) {
372 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
373 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
375 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
377 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
378 currentServiceName, workflowSubResource);
379 org.collectionspace.services.authorization.ObjectFactory objectFactory =
380 new org.collectionspace.services.authorization.ObjectFactory();
381 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
382 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap);
383 ctx.addOutputPart(accountPermissionPart);
389 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
392 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
394 //TODO filling extension parts should be dynamic
395 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
396 //not an ideal way of populating objects.
397 DocumentModel docModel = wrapDoc.getWrappedObject();
398 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
399 PoxPayloadIn input = ctx.getInput();
400 if (input.getParts().isEmpty()) {
401 String msg = "No payload found!";
402 logger.error(msg + "Ctx=" + getServiceContext().toString());
403 throw new BadRequestException(msg);
406 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
408 //iterate over parts received and fill those parts
409 List<PayloadInputPart> inputParts = input.getParts();
410 for (PayloadInputPart part : inputParts) {
412 String partLabel = part.getLabel();
413 if (partLabel == null) {
414 String msg = "Part label is missing or empty!";
415 logger.error(msg + "Ctx=" + getServiceContext().toString());
416 throw new BadRequestException(msg);
419 //skip if the part is not in metadata
420 ObjectPartType partMeta = partsMetaMap.get(partLabel);
421 if (partMeta == null) {
424 fillPart(part, docModel, partMeta, action, ctx);
430 * fillPart fills an XML part into given document model
431 * @param part to fill
432 * @param docModel for the given object
433 * @param partMeta metadata for the object to fill
436 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
437 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
439 //check if this is an xml part
440 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
441 Element element = part.getElementBody();
442 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
443 if (action == Action.UPDATE) {
444 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
446 docModel.setProperties(partMeta.getLabel(), objectProps);
451 * Filters out read only properties, so they cannot be set on update.
452 * TODO: add configuration support to do this generally
453 * @param objectProps the properties parsed from the update payload
454 * @param partMeta metadata for the object to fill
456 public void filterReadOnlyPropertiesForPart(
457 Map<String, Object> objectProps, ObjectPartType partMeta) {
458 // Should add in logic to filter most of the core items on update
459 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
460 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
461 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
462 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
463 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
464 // Note that the updatedAt/updatedBy fields are set internally
465 // in DocumentModelHandler.handleCoreValues().
470 * extractPart extracts an XML object from given DocumentModel
472 * @param schema of the object to extract
473 * @param partMeta metadata for the object to extract
476 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
478 return extractPart(docModel, schema, (Map<String, Object>)null);
482 * extractPart extracts an XML object from given DocumentModel
484 * @param schema of the object to extract
485 * @param partMeta metadata for the object to extract
489 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
491 return extractPart(docModel, schema, partMeta, null);
495 * extractPart extracts an XML object from given DocumentModel
497 * @param schema of the object to extract
498 * @param partMeta metadata for the object to extract
501 protected Map<String, Object> extractPart(
502 DocumentModel docModel,
504 Map<String, Object> addToMap)
506 Map<String, Object> result = null;
508 Map<String, Object> objectProps = docModel.getProperties(schema);
509 if (objectProps != null) {
510 //unqualify properties before sending the doc over the wire (to save bandwidh)
511 //FIXME: is there a better way to avoid duplication of a Map/Collection?
512 Map<String, Object> unQObjectProperties =
513 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
514 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
515 for (Entry<String, Object> entry : qualifiedEntries) {
516 String unqProp = getUnQProperty(entry.getKey());
517 unQObjectProperties.put(unqProp, entry.getValue());
519 result = unQObjectProperties;
526 * extractPart extracts an XML object from given DocumentModel
528 * @param schema of the object to extract
529 * @param partMeta metadata for the object to extract
533 protected Map<String, Object> extractPart(
534 DocumentModel docModel, String schema, ObjectPartType partMeta,
535 Map<String, Object> addToMap)
537 Map<String, Object> result = null;
539 result = this.extractPart(docModel, schema, addToMap);
545 public String getStringPropertyFromDoc(
548 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
549 RepositoryInstance repoSession = null;
550 boolean releaseRepoSession = false;
551 String returnValue = null;
554 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
555 repoSession = this.getRepositorySession();
556 if (repoSession == null) {
557 repoSession = repoClient.getRepositorySession();
558 releaseRepoSession = true;
562 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
563 DocumentModel docModel = wrapper.getWrappedObject();
564 returnValue = (String) docModel.getPropertyValue(propertyXPath);
565 } catch (PropertyException pe) {
567 } catch (DocumentException de) {
569 } catch (Exception e) {
570 if (logger.isDebugEnabled()) {
571 logger.debug("Caught exception ", e);
573 throw new DocumentException(e);
575 if (releaseRepoSession && repoSession != null) {
576 repoClient.releaseRepositorySession(repoSession);
579 } catch (Exception e) {
580 if (logger.isDebugEnabled()) {
581 logger.debug("Caught exception ", e);
583 throw new DocumentException(e);
587 if (logger.isWarnEnabled() == true) {
588 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
597 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
600 public AuthorityRefList getAuthorityRefs(
602 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
604 AuthorityRefList authRefList = new AuthorityRefList();
605 AbstractCommonList commonList = (AbstractCommonList) authRefList;
607 DocumentFilter docFilter = this.getDocumentFilter();
608 long pageSize = docFilter.getPageSize();
609 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
610 // set the page size and page number
611 commonList.setPageNum(pageNum);
612 commonList.setPageSize(pageSize);
614 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
617 int iFirstToUse = (int)(pageSize*pageNum);
618 int nFoundInPage = 0;
621 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
622 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
624 boolean releaseRepoSession = false;
625 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
626 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
627 RepositoryInstance repoSession = this.getRepositorySession();
628 if (repoSession == null) {
629 repoSession = repoClient.getRepositorySession(ctx);
630 releaseRepoSession = true;
634 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
635 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
636 // Slightly goofy pagination support - how many refs do we expect from one object?
637 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
638 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
639 if(appendToAuthRefsList(ari, list)) {
648 if (releaseRepoSession == true) {
649 repoClient.releaseRepositorySession(ctx, repoSession);
653 // Set num of items in list. this is useful to our testing framework.
654 commonList.setItemsInPage(nFoundInPage);
655 // set the total result size
656 commonList.setTotalItems(nFoundTotal);
658 } catch (PropertyException pe) {
659 String msg = "Attempted to retrieve value for invalid or missing authority field. "
660 + "Check authority field properties in tenant bindings.";
661 logger.warn(msg, pe);
663 } catch (Exception e) {
664 if (logger.isDebugEnabled()) {
665 logger.debug("Caught exception in getAuthorityRefs", e);
667 Response response = Response.status(
668 Response.Status.INTERNAL_SERVER_ERROR).entity(
669 "Failed to retrieve authority references").type(
670 "text/plain").build();
671 throw new WebApplicationException(response);
677 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
678 List<AuthorityRefList.AuthorityRefItem> list)
680 String fieldName = ari.getQualifiedDisplayName();
682 String refNameValue = (String)ari.getProperty().getValue();
683 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
684 if(item!=null) { // ignore garbage values.
688 } catch(PropertyException pe) {
689 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
694 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
696 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
698 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
699 ilistItem.setRefName(refName);
700 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
701 ilistItem.setItemDisplayName(termInfo.displayName);
702 ilistItem.setSourceField(authRefFieldName);
703 ilistItem.setUri(termInfo.getRelativeUri());
704 } catch (Exception e) {
705 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
712 * Returns the primary value from a list of values.
714 * Assumes that the first value is the primary value.
715 * This assumption may change when and if the primary value
716 * is identified explicitly.
718 * @param values a list of values.
719 * @param propertyName the name of a property through
720 * which the value can be extracted.
721 * @return the primary value.
722 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
723 String primaryValue = "";
724 if (values == null || values.size() == 0) {
727 Object value = values.get(0);
728 if (value instanceof String) {
730 primaryValue = (String) value;
732 // Multivalue group of fields
733 } else if (value instanceof Map) {
735 Map map = (Map) value;
736 if (map.values().size() > 0) {
737 if (map.get(propertyName) != null) {
738 primaryValue = (String) map.get(propertyName);
743 logger.warn("Unexpected type for property " + propertyName
744 + " in multivalue list: not String or Map.");
751 * Gets a simple property from the document.
753 * For completeness, as this duplicates DocumentModel method.
755 * @param docModel The document model to get info from
756 * @param schema The name of the schema (part)
757 * @param propertyName The simple scalar property type
758 * @return property value as String
760 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
761 String xpath = "/"+schema+":"+propName;
763 return (String)docModel.getPropertyValue(xpath);
764 } catch(PropertyException pe) {
765 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
766 +pe.getLocalizedMessage());
767 } catch(ClassCastException cce) {
768 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
769 +cce.getLocalizedMessage());
770 } catch(Exception e) {
771 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
772 +e.getLocalizedMessage());
777 * Gets first of a repeating list of scalar values, as a String, from the document.
779 * @param docModel The document model to get info from
780 * @param schema The name of the schema (part)
781 * @param listName The name of the scalar list property
782 * @return first value in list, as a String, or empty string if the list is empty
784 protected String getFirstRepeatingStringProperty(
785 DocumentModel docModel, String schema, String listName) {
786 String xpath = "/"+schema+":"+listName+"/[0]";
788 return (String)docModel.getPropertyValue(xpath);
789 } catch(PropertyException pe) {
790 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
791 +pe.getLocalizedMessage());
792 } catch(IndexOutOfBoundsException ioobe) {
793 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
794 return ""; // gracefully handle missing elements
795 } catch(ClassCastException cce) {
796 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
797 +cce.getLocalizedMessage());
798 } catch(Exception e) {
799 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
800 +e.getLocalizedMessage());
806 * Gets first of a repeating list of scalar values, as a String, from the document.
808 * @param docModel The document model to get info from
809 * @param schema The name of the schema (part)
810 * @param listName The name of the scalar list property
811 * @return first value in list, as a String, or empty string if the list is empty
813 protected String getStringValueInPrimaryRepeatingComplexProperty(
814 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
815 String result = null;
817 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
819 result = (String)docModel.getPropertyValue(xpath);
820 } catch(PropertyException pe) {
821 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
822 +pe.getLocalizedMessage());
823 } catch(IndexOutOfBoundsException ioobe) {
824 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
825 result = ""; // gracefully handle missing elements
826 } catch(ClassCastException cce) {
827 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
828 +cce.getLocalizedMessage());
829 } catch(Exception e) {
830 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
831 +e.getLocalizedMessage());
838 * Gets XPath value from schema. Note that only "/" and "[n]" are
839 * supported for xpath. Can omit grouping elements for repeating complex types,
840 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
841 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
842 * If there are no entries for a list of scalars or for a list of complex types,
843 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
844 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
845 * that many elements in the list.
846 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
848 * @param docModel The document model to get info from
849 * @param schema The name of the schema (part)
850 * @param xpath The XPath expression (without schema prefix)
851 * @return value the indicated property value as a String
853 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
854 String schema, ListResultField field) {
855 Object result = null;
857 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
862 protected String getStringValue(DocumentModel docModel,
863 String schema, ListResultField field) {
864 String result = null;
866 Object value = getListResultValue(docModel, schema, field);
867 if (value != null && value instanceof String) {
868 String strValue = (String) value;
869 if (strValue.trim().isEmpty() == false) {
877 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
881 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
883 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
885 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
887 sb.append(item.getPredicate());
889 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
893 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
894 StringBuilder sb = new StringBuilder();
896 if (list.size() > 0) {
897 sb.append("=========== " + label + " ==========" + CR);
899 for (RelationsCommonList.RelationListItem item : list) {
900 itemToString(sb, "== ", item);
903 return sb.toString();
906 /** @return null on parent not found
908 protected String getParentCSID(String thisCSID) throws Exception {
909 String parentCSID = null;
911 String predicate = RelationshipType.HAS_BROADER.value();
912 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
913 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
914 if (parentList != null) {
915 if (parentList.size() == 0) {
918 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
919 parentCSID = relationListItem.getObjectCsid();
922 } catch (Exception e) {
923 logger.error("Could not find parent for this: " + thisCSID, e);
928 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
929 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
933 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
934 MultipartServiceContext ctx) throws Exception {
935 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
936 String parentCSID = getParentCSID(thisCSID);
937 if (parentCSID == null) {
938 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
942 String predicate = RelationshipType.HAS_BROADER.value();
943 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
944 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
946 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
949 RelationsCommonList.RelationListItem item = null;
950 for (RelationsCommonList.RelationListItem sibling : siblingList) {
951 if (thisCSID.equals(sibling.getSubjectCsid())) {
952 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.
955 //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.
956 for (RelationsCommonList.RelationListItem self : toRemoveList) {
957 removeFromList(siblingList, self);
960 long siblingSize = siblingList.size();
961 siblingListOuter.setTotalItems(siblingSize);
962 siblingListOuter.setItemsInPage(siblingSize);
963 if(logger.isTraceEnabled()) {
964 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
965 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
968 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
969 ctx.addOutputPart(relationsPart);
972 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
973 MultipartServiceContext ctx) throws Exception {
974 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
976 String predicate = RelationshipType.HAS_BROADER.value();
977 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
978 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
980 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
981 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
983 if(logger.isTraceEnabled()) {
984 String dump = dumpLists(thisCSID, parentList, childrenList, null);
985 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
988 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
989 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
990 //Not optimal, but that's the current design spec.
992 for (RelationsCommonList.RelationListItem parent : parentList) {
993 childrenList.add(parent);
996 long childrenSize = childrenList.size();
997 childrenListOuter.setTotalItems(childrenSize);
998 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1000 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1001 ctx.addOutputPart(relationsPart);
1004 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1005 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1007 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1008 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1010 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1011 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1013 if(logger.isTraceEnabled()) {
1014 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1015 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1018 subjectList.addAll(objectList);
1020 //now subjectList actually has records BOTH where thisCSID is subject and object.
1021 long relatedSize = subjectList.size();
1022 subjectListOuter.setTotalItems(relatedSize);
1023 subjectListOuter.setItemsInPage(relatedSize);
1025 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1026 ctx.addOutputPart(relationsPart);
1029 private String dumpLists(String itemCSID,
1030 List<RelationsCommonList.RelationListItem> parentList,
1031 List<RelationsCommonList.RelationListItem> childList,
1032 List<RelationsCommonList.RelationListItem> actionList) {
1033 StringBuilder sb = new StringBuilder();
1034 sb.append("itemCSID: " + itemCSID + CR);
1035 if(parentList!=null) {
1036 sb.append(dumpList(parentList, "parentList"));
1038 if(childList!=null) {
1039 sb.append(dumpList(childList, "childList"));
1041 if(actionList!=null) {
1042 sb.append(dumpList(actionList, "actionList"));
1044 return sb.toString();
1047 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1048 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1049 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1050 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1051 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1052 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1053 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1055 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1056 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1057 return relationsCommonList;
1059 //============================= END TODO refactor ==========================
1061 // this method calls the RelationResource to have it create the relations and persist them.
1062 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1063 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1064 for (RelationsCommonList.RelationListItem item : inboundList) {
1065 RelationsCommon rc = new RelationsCommon();
1066 //rc.setCsid(item.getCsid());
1067 //todo: assignTo(item, rc);
1068 RelationsDocListItem itemSubject = item.getSubject();
1069 RelationsDocListItem itemObject = item.getObject();
1071 // Set at least one of CSID and refName for Subject and Object
1072 // Either value might be null for for each of Subject and Object
1073 String subjectCsid = itemSubject.getCsid();
1074 rc.setSubjectCsid(subjectCsid);
1076 String objCsid = itemObject.getCsid();
1077 rc.setObjectCsid(objCsid);
1079 rc.setSubjectRefName(itemSubject.getRefName());
1080 rc.setObjectRefName(itemObject.getRefName());
1082 rc.setRelationshipType(item.getPredicate());
1083 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1084 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1086 // This is superfluous, since it will be fetched by the Relations Create logic.
1087 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1088 rc.setObjectDocumentType(itemObject.getDocumentType());
1090 // This is superfluous, since it will be fetched by the Relations Create logic.
1091 rc.setSubjectUri(itemSubject.getUri());
1092 rc.setObjectUri(itemObject.getUri());
1093 // May not have the info here. Only really require CSID or refName.
1094 // Rest is handled in the Relation create mechanism
1095 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1097 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1098 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1099 payloadOut.addPart(outputPart);
1100 RelationResource relationResource = new RelationResource();
1101 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1102 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1106 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1107 // But item1 must not be sparse
1108 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1109 if (item1 == null || item2 == null) {
1112 RelationsDocListItem subj1 = item1.getSubject();
1113 RelationsDocListItem subj2 = item2.getSubject();
1114 RelationsDocListItem obj1 = item1.getObject();
1115 RelationsDocListItem obj2 = item2.getObject();
1116 String subj1Csid = subj1.getCsid();
1117 String subj2Csid = subj2.getCsid();
1118 String subj1RefName = subj1.getRefName();
1119 String subj2RefName = subj2.getRefName();
1121 String obj1Csid = obj1.getCsid();
1122 String obj2Csid = obj2.getCsid();
1123 String obj1RefName = obj1.getRefName();
1124 String obj2RefName = obj2.getRefName();
1127 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1128 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1129 // predicate is proper, but still allow relationshipType
1130 && (item1.getPredicate().equals(item2.getPredicate())
1131 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1132 // Allow missing docTypes, so long as they do not conflict
1133 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1134 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1138 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1139 // But the list items must not be sparse
1140 private RelationsCommonList.RelationListItem findInList(
1141 List<RelationsCommonList.RelationListItem> list,
1142 RelationsCommonList.RelationListItem item) {
1143 RelationsCommonList.RelationListItem foundItem = null;
1144 for (RelationsCommonList.RelationListItem listItem : list) {
1145 if (itemsEqual(listItem, item)) { //equals must be defined, else
1146 foundItem = listItem;
1153 /** updateRelations strategy:
1156 go through inboundList, remove anything from childList that matches from childList
1157 go through inboundList, remove anything from parentList that matches from parentList
1158 go through parentList, delete all remaining
1159 go through childList, delete all remaining
1160 go through actionList, add all remaining.
1161 check for duplicate children
1162 check for more than one parent.
1164 inboundList parentList childList actionList
1165 ---------------- --------------- ---------------- ----------------
1166 child-a parent-c child-a child-b
1167 child-b parent-d child-c
1172 private RelationsCommonList updateRelations(
1173 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1175 if (logger.isTraceEnabled()) {
1176 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1178 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1180 return null; //nothing to do--they didn't send a list of relations.
1182 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1183 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1184 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1185 List<RelationsCommonList.RelationListItem> childList = null;
1186 List<RelationsCommonList.RelationListItem> parentList = null;
1187 DocumentModel docModel = wrapDoc.getWrappedObject();
1188 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
1190 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1191 //Do magic replacement of ${itemCSID} and fix URI's.
1192 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1194 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1195 UriInfo uriInfo = ctx.getUriInfo();
1196 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1199 //Run getList() once as sent to get childListOuter:
1200 String predicate = RelationshipType.HAS_BROADER.value();
1201 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1202 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1203 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1204 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1205 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1207 RelationResource relationResource = new RelationResource();
1208 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1210 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1211 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1212 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1213 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1214 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1217 childList = childListOuter.getRelationListItem();
1218 parentList = parentListOuter.getRelationListItem();
1220 if (parentList.size() > 1) {
1221 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1224 if (logger.isTraceEnabled()) {
1225 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1229 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1230 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1231 // and so the CSID for those may be null
1232 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1233 // Look for parents and children
1234 if(itemCSID.equals(inboundItem.getObject().getCsid())
1235 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1236 //then this is an item that says we have a child. That child is inboundItem
1237 RelationsCommonList.RelationListItem childItem =
1238 (childList == null) ? null : findInList(childList, inboundItem);
1239 if (childItem != null) {
1240 if (logger.isTraceEnabled()) {
1241 StringBuilder sb = new StringBuilder();
1242 itemToString(sb, "== Child: ", childItem);
1243 logger.trace("Found inboundChild in current child list: " + sb.toString());
1245 removeFromList(childList, childItem); //exists, just take it off delete list
1247 if (logger.isTraceEnabled()) {
1248 StringBuilder sb = new StringBuilder();
1249 itemToString(sb, "== Child: ", inboundItem);
1250 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1252 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1253 String newChildCsid = inboundItem.getSubject().getCsid();
1254 if(newChildCsid == null) {
1255 String newChildRefName = inboundItem.getSubject().getRefName();
1256 if(newChildRefName==null) {
1257 throw new RuntimeException("Child with no CSID or refName!");
1259 if (logger.isTraceEnabled()) {
1260 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1262 DocumentModel newChildDocModel =
1263 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1264 newChildRefName, getServiceContext().getResourceMap());
1265 newChildCsid = getCsid(newChildDocModel);
1267 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1270 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1271 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1272 //then this is an item that says we have a parent. inboundItem is that parent.
1273 RelationsCommonList.RelationListItem parentItem =
1274 (parentList == null) ? null : findInList(parentList, inboundItem);
1275 if (parentItem != null) {
1276 removeFromList(parentList, parentItem); //exists, just take it off delete list
1278 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1281 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1284 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1287 if (logger.isTraceEnabled()) {
1288 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1289 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1292 if (logger.isTraceEnabled()) {
1293 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1294 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1296 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1297 deleteRelations(childList, ctx, "childList");
1299 if (logger.isTraceEnabled()) {
1300 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1301 + actionList.size() + " new parents and children.");
1303 createRelations(actionList, ctx);
1304 if (logger.isTraceEnabled()) {
1305 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1307 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1308 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1309 return relationsCommonListBody;
1312 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1313 * and sets URI correctly for related items.
1314 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1316 protected void fixupInboundListItems(ServiceContext ctx,
1317 List<RelationsCommonList.RelationListItem> inboundList,
1318 DocumentModel docModel,
1319 String itemCSID) throws Exception {
1320 String thisURI = this.getUri(docModel);
1321 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1322 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1323 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1324 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1325 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1327 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1328 inboundItem.setObjectCsid(itemCSID);
1329 inboundItemObject.setCsid(itemCSID);
1330 //inboundItemObject.setUri(getUri(docModel));
1333 String objectCsid = inboundItemObject.getCsid();
1334 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1335 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1336 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1337 inboundItemObject.setUri(uri); //CSPACE-4037
1340 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1342 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1343 inboundItem.setSubjectCsid(itemCSID);
1344 inboundItemSubject.setCsid(itemCSID);
1345 //inboundItemSubject.setUri(getUri(docModel));
1348 String subjectCsid = inboundItemSubject.getCsid();
1349 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1350 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1351 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1352 inboundItemSubject.setUri(uri); //CSPACE-4037
1355 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1360 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1361 MultivaluedMap<String, String> queryParams, String childCSID) {
1362 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1363 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1364 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1365 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1367 RelationResource relationResource = new RelationResource();
1368 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1369 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1370 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1371 deleteRelations(parentList, ctx, "parentList-delete");
1374 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1375 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1378 for (RelationsCommonList.RelationListItem item : list) {
1379 RelationResource relationResource = new RelationResource();
1380 if(logger.isTraceEnabled()) {
1381 StringBuilder sb = new StringBuilder();
1382 itemToString(sb, "==== TO DELETE: ", item);
1383 logger.trace(sb.toString());
1385 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1386 if (logger.isDebugEnabled()) {
1387 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1390 } catch (Throwable t) {
1391 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1396 // Note that we must do this after we have completed the Update, so that the repository has the
1397 // info for the item itself. The relations code must call into the repo to get info for each end.
1398 // This could be optimized to pass in the parent docModel, since it will often be one end.
1399 // Nevertheless, we should complete the item save before we do work on the relations, especially
1400 // since a save on Create might fail, and we would not want to create relations for something
1401 // that may not be created...
1402 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1403 ServiceContext ctx = getServiceContext();
1404 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1405 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1406 String itemCsid = documentModel.getName();
1408 //Updates relations part
1409 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1411 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
1412 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1414 //now we add part for relations list
1415 //ServiceContext ctx = getServiceContext();
1416 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1417 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1421 * Checks to see if the refName has changed, and if so,
1422 * uses utilities to find all references and update them.
1425 protected void handleItemRefNameReferenceUpdate() throws Exception {
1426 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
1427 // We have work to do.
1428 if (logger.isDebugEnabled()) {
1429 String eol = System.getProperty("line.separator");
1430 logger.debug("Need to find and update references to Item." + eol
1431 + " Old refName" + oldRefNameOnUpdate + eol
1432 + " New refName" + newRefNameOnUpdate);
1434 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1435 RepositoryClient repoClient = getRepositoryClient(ctx);
1436 String refNameProp = getRefPropName();
1438 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
1439 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
1440 if (logger.isDebugEnabled()) {
1441 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
1447 * Note: The Vocabulary document handler overrides this method.
1449 protected String getRefPropName() {
1450 return ServiceBindingUtils.AUTH_REF_PROP;