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());
143 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
144 String result = null;
146 DocHandlerParams.Params params = null;
148 params = getDocHandlerParams();
149 ListResultField field = params.getRefnameDisplayNameField();
151 String schema = field.getSchema();
152 if (schema == null || schema.trim().isEmpty()) {
153 schema = getServiceContext().getCommonPartLabel();
156 result = getStringValue(docModel, schema, field);
157 } catch (Exception e) {
158 // TODO Auto-generated catch block
159 if (logger.isWarnEnabled()) {
160 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
168 public boolean supportsHierarchy() {
169 boolean result = false;
171 DocHandlerParams.Params params = null;
173 params = getDocHandlerParams();
174 Boolean bool = params.isSupportsHierarchy();
176 result = bool.booleanValue();
178 } catch (DocumentException e) {
179 // TODO Auto-generated catch block
180 logger.error(String.format("Could not get document handler params for class %s", this.getClass().getName()), e);
187 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
189 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
193 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
194 super.completeCreate(wrapDoc);
195 if (supportsHierarchy() == true) {
196 handleRelationsPayload(wrapDoc, false);
201 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
204 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
205 DocumentModel docModel = wrapDoc.getWrappedObject();
206 //return at least those document part(s) that were received
207 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
208 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
209 PoxPayloadIn input = ctx.getInput();
211 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
212 for (PayloadInputPart part : inputParts) {
213 String partLabel = part.getLabel();
215 ObjectPartType partMeta = partsMetaMap.get(partLabel);
216 // CSPACE-4030 - generates NPE if the part is missing.
218 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
219 if(unQObjectProperties!=null) {
220 addOutputPart(unQObjectProperties, partLabel, partMeta);
223 } catch (Throwable t){
224 logger.error("Unable to addOutputPart: "+partLabel
225 +" in serviceContextPath: "+this.getServiceContextPath()
226 +" with URI: "+this.getServiceContext().getUriInfo().getPath()
231 if (logger.isWarnEnabled() == true) {
232 logger.warn("MultipartInput part was null for document id = " +
237 if (supportsHierarchy() == true) {
238 handleRelationsPayload(wrapDoc, true);
239 handleItemRefNameReferenceUpdate();
244 * Adds the output part.
246 * @param unQObjectProperties the un q object properties
247 * @param schema the schema
248 * @param partMeta the part meta
249 * @throws Exception the exception
250 * MediaType.APPLICATION_XML_TYPE
252 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
254 Element doc = DocumentUtils.buildDocument(partMeta, schema,
255 unQObjectProperties);
256 if (logger.isTraceEnabled() == true) {
257 logger.trace(doc.asXML());
259 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
260 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
264 * Extract paging info.
266 * @param commonsList the commons list
268 * @throws Exception the exception
270 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
272 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
274 DocumentFilter docFilter = this.getDocumentFilter();
275 long pageSize = docFilter.getPageSize();
276 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
277 // set the page size and page number
278 commonList.setPageNum(pageNum);
279 commonList.setPageSize(pageSize);
280 DocumentModelList docList = wrapDoc.getWrappedObject();
281 // Set num of items in list. this is useful to our testing framework.
282 commonList.setItemsInPage(docList.size());
283 // set the total result size
284 commonList.setTotalItems(docList.totalSize());
286 return (TL) commonList;
290 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
293 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
296 DocumentModel docModel = wrapDoc.getWrappedObject();
297 String[] schemas = docModel.getDeclaredSchemas();
298 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
299 for (String schema : schemas) {
300 ObjectPartType partMeta = partsMetaMap.get(schema);
301 if (partMeta == null) {
302 continue; // unknown part, ignore
304 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
305 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
306 addExtraCoreValues(docModel, unQObjectProperties);
308 addOutputPart(unQObjectProperties, schema, partMeta);
311 if (supportsHierarchy() == true) {
312 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
313 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
314 if (Tools.isTrue(showSiblings)) {
315 showSiblings(wrapDoc, ctx);
316 return; // actual result is returned on ctx.addOutputPart();
319 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
320 if (Tools.isTrue(showRelations)) {
321 showRelations(wrapDoc, ctx);
322 return; // actual result is returned on ctx.addOutputPart();
325 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
326 if (Tools.isTrue(showAllRelations)) {
327 showAllRelations(wrapDoc, ctx);
328 return; // actual result is returned on ctx.addOutputPart();
332 addAccountPermissionsPart();
335 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
337 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
340 private void addAccountPermissionsPart() throws Exception {
341 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
344 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
345 String currentServiceName = ctx.getServiceName();
346 String workflowSubResource = "/";
347 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
348 if (jaxRsContext != null) {
349 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
350 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
352 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
354 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
355 currentServiceName, workflowSubResource);
356 org.collectionspace.services.authorization.ObjectFactory objectFactory =
357 new org.collectionspace.services.authorization.ObjectFactory();
358 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
359 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap);
360 ctx.addOutputPart(accountPermissionPart);
366 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
369 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
371 //TODO filling extension parts should be dynamic
372 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
373 //not an ideal way of populating objects.
374 DocumentModel docModel = wrapDoc.getWrappedObject();
375 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
376 PoxPayloadIn input = ctx.getInput();
377 if (input.getParts().isEmpty()) {
378 String msg = "No payload found!";
379 logger.error(msg + "Ctx=" + getServiceContext().toString());
380 throw new BadRequestException(msg);
383 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
385 //iterate over parts received and fill those parts
386 List<PayloadInputPart> inputParts = input.getParts();
387 for (PayloadInputPart part : inputParts) {
389 String partLabel = part.getLabel();
390 if (partLabel == null) {
391 String msg = "Part label is missing or empty!";
392 logger.error(msg + "Ctx=" + getServiceContext().toString());
393 throw new BadRequestException(msg);
396 //skip if the part is not in metadata
397 ObjectPartType partMeta = partsMetaMap.get(partLabel);
398 if (partMeta == null) {
401 fillPart(part, docModel, partMeta, action, ctx);
407 * fillPart fills an XML part into given document model
408 * @param part to fill
409 * @param docModel for the given object
410 * @param partMeta metadata for the object to fill
413 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
414 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
416 //check if this is an xml part
417 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
418 Element element = part.getElementBody();
419 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
420 if (action == Action.UPDATE) {
421 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
423 docModel.setProperties(partMeta.getLabel(), objectProps);
428 * Filters out read only properties, so they cannot be set on update.
429 * TODO: add configuration support to do this generally
430 * @param objectProps the properties parsed from the update payload
431 * @param partMeta metadata for the object to fill
433 public void filterReadOnlyPropertiesForPart(
434 Map<String, Object> objectProps, ObjectPartType partMeta) {
435 // Should add in logic to filter most of the core items on update
436 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
437 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
438 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
439 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
440 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
441 // Note that the updatedAt/updatedBy fields are set internally
442 // in DocumentModelHandler.handleCoreValues().
447 * extractPart extracts an XML object from given DocumentModel
449 * @param schema of the object to extract
450 * @param partMeta metadata for the object to extract
453 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
455 return extractPart(docModel, schema, (Map<String, Object>)null);
459 * extractPart extracts an XML object from given DocumentModel
461 * @param schema of the object to extract
462 * @param partMeta metadata for the object to extract
466 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
468 return extractPart(docModel, schema, partMeta, null);
472 * extractPart extracts an XML object from given DocumentModel
474 * @param schema of the object to extract
475 * @param partMeta metadata for the object to extract
478 protected Map<String, Object> extractPart(
479 DocumentModel docModel,
481 Map<String, Object> addToMap)
483 Map<String, Object> result = null;
485 Map<String, Object> objectProps = docModel.getProperties(schema);
486 if (objectProps != null) {
487 //unqualify properties before sending the doc over the wire (to save bandwidh)
488 //FIXME: is there a better way to avoid duplication of a Map/Collection?
489 Map<String, Object> unQObjectProperties =
490 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
491 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
492 for (Entry<String, Object> entry : qualifiedEntries) {
493 String unqProp = getUnQProperty(entry.getKey());
494 unQObjectProperties.put(unqProp, entry.getValue());
496 result = unQObjectProperties;
503 * extractPart extracts an XML object from given DocumentModel
505 * @param schema of the object to extract
506 * @param partMeta metadata for the object to extract
510 protected Map<String, Object> extractPart(
511 DocumentModel docModel, String schema, ObjectPartType partMeta,
512 Map<String, Object> addToMap)
514 Map<String, Object> result = null;
516 result = this.extractPart(docModel, schema, addToMap);
522 public String getStringPropertyFromDoc(
525 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
526 RepositoryInstance repoSession = null;
527 boolean releaseRepoSession = false;
528 String returnValue = null;
531 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
532 repoSession = this.getRepositorySession();
533 if (repoSession == null) {
534 repoSession = repoClient.getRepositorySession();
535 releaseRepoSession = true;
539 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
540 DocumentModel docModel = wrapper.getWrappedObject();
541 returnValue = (String) docModel.getPropertyValue(propertyXPath);
542 } catch (PropertyException pe) {
544 } catch (DocumentException de) {
546 } catch (Exception e) {
547 if (logger.isDebugEnabled()) {
548 logger.debug("Caught exception ", e);
550 throw new DocumentException(e);
552 if (releaseRepoSession && repoSession != null) {
553 repoClient.releaseRepositorySession(repoSession);
556 } catch (Exception e) {
557 if (logger.isDebugEnabled()) {
558 logger.debug("Caught exception ", e);
560 throw new DocumentException(e);
564 if (logger.isWarnEnabled() == true) {
565 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
574 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
577 public AuthorityRefList getAuthorityRefs(
579 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
581 AuthorityRefList authRefList = new AuthorityRefList();
582 AbstractCommonList commonList = (AbstractCommonList) authRefList;
584 DocumentFilter docFilter = this.getDocumentFilter();
585 long pageSize = docFilter.getPageSize();
586 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
587 // set the page size and page number
588 commonList.setPageNum(pageNum);
589 commonList.setPageSize(pageSize);
591 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
594 int iFirstToUse = (int)(pageSize*pageNum);
595 int nFoundInPage = 0;
598 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
599 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
601 boolean releaseRepoSession = false;
602 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
603 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
604 RepositoryInstance repoSession = this.getRepositorySession();
605 if (repoSession == null) {
606 repoSession = repoClient.getRepositorySession(ctx);
607 releaseRepoSession = true;
611 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
612 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
613 // Slightly goofy pagination support - how many refs do we expect from one object?
614 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
615 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
616 if(appendToAuthRefsList(ari, list)) {
625 if (releaseRepoSession == true) {
626 repoClient.releaseRepositorySession(ctx, repoSession);
630 // Set num of items in list. this is useful to our testing framework.
631 commonList.setItemsInPage(nFoundInPage);
632 // set the total result size
633 commonList.setTotalItems(nFoundTotal);
635 } catch (PropertyException pe) {
636 String msg = "Attempted to retrieve value for invalid or missing authority field. "
637 + "Check authority field properties in tenant bindings.";
638 logger.warn(msg, pe);
640 } catch (Exception e) {
641 if (logger.isDebugEnabled()) {
642 logger.debug("Caught exception in getAuthorityRefs", e);
644 Response response = Response.status(
645 Response.Status.INTERNAL_SERVER_ERROR).entity(
646 "Failed to retrieve authority references").type(
647 "text/plain").build();
648 throw new WebApplicationException(response);
654 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
655 List<AuthorityRefList.AuthorityRefItem> list)
657 String fieldName = ari.getQualifiedDisplayName();
659 String refNameValue = (String)ari.getProperty().getValue();
660 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
661 if(item!=null) { // ignore garbage values.
665 } catch(PropertyException pe) {
666 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
671 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
673 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
675 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
676 ilistItem.setRefName(refName);
677 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
678 ilistItem.setItemDisplayName(termInfo.displayName);
679 ilistItem.setSourceField(authRefFieldName);
680 ilistItem.setUri(termInfo.getRelativeUri());
681 } catch (Exception e) {
682 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
689 * Returns the primary value from a list of values.
691 * Assumes that the first value is the primary value.
692 * This assumption may change when and if the primary value
693 * is identified explicitly.
695 * @param values a list of values.
696 * @param propertyName the name of a property through
697 * which the value can be extracted.
698 * @return the primary value.
699 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
700 String primaryValue = "";
701 if (values == null || values.size() == 0) {
704 Object value = values.get(0);
705 if (value instanceof String) {
707 primaryValue = (String) value;
709 // Multivalue group of fields
710 } else if (value instanceof Map) {
712 Map map = (Map) value;
713 if (map.values().size() > 0) {
714 if (map.get(propertyName) != null) {
715 primaryValue = (String) map.get(propertyName);
720 logger.warn("Unexpected type for property " + propertyName
721 + " in multivalue list: not String or Map.");
728 * Gets a simple property from the document.
730 * For completeness, as this duplicates DocumentModel method.
732 * @param docModel The document model to get info from
733 * @param schema The name of the schema (part)
734 * @param propertyName The simple scalar property type
735 * @return property value as String
737 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
738 String xpath = "/"+schema+":"+propName;
740 return (String)docModel.getPropertyValue(xpath);
741 } catch(PropertyException pe) {
742 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
743 +pe.getLocalizedMessage());
744 } catch(ClassCastException cce) {
745 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
746 +cce.getLocalizedMessage());
747 } catch(Exception e) {
748 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
749 +e.getLocalizedMessage());
754 * Gets first of a repeating list of scalar values, as a String, from the document.
756 * @param docModel The document model to get info from
757 * @param schema The name of the schema (part)
758 * @param listName The name of the scalar list property
759 * @return first value in list, as a String, or empty string if the list is empty
761 protected String getFirstRepeatingStringProperty(
762 DocumentModel docModel, String schema, String listName) {
763 String xpath = "/"+schema+":"+listName+"/[0]";
765 return (String)docModel.getPropertyValue(xpath);
766 } catch(PropertyException pe) {
767 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
768 +pe.getLocalizedMessage());
769 } catch(IndexOutOfBoundsException ioobe) {
770 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
771 return ""; // gracefully handle missing elements
772 } catch(ClassCastException cce) {
773 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
774 +cce.getLocalizedMessage());
775 } catch(Exception e) {
776 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
777 +e.getLocalizedMessage());
783 * Gets first of a repeating list of scalar values, as a String, from the document.
785 * @param docModel The document model to get info from
786 * @param schema The name of the schema (part)
787 * @param listName The name of the scalar list property
788 * @return first value in list, as a String, or empty string if the list is empty
790 protected String getStringValueInPrimaryRepeatingComplexProperty(
791 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
792 String result = null;
794 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
796 result = (String)docModel.getPropertyValue(xpath);
797 } catch(PropertyException pe) {
798 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
799 +pe.getLocalizedMessage());
800 } catch(IndexOutOfBoundsException ioobe) {
801 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
802 result = ""; // gracefully handle missing elements
803 } catch(ClassCastException cce) {
804 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
805 +cce.getLocalizedMessage());
806 } catch(Exception e) {
807 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
808 +e.getLocalizedMessage());
815 * Gets XPath value from schema. Note that only "/" and "[n]" are
816 * supported for xpath. Can omit grouping elements for repeating complex types,
817 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
818 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
819 * If there are no entries for a list of scalars or for a list of complex types,
820 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
821 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
822 * that many elements in the list.
823 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
825 * @param docModel The document model to get info from
826 * @param schema The name of the schema (part)
827 * @param xpath The XPath expression (without schema prefix)
828 * @return value the indicated property value as a String
830 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
831 String schema, ListResultField field) {
832 Object result = null;
834 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
839 protected String getStringValue(DocumentModel docModel,
840 String schema, ListResultField field) {
841 String result = null;
843 Object value = getListResultValue(docModel, schema, field);
844 if (value != null && value instanceof String) {
845 String strValue = (String) value;
846 if (strValue.trim().isEmpty() == false) {
854 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
858 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
860 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
862 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
864 sb.append(item.getPredicate());
866 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
870 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
871 StringBuilder sb = new StringBuilder();
873 if (list.size() > 0) {
874 sb.append("=========== " + label + " ==========" + CR);
876 for (RelationsCommonList.RelationListItem item : list) {
877 itemToString(sb, "== ", item);
880 return sb.toString();
883 /** @return null on parent not found
885 protected String getParentCSID(String thisCSID) throws Exception {
886 String parentCSID = null;
888 String predicate = RelationshipType.HAS_BROADER.value();
889 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
890 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
891 if (parentList != null) {
892 if (parentList.size() == 0) {
895 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
896 parentCSID = relationListItem.getObjectCsid();
899 } catch (Exception e) {
900 logger.error("Could not find parent for this: " + thisCSID, e);
905 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
906 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
910 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
911 MultipartServiceContext ctx) throws Exception {
912 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
913 String parentCSID = getParentCSID(thisCSID);
914 if (parentCSID == null) {
915 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
919 String predicate = RelationshipType.HAS_BROADER.value();
920 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
921 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
923 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
926 RelationsCommonList.RelationListItem item = null;
927 for (RelationsCommonList.RelationListItem sibling : siblingList) {
928 if (thisCSID.equals(sibling.getSubjectCsid())) {
929 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.
932 //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.
933 for (RelationsCommonList.RelationListItem self : toRemoveList) {
934 removeFromList(siblingList, self);
937 long siblingSize = siblingList.size();
938 siblingListOuter.setTotalItems(siblingSize);
939 siblingListOuter.setItemsInPage(siblingSize);
940 if(logger.isTraceEnabled()) {
941 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
942 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
945 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
946 ctx.addOutputPart(relationsPart);
949 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
950 MultipartServiceContext ctx) throws Exception {
951 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
953 String predicate = RelationshipType.HAS_BROADER.value();
954 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
955 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
957 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
958 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
960 if(logger.isTraceEnabled()) {
961 String dump = dumpLists(thisCSID, parentList, childrenList, null);
962 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
965 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
966 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
967 //Not optimal, but that's the current design spec.
969 for (RelationsCommonList.RelationListItem parent : parentList) {
970 childrenList.add(parent);
973 long childrenSize = childrenList.size();
974 childrenListOuter.setTotalItems(childrenSize);
975 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
977 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
978 ctx.addOutputPart(relationsPart);
981 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
982 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
984 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
985 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
987 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
988 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
990 if(logger.isTraceEnabled()) {
991 String dump = dumpLists(thisCSID, subjectList, objectList, null);
992 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
995 subjectList.addAll(objectList);
997 //now subjectList actually has records BOTH where thisCSID is subject and object.
998 long relatedSize = subjectList.size();
999 subjectListOuter.setTotalItems(relatedSize);
1000 subjectListOuter.setItemsInPage(relatedSize);
1002 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1003 ctx.addOutputPart(relationsPart);
1006 private String dumpLists(String itemCSID,
1007 List<RelationsCommonList.RelationListItem> parentList,
1008 List<RelationsCommonList.RelationListItem> childList,
1009 List<RelationsCommonList.RelationListItem> actionList) {
1010 StringBuilder sb = new StringBuilder();
1011 sb.append("itemCSID: " + itemCSID + CR);
1012 if(parentList!=null) {
1013 sb.append(dumpList(parentList, "parentList"));
1015 if(childList!=null) {
1016 sb.append(dumpList(childList, "childList"));
1018 if(actionList!=null) {
1019 sb.append(dumpList(actionList, "actionList"));
1021 return sb.toString();
1024 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1025 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1026 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1027 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1028 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1029 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1030 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1032 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1033 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1034 return relationsCommonList;
1036 //============================= END TODO refactor ==========================
1038 // this method calls the RelationResource to have it create the relations and persist them.
1039 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1040 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1041 for (RelationsCommonList.RelationListItem item : inboundList) {
1042 RelationsCommon rc = new RelationsCommon();
1043 //rc.setCsid(item.getCsid());
1044 //todo: assignTo(item, rc);
1045 RelationsDocListItem itemSubject = item.getSubject();
1046 RelationsDocListItem itemObject = item.getObject();
1048 // Set at least one of CSID and refName for Subject and Object
1049 // Either value might be null for for each of Subject and Object
1050 String subjectCsid = itemSubject.getCsid();
1051 rc.setSubjectCsid(subjectCsid);
1053 String objCsid = itemObject.getCsid();
1054 rc.setObjectCsid(objCsid);
1056 rc.setSubjectRefName(itemSubject.getRefName());
1057 rc.setObjectRefName(itemObject.getRefName());
1059 rc.setRelationshipType(item.getPredicate());
1060 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1061 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1063 // This is superfluous, since it will be fetched by the Relations Create logic.
1064 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1065 rc.setObjectDocumentType(itemObject.getDocumentType());
1067 // This is superfluous, since it will be fetched by the Relations Create logic.
1068 rc.setSubjectUri(itemSubject.getUri());
1069 rc.setObjectUri(itemObject.getUri());
1070 // May not have the info here. Only really require CSID or refName.
1071 // Rest is handled in the Relation create mechanism
1072 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1074 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1075 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1076 payloadOut.addPart(outputPart);
1077 RelationResource relationResource = new RelationResource();
1078 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1079 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1083 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1084 // But item1 must not be sparse
1085 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1086 if (item1 == null || item2 == null) {
1089 RelationsDocListItem subj1 = item1.getSubject();
1090 RelationsDocListItem subj2 = item2.getSubject();
1091 RelationsDocListItem obj1 = item1.getObject();
1092 RelationsDocListItem obj2 = item2.getObject();
1093 String subj1Csid = subj1.getCsid();
1094 String subj2Csid = subj2.getCsid();
1095 String subj1RefName = subj1.getRefName();
1096 String subj2RefName = subj2.getRefName();
1098 String obj1Csid = obj1.getCsid();
1099 String obj2Csid = obj2.getCsid();
1100 String obj1RefName = obj1.getRefName();
1101 String obj2RefName = obj2.getRefName();
1104 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1105 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1106 // predicate is proper, but still allow relationshipType
1107 && (item1.getPredicate().equals(item2.getPredicate())
1108 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1109 // Allow missing docTypes, so long as they do not conflict
1110 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1111 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1115 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1116 // But the list items must not be sparse
1117 private RelationsCommonList.RelationListItem findInList(
1118 List<RelationsCommonList.RelationListItem> list,
1119 RelationsCommonList.RelationListItem item) {
1120 RelationsCommonList.RelationListItem foundItem = null;
1121 for (RelationsCommonList.RelationListItem listItem : list) {
1122 if (itemsEqual(listItem, item)) { //equals must be defined, else
1123 foundItem = listItem;
1130 /** updateRelations strategy:
1133 go through inboundList, remove anything from childList that matches from childList
1134 go through inboundList, remove anything from parentList that matches from parentList
1135 go through parentList, delete all remaining
1136 go through childList, delete all remaining
1137 go through actionList, add all remaining.
1138 check for duplicate children
1139 check for more than one parent.
1141 inboundList parentList childList actionList
1142 ---------------- --------------- ---------------- ----------------
1143 child-a parent-c child-a child-b
1144 child-b parent-d child-c
1149 private RelationsCommonList updateRelations(
1150 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1152 if (logger.isTraceEnabled()) {
1153 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1155 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1157 return null; //nothing to do--they didn't send a list of relations.
1159 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1160 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1161 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1162 List<RelationsCommonList.RelationListItem> childList = null;
1163 List<RelationsCommonList.RelationListItem> parentList = null;
1164 DocumentModel docModel = wrapDoc.getWrappedObject();
1165 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
1167 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1168 //Do magic replacement of ${itemCSID} and fix URI's.
1169 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1171 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1172 UriInfo uriInfo = ctx.getUriInfo();
1173 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1176 //Run getList() once as sent to get childListOuter:
1177 String predicate = RelationshipType.HAS_BROADER.value();
1178 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1179 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1180 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1181 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1182 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1184 RelationResource relationResource = new RelationResource();
1185 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1187 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1188 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1189 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1190 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1191 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1194 childList = childListOuter.getRelationListItem();
1195 parentList = parentListOuter.getRelationListItem();
1197 if (parentList.size() > 1) {
1198 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1201 if (logger.isTraceEnabled()) {
1202 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1206 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1207 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1208 // and so the CSID for those may be null
1209 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1210 // Look for parents and children
1211 if(itemCSID.equals(inboundItem.getObject().getCsid())
1212 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1213 //then this is an item that says we have a child. That child is inboundItem
1214 RelationsCommonList.RelationListItem childItem =
1215 (childList == null) ? null : findInList(childList, inboundItem);
1216 if (childItem != null) {
1217 if (logger.isTraceEnabled()) {
1218 StringBuilder sb = new StringBuilder();
1219 itemToString(sb, "== Child: ", childItem);
1220 logger.trace("Found inboundChild in current child list: " + sb.toString());
1222 removeFromList(childList, childItem); //exists, just take it off delete list
1224 if (logger.isTraceEnabled()) {
1225 StringBuilder sb = new StringBuilder();
1226 itemToString(sb, "== Child: ", inboundItem);
1227 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1229 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1230 String newChildCsid = inboundItem.getSubject().getCsid();
1231 if(newChildCsid == null) {
1232 String newChildRefName = inboundItem.getSubject().getRefName();
1233 if(newChildRefName==null) {
1234 throw new RuntimeException("Child with no CSID or refName!");
1236 if (logger.isTraceEnabled()) {
1237 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1239 DocumentModel newChildDocModel =
1240 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1241 newChildRefName, getServiceContext().getResourceMap());
1242 newChildCsid = getCsid(newChildDocModel);
1244 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1247 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1248 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1249 //then this is an item that says we have a parent. inboundItem is that parent.
1250 RelationsCommonList.RelationListItem parentItem =
1251 (parentList == null) ? null : findInList(parentList, inboundItem);
1252 if (parentItem != null) {
1253 removeFromList(parentList, parentItem); //exists, just take it off delete list
1255 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1258 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1261 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1264 if (logger.isTraceEnabled()) {
1265 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1266 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1269 if (logger.isTraceEnabled()) {
1270 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1271 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1273 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1274 deleteRelations(childList, ctx, "childList");
1276 if (logger.isTraceEnabled()) {
1277 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1278 + actionList.size() + " new parents and children.");
1280 createRelations(actionList, ctx);
1281 if (logger.isTraceEnabled()) {
1282 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1284 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1285 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1286 return relationsCommonListBody;
1289 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1290 * and sets URI correctly for related items.
1291 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1293 protected void fixupInboundListItems(ServiceContext ctx,
1294 List<RelationsCommonList.RelationListItem> inboundList,
1295 DocumentModel docModel,
1296 String itemCSID) throws Exception {
1297 String thisURI = this.getUri(docModel);
1298 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1299 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1300 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1301 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1302 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1304 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1305 inboundItem.setObjectCsid(itemCSID);
1306 inboundItemObject.setCsid(itemCSID);
1307 //inboundItemObject.setUri(getUri(docModel));
1310 String objectCsid = inboundItemObject.getCsid();
1311 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1312 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1313 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1314 inboundItemObject.setUri(uri); //CSPACE-4037
1317 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1319 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1320 inboundItem.setSubjectCsid(itemCSID);
1321 inboundItemSubject.setCsid(itemCSID);
1322 //inboundItemSubject.setUri(getUri(docModel));
1325 String subjectCsid = inboundItemSubject.getCsid();
1326 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1327 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1328 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1329 inboundItemSubject.setUri(uri); //CSPACE-4037
1332 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1337 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1338 MultivaluedMap<String, String> queryParams, String childCSID) {
1339 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1340 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1341 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1342 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1344 RelationResource relationResource = new RelationResource();
1345 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1346 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1347 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1348 deleteRelations(parentList, ctx, "parentList-delete");
1351 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1352 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1355 for (RelationsCommonList.RelationListItem item : list) {
1356 RelationResource relationResource = new RelationResource();
1357 if(logger.isTraceEnabled()) {
1358 StringBuilder sb = new StringBuilder();
1359 itemToString(sb, "==== TO DELETE: ", item);
1360 logger.trace(sb.toString());
1362 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1363 if (logger.isDebugEnabled()) {
1364 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1367 } catch (Throwable t) {
1368 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1373 // Note that we must do this after we have completed the Update, so that the repository has the
1374 // info for the item itself. The relations code must call into the repo to get info for each end.
1375 // This could be optimized to pass in the parent docModel, since it will often be one end.
1376 // Nevertheless, we should complete the item save before we do work on the relations, especially
1377 // since a save on Create might fail, and we would not want to create relations for something
1378 // that may not be created...
1379 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1380 ServiceContext ctx = getServiceContext();
1381 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1382 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1383 String itemCsid = documentModel.getName();
1385 //Updates relations part
1386 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1388 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
1389 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1391 //now we add part for relations list
1392 //ServiceContext ctx = getServiceContext();
1393 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1394 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1398 * Checks to see if the refName has changed, and if so,
1399 * uses utilities to find all references and update them.
1402 protected void handleItemRefNameReferenceUpdate() throws Exception {
1403 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
1404 // We have work to do.
1405 if (logger.isDebugEnabled()) {
1406 String eol = System.getProperty("line.separator");
1407 logger.debug("Need to find and update references to Item." + eol
1408 + " Old refName" + oldRefNameOnUpdate + eol
1409 + " New refName" + newRefNameOnUpdate);
1411 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1412 RepositoryClient repoClient = getRepositoryClient(ctx);
1413 String refNameProp = getRefPropName();
1415 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
1416 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
1417 if (logger.isDebugEnabled()) {
1418 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
1424 * Note: The Vocabulary document handler overrides this method.
1426 protected String getRefPropName() {
1427 return ServiceBindingUtils.AUTH_REF_PROP;