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 params = getDocHandlerParams();
160 Boolean bool = params.isSupportsHierarchy();
162 result = bool.booleanValue();
164 } catch (DocumentException e) {
165 // TODO Auto-generated catch block
166 logger.error(String.format("Could not get document handler params for class %s", this.getClass().getName()), e);
173 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
175 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
179 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
180 super.completeCreate(wrapDoc);
181 if (supportsHierarchy() == true) {
182 handleRelationsPayload(wrapDoc, false);
187 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
190 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
191 DocumentModel docModel = wrapDoc.getWrappedObject();
192 //return at least those document part(s) that were received
193 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
194 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
195 PoxPayloadIn input = ctx.getInput();
197 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
198 for (PayloadInputPart part : inputParts) {
199 String partLabel = part.getLabel();
201 ObjectPartType partMeta = partsMetaMap.get(partLabel);
202 // CSPACE-4030 - generates NPE if the part is missing.
204 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
205 if(unQObjectProperties!=null) {
206 addOutputPart(unQObjectProperties, partLabel, partMeta);
209 } catch (Throwable t){
210 logger.error("Unable to addOutputPart: "+partLabel
211 +" in serviceContextPath: "+this.getServiceContextPath()
212 +" with URI: "+this.getServiceContext().getUriInfo().getPath()
217 if (logger.isWarnEnabled() == true) {
218 logger.warn("MultipartInput part was null for document id = " +
223 if (supportsHierarchy() == true) {
224 handleRelationsPayload(wrapDoc, true);
225 handleItemRefNameReferenceUpdate();
230 * Adds the output part.
232 * @param unQObjectProperties the un q object properties
233 * @param schema the schema
234 * @param partMeta the part meta
235 * @throws Exception the exception
236 * MediaType.APPLICATION_XML_TYPE
238 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
240 Element doc = DocumentUtils.buildDocument(partMeta, schema,
241 unQObjectProperties);
242 if (logger.isTraceEnabled() == true) {
243 logger.trace(doc.asXML());
245 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
246 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
250 * Extract paging info.
252 * @param commonsList the commons list
254 * @throws Exception the exception
256 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
258 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
260 DocumentFilter docFilter = this.getDocumentFilter();
261 long pageSize = docFilter.getPageSize();
262 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
263 // set the page size and page number
264 commonList.setPageNum(pageNum);
265 commonList.setPageSize(pageSize);
266 DocumentModelList docList = wrapDoc.getWrappedObject();
267 // Set num of items in list. this is useful to our testing framework.
268 commonList.setItemsInPage(docList.size());
269 // set the total result size
270 commonList.setTotalItems(docList.totalSize());
272 return (TL) commonList;
276 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
279 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
282 DocumentModel docModel = wrapDoc.getWrappedObject();
283 String[] schemas = docModel.getDeclaredSchemas();
284 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
285 for (String schema : schemas) {
286 ObjectPartType partMeta = partsMetaMap.get(schema);
287 if (partMeta == null) {
288 continue; // unknown part, ignore
290 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
291 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
292 addExtraCoreValues(docModel, unQObjectProperties);
294 addOutputPart(unQObjectProperties, schema, partMeta);
297 if (supportsHierarchy() == true) {
298 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
299 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
300 if (Tools.isTrue(showSiblings)) {
301 showSiblings(wrapDoc, ctx);
302 return; // actual result is returned on ctx.addOutputPart();
305 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
306 if (Tools.isTrue(showRelations)) {
307 showRelations(wrapDoc, ctx);
308 return; // actual result is returned on ctx.addOutputPart();
311 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
312 if (Tools.isTrue(showAllRelations)) {
313 showAllRelations(wrapDoc, ctx);
314 return; // actual result is returned on ctx.addOutputPart();
318 addAccountPermissionsPart();
321 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
323 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
326 private void addAccountPermissionsPart() throws Exception {
327 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
330 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
331 String currentServiceName = ctx.getServiceName();
332 String workflowSubResource = "/";
333 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
334 if (jaxRsContext != null) {
335 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
336 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
338 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
340 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
341 currentServiceName, workflowSubResource);
342 org.collectionspace.services.authorization.ObjectFactory objectFactory =
343 new org.collectionspace.services.authorization.ObjectFactory();
344 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
345 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap);
346 ctx.addOutputPart(accountPermissionPart);
352 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
355 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
357 //TODO filling extension parts should be dynamic
358 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
359 //not an ideal way of populating objects.
360 DocumentModel docModel = wrapDoc.getWrappedObject();
361 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
362 PoxPayloadIn input = ctx.getInput();
363 if (input.getParts().isEmpty()) {
364 String msg = "No payload found!";
365 logger.error(msg + "Ctx=" + getServiceContext().toString());
366 throw new BadRequestException(msg);
369 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
371 //iterate over parts received and fill those parts
372 List<PayloadInputPart> inputParts = input.getParts();
373 for (PayloadInputPart part : inputParts) {
375 String partLabel = part.getLabel();
376 if (partLabel == null) {
377 String msg = "Part label is missing or empty!";
378 logger.error(msg + "Ctx=" + getServiceContext().toString());
379 throw new BadRequestException(msg);
382 //skip if the part is not in metadata
383 ObjectPartType partMeta = partsMetaMap.get(partLabel);
384 if (partMeta == null) {
387 fillPart(part, docModel, partMeta, action, ctx);
393 * fillPart fills an XML part into given document model
394 * @param part to fill
395 * @param docModel for the given object
396 * @param partMeta metadata for the object to fill
399 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
400 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
402 //check if this is an xml part
403 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
404 Element element = part.getElementBody();
405 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
406 if (action == Action.UPDATE) {
407 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
409 docModel.setProperties(partMeta.getLabel(), objectProps);
414 * Filters out read only properties, so they cannot be set on update.
415 * TODO: add configuration support to do this generally
416 * @param objectProps the properties parsed from the update payload
417 * @param partMeta metadata for the object to fill
419 public void filterReadOnlyPropertiesForPart(
420 Map<String, Object> objectProps, ObjectPartType partMeta) {
421 // Should add in logic to filter most of the core items on update
422 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
423 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
424 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
425 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
426 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
427 // Note that the updatedAt/updatedBy fields are set internally
428 // in DocumentModelHandler.handleCoreValues().
433 * extractPart extracts an XML object from given DocumentModel
435 * @param schema of the object to extract
436 * @param partMeta metadata for the object to extract
439 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
441 return extractPart(docModel, schema, (Map<String, Object>)null);
445 * extractPart extracts an XML object from given DocumentModel
447 * @param schema of the object to extract
448 * @param partMeta metadata for the object to extract
452 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
454 return extractPart(docModel, schema, partMeta, null);
458 * extractPart extracts an XML object from given DocumentModel
460 * @param schema of the object to extract
461 * @param partMeta metadata for the object to extract
464 protected Map<String, Object> extractPart(
465 DocumentModel docModel,
467 Map<String, Object> addToMap)
469 Map<String, Object> result = null;
471 Map<String, Object> objectProps = docModel.getProperties(schema);
472 if (objectProps != null) {
473 //unqualify properties before sending the doc over the wire (to save bandwidh)
474 //FIXME: is there a better way to avoid duplication of a Map/Collection?
475 Map<String, Object> unQObjectProperties =
476 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
477 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
478 for (Entry<String, Object> entry : qualifiedEntries) {
479 String unqProp = getUnQProperty(entry.getKey());
480 unQObjectProperties.put(unqProp, entry.getValue());
482 result = unQObjectProperties;
489 * extractPart extracts an XML object from given DocumentModel
491 * @param schema of the object to extract
492 * @param partMeta metadata for the object to extract
496 protected Map<String, Object> extractPart(
497 DocumentModel docModel, String schema, ObjectPartType partMeta,
498 Map<String, Object> addToMap)
500 Map<String, Object> result = null;
502 result = this.extractPart(docModel, schema, addToMap);
508 public String getStringPropertyFromDoc(
511 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
512 RepositoryInstance repoSession = null;
513 boolean releaseRepoSession = false;
514 String returnValue = null;
517 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
518 repoSession = this.getRepositorySession();
519 if (repoSession == null) {
520 repoSession = repoClient.getRepositorySession();
521 releaseRepoSession = true;
525 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
526 DocumentModel docModel = wrapper.getWrappedObject();
527 returnValue = (String) docModel.getPropertyValue(propertyXPath);
528 } catch (PropertyException pe) {
530 } catch (DocumentException de) {
532 } catch (Exception e) {
533 if (logger.isDebugEnabled()) {
534 logger.debug("Caught exception ", e);
536 throw new DocumentException(e);
538 if (releaseRepoSession && repoSession != null) {
539 repoClient.releaseRepositorySession(repoSession);
542 } catch (Exception e) {
543 if (logger.isDebugEnabled()) {
544 logger.debug("Caught exception ", e);
546 throw new DocumentException(e);
550 if (logger.isWarnEnabled() == true) {
551 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
560 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
563 public AuthorityRefList getAuthorityRefs(
565 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
567 AuthorityRefList authRefList = new AuthorityRefList();
568 AbstractCommonList commonList = (AbstractCommonList) authRefList;
570 DocumentFilter docFilter = this.getDocumentFilter();
571 long pageSize = docFilter.getPageSize();
572 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
573 // set the page size and page number
574 commonList.setPageNum(pageNum);
575 commonList.setPageSize(pageSize);
577 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
580 int iFirstToUse = (int)(pageSize*pageNum);
581 int nFoundInPage = 0;
584 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
585 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
587 boolean releaseRepoSession = false;
588 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
589 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
590 RepositoryInstance repoSession = this.getRepositorySession();
591 if (repoSession == null) {
592 repoSession = repoClient.getRepositorySession(ctx);
593 releaseRepoSession = true;
597 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
598 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
599 // Slightly goofy pagination support - how many refs do we expect from one object?
600 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
601 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
602 if(appendToAuthRefsList(ari, list)) {
611 if (releaseRepoSession == true) {
612 repoClient.releaseRepositorySession(ctx, repoSession);
616 // Set num of items in list. this is useful to our testing framework.
617 commonList.setItemsInPage(nFoundInPage);
618 // set the total result size
619 commonList.setTotalItems(nFoundTotal);
621 } catch (PropertyException pe) {
622 String msg = "Attempted to retrieve value for invalid or missing authority field. "
623 + "Check authority field properties in tenant bindings.";
624 logger.warn(msg, pe);
626 } catch (Exception e) {
627 if (logger.isDebugEnabled()) {
628 logger.debug("Caught exception in getAuthorityRefs", e);
630 Response response = Response.status(
631 Response.Status.INTERNAL_SERVER_ERROR).entity(
632 "Failed to retrieve authority references").type(
633 "text/plain").build();
634 throw new WebApplicationException(response);
640 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
641 List<AuthorityRefList.AuthorityRefItem> list)
643 String fieldName = ari.getQualifiedDisplayName();
645 String refNameValue = (String)ari.getProperty().getValue();
646 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
647 if(item!=null) { // ignore garbage values.
651 } catch(PropertyException pe) {
652 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
657 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
659 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
661 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
662 ilistItem.setRefName(refName);
663 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
664 ilistItem.setItemDisplayName(termInfo.displayName);
665 ilistItem.setSourceField(authRefFieldName);
666 ilistItem.setUri(termInfo.getRelativeUri());
667 } catch (Exception e) {
668 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
675 * Returns the primary value from a list of values.
677 * Assumes that the first value is the primary value.
678 * This assumption may change when and if the primary value
679 * is identified explicitly.
681 * @param values a list of values.
682 * @param propertyName the name of a property through
683 * which the value can be extracted.
684 * @return the primary value.
685 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
686 String primaryValue = "";
687 if (values == null || values.size() == 0) {
690 Object value = values.get(0);
691 if (value instanceof String) {
693 primaryValue = (String) value;
695 // Multivalue group of fields
696 } else if (value instanceof Map) {
698 Map map = (Map) value;
699 if (map.values().size() > 0) {
700 if (map.get(propertyName) != null) {
701 primaryValue = (String) map.get(propertyName);
706 logger.warn("Unexpected type for property " + propertyName
707 + " in multivalue list: not String or Map.");
714 * Gets a simple property from the document.
716 * For completeness, as this duplicates DocumentModel method.
718 * @param docModel The document model to get info from
719 * @param schema The name of the schema (part)
720 * @param propertyName The simple scalar property type
721 * @return property value as String
723 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
724 String xpath = "/"+schema+":"+propName;
726 return (String)docModel.getPropertyValue(xpath);
727 } catch(PropertyException pe) {
728 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
729 +pe.getLocalizedMessage());
730 } catch(ClassCastException cce) {
731 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
732 +cce.getLocalizedMessage());
733 } catch(Exception e) {
734 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
735 +e.getLocalizedMessage());
740 * Gets first of a repeating list of scalar values, as a String, from the document.
742 * @param docModel The document model to get info from
743 * @param schema The name of the schema (part)
744 * @param listName The name of the scalar list property
745 * @return first value in list, as a String, or empty string if the list is empty
747 protected String getFirstRepeatingStringProperty(
748 DocumentModel docModel, String schema, String listName) {
749 String xpath = "/"+schema+":"+listName+"/[0]";
751 return (String)docModel.getPropertyValue(xpath);
752 } catch(PropertyException pe) {
753 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
754 +pe.getLocalizedMessage());
755 } catch(IndexOutOfBoundsException ioobe) {
756 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
757 return ""; // gracefully handle missing elements
758 } catch(ClassCastException cce) {
759 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
760 +cce.getLocalizedMessage());
761 } catch(Exception e) {
762 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
763 +e.getLocalizedMessage());
769 * Gets first of a repeating list of scalar values, as a String, from the document.
771 * @param docModel The document model to get info from
772 * @param schema The name of the schema (part)
773 * @param listName The name of the scalar list property
774 * @return first value in list, as a String, or empty string if the list is empty
776 protected String getStringValueInPrimaryRepeatingComplexProperty(
777 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
778 String result = null;
780 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
782 result = (String)docModel.getPropertyValue(xpath);
783 } catch(PropertyException pe) {
784 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
785 +pe.getLocalizedMessage());
786 } catch(IndexOutOfBoundsException ioobe) {
787 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
788 result = ""; // gracefully handle missing elements
789 } catch(ClassCastException cce) {
790 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
791 +cce.getLocalizedMessage());
792 } catch(Exception e) {
793 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
794 +e.getLocalizedMessage());
801 * Gets XPath value from schema. Note that only "/" and "[n]" are
802 * supported for xpath. Can omit grouping elements for repeating complex types,
803 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
804 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
805 * If there are no entries for a list of scalars or for a list of complex types,
806 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
807 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
808 * that many elements in the list.
809 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
811 * @param docModel The document model to get info from
812 * @param schema The name of the schema (part)
813 * @param xpath The XPath expression (without schema prefix)
814 * @return value the indicated property value as a String
816 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
817 String schema, ListResultField field) {
818 Object result = null;
820 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
825 protected String getStringValue(DocumentModel docModel,
826 String schema, ListResultField field) {
827 String result = null;
829 Object value = getListResultValue(docModel, schema, field);
830 if (value != null && value instanceof String) {
831 String strValue = (String) value;
832 if (strValue.trim().isEmpty() == false) {
840 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
844 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
846 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
848 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
850 sb.append(item.getPredicate());
852 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
856 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
857 StringBuilder sb = new StringBuilder();
859 if (list.size() > 0) {
860 sb.append("=========== " + label + " ==========" + CR);
862 for (RelationsCommonList.RelationListItem item : list) {
863 itemToString(sb, "== ", item);
866 return sb.toString();
869 /** @return null on parent not found
871 protected String getParentCSID(String thisCSID) throws Exception {
872 String parentCSID = null;
874 String predicate = RelationshipType.HAS_BROADER.value();
875 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
876 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
877 if (parentList != null) {
878 if (parentList.size() == 0) {
881 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
882 parentCSID = relationListItem.getObjectCsid();
885 } catch (Exception e) {
886 logger.error("Could not find parent for this: " + thisCSID, e);
891 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
892 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
896 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
897 MultipartServiceContext ctx) throws Exception {
898 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
899 String parentCSID = getParentCSID(thisCSID);
900 if (parentCSID == null) {
901 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
905 String predicate = RelationshipType.HAS_BROADER.value();
906 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
907 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
909 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
912 RelationsCommonList.RelationListItem item = null;
913 for (RelationsCommonList.RelationListItem sibling : siblingList) {
914 if (thisCSID.equals(sibling.getSubjectCsid())) {
915 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.
918 //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.
919 for (RelationsCommonList.RelationListItem self : toRemoveList) {
920 removeFromList(siblingList, self);
923 long siblingSize = siblingList.size();
924 siblingListOuter.setTotalItems(siblingSize);
925 siblingListOuter.setItemsInPage(siblingSize);
926 if(logger.isTraceEnabled()) {
927 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
928 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
931 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
932 ctx.addOutputPart(relationsPart);
935 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
936 MultipartServiceContext ctx) throws Exception {
937 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
939 String predicate = RelationshipType.HAS_BROADER.value();
940 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
941 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
943 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
944 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
946 if(logger.isTraceEnabled()) {
947 String dump = dumpLists(thisCSID, parentList, childrenList, null);
948 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
951 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
952 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
953 //Not optimal, but that's the current design spec.
955 for (RelationsCommonList.RelationListItem parent : parentList) {
956 childrenList.add(parent);
959 long childrenSize = childrenList.size();
960 childrenListOuter.setTotalItems(childrenSize);
961 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
963 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
964 ctx.addOutputPart(relationsPart);
967 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
968 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
970 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
971 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
973 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
974 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
976 if(logger.isTraceEnabled()) {
977 String dump = dumpLists(thisCSID, subjectList, objectList, null);
978 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
981 subjectList.addAll(objectList);
983 //now subjectList actually has records BOTH where thisCSID is subject and object.
984 long relatedSize = subjectList.size();
985 subjectListOuter.setTotalItems(relatedSize);
986 subjectListOuter.setItemsInPage(relatedSize);
988 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
989 ctx.addOutputPart(relationsPart);
992 private String dumpLists(String itemCSID,
993 List<RelationsCommonList.RelationListItem> parentList,
994 List<RelationsCommonList.RelationListItem> childList,
995 List<RelationsCommonList.RelationListItem> actionList) {
996 StringBuilder sb = new StringBuilder();
997 sb.append("itemCSID: " + itemCSID + CR);
998 if(parentList!=null) {
999 sb.append(dumpList(parentList, "parentList"));
1001 if(childList!=null) {
1002 sb.append(dumpList(childList, "childList"));
1004 if(actionList!=null) {
1005 sb.append(dumpList(actionList, "actionList"));
1007 return sb.toString();
1010 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1011 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1012 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1013 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1014 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1015 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1016 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1018 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1019 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1020 return relationsCommonList;
1022 //============================= END TODO refactor ==========================
1024 // this method calls the RelationResource to have it create the relations and persist them.
1025 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1026 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1027 for (RelationsCommonList.RelationListItem item : inboundList) {
1028 RelationsCommon rc = new RelationsCommon();
1029 //rc.setCsid(item.getCsid());
1030 //todo: assignTo(item, rc);
1031 RelationsDocListItem itemSubject = item.getSubject();
1032 RelationsDocListItem itemObject = item.getObject();
1034 // Set at least one of CSID and refName for Subject and Object
1035 // Either value might be null for for each of Subject and Object
1036 String subjectCsid = itemSubject.getCsid();
1037 rc.setSubjectCsid(subjectCsid);
1039 String objCsid = itemObject.getCsid();
1040 rc.setObjectCsid(objCsid);
1042 rc.setSubjectRefName(itemSubject.getRefName());
1043 rc.setObjectRefName(itemObject.getRefName());
1045 rc.setRelationshipType(item.getPredicate());
1046 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1047 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1049 // This is superfluous, since it will be fetched by the Relations Create logic.
1050 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1051 rc.setObjectDocumentType(itemObject.getDocumentType());
1053 // This is superfluous, since it will be fetched by the Relations Create logic.
1054 rc.setSubjectUri(itemSubject.getUri());
1055 rc.setObjectUri(itemObject.getUri());
1056 // May not have the info here. Only really require CSID or refName.
1057 // Rest is handled in the Relation create mechanism
1058 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1060 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1061 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1062 payloadOut.addPart(outputPart);
1063 RelationResource relationResource = new RelationResource();
1064 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1065 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1069 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1070 // But item1 must not be sparse
1071 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1072 if (item1 == null || item2 == null) {
1075 RelationsDocListItem subj1 = item1.getSubject();
1076 RelationsDocListItem subj2 = item2.getSubject();
1077 RelationsDocListItem obj1 = item1.getObject();
1078 RelationsDocListItem obj2 = item2.getObject();
1079 String subj1Csid = subj1.getCsid();
1080 String subj2Csid = subj2.getCsid();
1081 String subj1RefName = subj1.getRefName();
1082 String subj2RefName = subj2.getRefName();
1084 String obj1Csid = obj1.getCsid();
1085 String obj2Csid = obj2.getCsid();
1086 String obj1RefName = obj1.getRefName();
1087 String obj2RefName = obj2.getRefName();
1090 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1091 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1092 // predicate is proper, but still allow relationshipType
1093 && (item1.getPredicate().equals(item2.getPredicate())
1094 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1095 // Allow missing docTypes, so long as they do not conflict
1096 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1097 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1101 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1102 // But the list items must not be sparse
1103 private RelationsCommonList.RelationListItem findInList(
1104 List<RelationsCommonList.RelationListItem> list,
1105 RelationsCommonList.RelationListItem item) {
1106 RelationsCommonList.RelationListItem foundItem = null;
1107 for (RelationsCommonList.RelationListItem listItem : list) {
1108 if (itemsEqual(listItem, item)) { //equals must be defined, else
1109 foundItem = listItem;
1116 /** updateRelations strategy:
1119 go through inboundList, remove anything from childList that matches from childList
1120 go through inboundList, remove anything from parentList that matches from parentList
1121 go through parentList, delete all remaining
1122 go through childList, delete all remaining
1123 go through actionList, add all remaining.
1124 check for duplicate children
1125 check for more than one parent.
1127 inboundList parentList childList actionList
1128 ---------------- --------------- ---------------- ----------------
1129 child-a parent-c child-a child-b
1130 child-b parent-d child-c
1135 private RelationsCommonList updateRelations(
1136 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1138 if (logger.isTraceEnabled()) {
1139 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1141 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1143 return null; //nothing to do--they didn't send a list of relations.
1145 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1146 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1147 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1148 List<RelationsCommonList.RelationListItem> childList = null;
1149 List<RelationsCommonList.RelationListItem> parentList = null;
1150 DocumentModel docModel = wrapDoc.getWrappedObject();
1151 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
1153 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1154 //Do magic replacement of ${itemCSID} and fix URI's.
1155 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1157 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1158 UriInfo uriInfo = ctx.getUriInfo();
1159 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1162 //Run getList() once as sent to get childListOuter:
1163 String predicate = RelationshipType.HAS_BROADER.value();
1164 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1165 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1166 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1167 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1168 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1170 RelationResource relationResource = new RelationResource();
1171 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1173 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1174 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1175 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1176 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1177 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1180 childList = childListOuter.getRelationListItem();
1181 parentList = parentListOuter.getRelationListItem();
1183 if (parentList.size() > 1) {
1184 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1187 if (logger.isTraceEnabled()) {
1188 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1192 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1193 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1194 // and so the CSID for those may be null
1195 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1196 // Look for parents and children
1197 if(itemCSID.equals(inboundItem.getObject().getCsid())
1198 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1199 //then this is an item that says we have a child. That child is inboundItem
1200 RelationsCommonList.RelationListItem childItem =
1201 (childList == null) ? null : findInList(childList, inboundItem);
1202 if (childItem != null) {
1203 if (logger.isTraceEnabled()) {
1204 StringBuilder sb = new StringBuilder();
1205 itemToString(sb, "== Child: ", childItem);
1206 logger.trace("Found inboundChild in current child list: " + sb.toString());
1208 removeFromList(childList, childItem); //exists, just take it off delete list
1210 if (logger.isTraceEnabled()) {
1211 StringBuilder sb = new StringBuilder();
1212 itemToString(sb, "== Child: ", inboundItem);
1213 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1215 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1216 String newChildCsid = inboundItem.getSubject().getCsid();
1217 if(newChildCsid == null) {
1218 String newChildRefName = inboundItem.getSubject().getRefName();
1219 if(newChildRefName==null) {
1220 throw new RuntimeException("Child with no CSID or refName!");
1222 if (logger.isTraceEnabled()) {
1223 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1225 DocumentModel newChildDocModel =
1226 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1227 newChildRefName, getServiceContext().getResourceMap());
1228 newChildCsid = getCsid(newChildDocModel);
1230 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1233 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1234 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1235 //then this is an item that says we have a parent. inboundItem is that parent.
1236 RelationsCommonList.RelationListItem parentItem =
1237 (parentList == null) ? null : findInList(parentList, inboundItem);
1238 if (parentItem != null) {
1239 removeFromList(parentList, parentItem); //exists, just take it off delete list
1241 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1244 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1247 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1250 if (logger.isTraceEnabled()) {
1251 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1252 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1255 if (logger.isTraceEnabled()) {
1256 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1257 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1259 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1260 deleteRelations(childList, ctx, "childList");
1262 if (logger.isTraceEnabled()) {
1263 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1264 + actionList.size() + " new parents and children.");
1266 createRelations(actionList, ctx);
1267 if (logger.isTraceEnabled()) {
1268 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1270 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1271 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1272 return relationsCommonListBody;
1275 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1276 * and sets URI correctly for related items.
1277 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1279 protected void fixupInboundListItems(ServiceContext ctx,
1280 List<RelationsCommonList.RelationListItem> inboundList,
1281 DocumentModel docModel,
1282 String itemCSID) throws Exception {
1283 String thisURI = this.getUri(docModel);
1284 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1285 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1286 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1287 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1288 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1290 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1291 inboundItem.setObjectCsid(itemCSID);
1292 inboundItemObject.setCsid(itemCSID);
1293 //inboundItemObject.setUri(getUri(docModel));
1296 String objectCsid = inboundItemObject.getCsid();
1297 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1298 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1299 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1300 inboundItemObject.setUri(uri); //CSPACE-4037
1303 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1305 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1306 inboundItem.setSubjectCsid(itemCSID);
1307 inboundItemSubject.setCsid(itemCSID);
1308 //inboundItemSubject.setUri(getUri(docModel));
1311 String subjectCsid = inboundItemSubject.getCsid();
1312 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1313 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1314 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1315 inboundItemSubject.setUri(uri); //CSPACE-4037
1318 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1323 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1324 MultivaluedMap<String, String> queryParams, String childCSID) {
1325 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1326 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1327 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1328 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1330 RelationResource relationResource = new RelationResource();
1331 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1332 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1333 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1334 deleteRelations(parentList, ctx, "parentList-delete");
1337 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1338 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1341 for (RelationsCommonList.RelationListItem item : list) {
1342 RelationResource relationResource = new RelationResource();
1343 if(logger.isTraceEnabled()) {
1344 StringBuilder sb = new StringBuilder();
1345 itemToString(sb, "==== TO DELETE: ", item);
1346 logger.trace(sb.toString());
1348 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1349 if (logger.isDebugEnabled()) {
1350 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1353 } catch (Throwable t) {
1354 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1359 // Note that we must do this after we have completed the Update, so that the repository has the
1360 // info for the item itself. The relations code must call into the repo to get info for each end.
1361 // This could be optimized to pass in the parent docModel, since it will often be one end.
1362 // Nevertheless, we should complete the item save before we do work on the relations, especially
1363 // since a save on Create might fail, and we would not want to create relations for something
1364 // that may not be created...
1365 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1366 ServiceContext ctx = getServiceContext();
1367 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1368 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1369 String itemCsid = documentModel.getName();
1371 //Updates relations part
1372 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1374 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
1375 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1377 //now we add part for relations list
1378 //ServiceContext ctx = getServiceContext();
1379 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1380 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1384 * Checks to see if the refName has changed, and if so,
1385 * uses utilities to find all references and update them.
1388 protected void handleItemRefNameReferenceUpdate() throws Exception {
1389 if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
1390 // We have work to do.
1391 if (logger.isDebugEnabled()) {
1392 String eol = System.getProperty("line.separator");
1393 logger.debug("Need to find and update references to Item." + eol
1394 + " Old refName" + oldRefNameOnUpdate + eol
1395 + " New refName" + newRefNameOnUpdate);
1397 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1398 RepositoryClient repoClient = getRepositoryClient(ctx);
1399 String refNameProp = getRefPropName();
1401 int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
1402 oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
1403 if (logger.isDebugEnabled()) {
1404 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
1410 * Note: The Vocabulary document handler overrides this method.
1412 protected String getRefPropName() {
1413 return ServiceBindingUtils.AUTH_REF_PROP;