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