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";
109 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
112 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
113 if (ctx instanceof MultipartServiceContext) {
114 super.setServiceContext(ctx);
116 throw new IllegalArgumentException("setServiceContext requires instance of "
117 + MultipartServiceContext.class.getName());
122 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
123 return getRefnameDisplayName(docWrapper.getWrappedObject());
126 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
127 String result = null;
128 ServiceContext ctx = this.getServiceContext();
130 DocHandlerParams.Params params = null;
132 params = ServiceConfigUtils.getDocHandlerParams(ctx);
133 ListResultField field = params.getRefnameDisplayNameField();
135 String schema = field.getSchema();
136 if (schema == null || schema.trim().isEmpty()) {
137 schema = ctx.getCommonPartLabel();
140 result = getStringValue(docModel, schema, field);
141 } catch (Exception e) {
142 if (logger.isWarnEnabled()) {
143 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
151 public boolean supportsHierarchy() {
152 boolean result = false;
154 DocHandlerParams.Params params = null;
156 ServiceContext ctx = this.getServiceContext();
157 params = ServiceConfigUtils.getDocHandlerParams(ctx);
158 Boolean bool = params.isSupportsHierarchy();
160 result = bool.booleanValue();
162 } catch (DocumentException e) {
163 // TODO Auto-generated catch block
164 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
165 if (logger.isWarnEnabled() == true) {
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);
187 /* NOTE: The authority item doc handler overrides (after calling) this method. It performs refName updates. In this
188 * method we just update any and all relationship records that use refNames that have changed.
190 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
193 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
194 DocumentModel docModel = wrapDoc.getWrappedObject();
195 // We need to return at least those document part(s) and corresponding payloads that were received
196 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
197 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
198 PoxPayloadIn input = ctx.getInput();
200 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
201 for (PayloadInputPart part : inputParts) {
202 String partLabel = part.getLabel();
204 ObjectPartType partMeta = partsMetaMap.get(partLabel);
205 // CSPACE-4030 - generates NPE if the part is missing.
207 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
208 if(unQObjectProperties!=null) {
209 addOutputPart(unQObjectProperties, partLabel, partMeta);
212 } catch (Throwable t){
213 logger.error("Unable to addOutputPart: " + partLabel
214 + " in serviceContextPath: "+this.getServiceContextPath()
215 + " with URI: " + this.getServiceContext().getUriInfo().getPath()
220 if (logger.isWarnEnabled() == true) {
221 logger.warn("MultipartInput part was null for document id = " +
226 // If the resource's service supports hierarchy then we need to perform a little more work
228 if (supportsHierarchy() == true) {
229 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
230 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
235 * Adds the output part.
237 * @param unQObjectProperties the un q object properties
238 * @param schema the schema
239 * @param partMeta the part meta
240 * @throws Exception the exception
241 * MediaType.APPLICATION_XML_TYPE
243 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
245 Element doc = DocumentUtils.buildDocument(partMeta, schema,
246 unQObjectProperties);
247 if (logger.isTraceEnabled() == true) {
248 logger.trace(doc.asXML());
250 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
251 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
255 * Extract paging info.
257 * @param commonsList the commons list
259 * @throws Exception the exception
261 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
263 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
265 DocumentFilter docFilter = this.getDocumentFilter();
266 long pageSize = docFilter.getPageSize();
267 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
268 // set the page size and page number
269 commonList.setPageNum(pageNum);
270 commonList.setPageSize(pageSize);
271 DocumentModelList docList = wrapDoc.getWrappedObject();
272 // Set num of items in list. this is useful to our testing framework.
273 commonList.setItemsInPage(docList.size());
274 // set the total result size
275 commonList.setTotalItems(docList.totalSize());
277 return (TL) commonList;
281 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
284 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
287 DocumentModel docModel = wrapDoc.getWrappedObject();
288 String[] schemas = docModel.getDeclaredSchemas();
289 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
290 for (String schema : schemas) {
291 ObjectPartType partMeta = partsMetaMap.get(schema);
292 if (partMeta == null) {
293 continue; // unknown part, ignore
295 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
296 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
297 addExtraCoreValues(docModel, unQObjectProperties);
299 addOutputPart(unQObjectProperties, schema, partMeta);
302 if (supportsHierarchy() == true) {
303 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
304 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
305 if (Tools.isTrue(showSiblings)) {
306 showSiblings(wrapDoc, ctx);
307 return; // actual result is returned on ctx.addOutputPart();
310 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
311 if (Tools.isTrue(showRelations)) {
312 showRelations(wrapDoc, ctx);
313 return; // actual result is returned on ctx.addOutputPart();
316 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
317 if (Tools.isTrue(showAllRelations)) {
318 showAllRelations(wrapDoc, ctx);
319 return; // actual result is returned on ctx.addOutputPart();
323 addAccountPermissionsPart();
326 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
328 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
331 private void addAccountPermissionsPart() throws Exception {
332 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
335 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
336 String currentServiceName = ctx.getServiceName();
337 String workflowSubResource = "/";
338 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
339 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
340 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
341 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
343 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
345 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
346 currentServiceName, workflowSubResource);
347 org.collectionspace.services.authorization.ObjectFactory objectFactory =
348 new org.collectionspace.services.authorization.ObjectFactory();
349 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
350 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
351 ctx.addOutputPart(accountPermissionPart);
357 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
360 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
362 //TODO filling extension parts should be dynamic
363 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
364 //not an ideal way of populating objects.
365 DocumentModel docModel = wrapDoc.getWrappedObject();
366 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
367 PoxPayloadIn input = ctx.getInput();
368 if (input.getParts().isEmpty()) {
369 String msg = "No payload found!";
370 logger.error(msg + "Ctx=" + getServiceContext().toString());
371 throw new BadRequestException(msg);
374 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
376 //iterate over parts received and fill those parts
377 List<PayloadInputPart> inputParts = input.getParts();
378 for (PayloadInputPart part : inputParts) {
380 String partLabel = part.getLabel();
381 if (partLabel == null) {
382 String msg = "Part label is missing or empty!";
383 logger.error(msg + "Ctx=" + getServiceContext().toString());
384 throw new BadRequestException(msg);
387 //skip if the part is not in metadata
388 ObjectPartType partMeta = partsMetaMap.get(partLabel);
389 if (partMeta == null) {
392 fillPart(part, docModel, partMeta, action, ctx);
398 * fillPart fills an XML part into given document model
399 * @param part to fill
400 * @param docModel for the given object
401 * @param partMeta metadata for the object to fill
404 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
405 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
407 //check if this is an xml part
408 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
409 Element element = part.getElementBody();
410 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
411 if (action == Action.UPDATE) {
412 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
414 docModel.setProperties(partMeta.getLabel(), objectProps);
419 * Filters out read only properties, so they cannot be set on update.
420 * TODO: add configuration support to do this generally
421 * @param objectProps the properties parsed from the update payload
422 * @param partMeta metadata for the object to fill
424 public void filterReadOnlyPropertiesForPart(
425 Map<String, Object> objectProps, ObjectPartType partMeta) {
426 // Should add in logic to filter most of the core items on update
427 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
428 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
429 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
430 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
431 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
432 // Note that the updatedAt/updatedBy fields are set internally
433 // in DocumentModelHandler.handleCoreValues().
438 * extractPart extracts an XML object from given DocumentModel
440 * @param schema of the object to extract
441 * @param partMeta metadata for the object to extract
444 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
446 return extractPart(docModel, schema, (Map<String, Object>)null);
450 * extractPart extracts an XML object from given DocumentModel
452 * @param schema of the object to extract
453 * @param partMeta metadata for the object to extract
457 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
459 return extractPart(docModel, schema, partMeta, null);
463 * extractPart extracts an XML object from given DocumentModel
465 * @param schema of the object to extract
466 * @param partMeta metadata for the object to extract
469 protected Map<String, Object> extractPart(
470 DocumentModel docModel,
472 Map<String, Object> addToMap)
474 Map<String, Object> result = null;
476 Map<String, Object> objectProps = docModel.getProperties(schema);
477 if (objectProps != null) {
478 //unqualify properties before sending the doc over the wire (to save bandwidh)
479 //FIXME: is there a better way to avoid duplication of a Map/Collection?
480 Map<String, Object> unQObjectProperties =
481 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
482 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
483 for (Entry<String, Object> entry : qualifiedEntries) {
484 String unqProp = getUnQProperty(entry.getKey());
485 unQObjectProperties.put(unqProp, entry.getValue());
487 result = unQObjectProperties;
494 * extractPart extracts an XML object from given DocumentModel
496 * @param schema of the object to extract
497 * @param partMeta metadata for the object to extract
501 protected Map<String, Object> extractPart(
502 DocumentModel docModel, String schema, ObjectPartType partMeta,
503 Map<String, Object> addToMap)
505 Map<String, Object> result = null;
507 result = this.extractPart(docModel, schema, addToMap);
513 public String getStringPropertyFromDoc(
516 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
517 RepositoryInstance repoSession = null;
518 boolean releaseRepoSession = false;
519 String returnValue = null;
522 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
523 repoSession = this.getRepositorySession();
524 if (repoSession == null) {
525 repoSession = repoClient.getRepositorySession();
526 releaseRepoSession = true;
530 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
531 DocumentModel docModel = wrapper.getWrappedObject();
532 returnValue = (String) docModel.getPropertyValue(propertyXPath);
533 } catch (PropertyException pe) {
535 } catch (DocumentException de) {
537 } catch (Exception e) {
538 if (logger.isDebugEnabled()) {
539 logger.debug("Caught exception ", e);
541 throw new DocumentException(e);
543 if (releaseRepoSession && repoSession != null) {
544 repoClient.releaseRepositorySession(repoSession);
547 } catch (Exception e) {
548 if (logger.isDebugEnabled()) {
549 logger.debug("Caught exception ", e);
551 throw new DocumentException(e);
555 if (logger.isWarnEnabled() == true) {
556 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
565 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
568 public AuthorityRefList getAuthorityRefs(
570 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
572 AuthorityRefList authRefList = new AuthorityRefList();
573 AbstractCommonList commonList = (AbstractCommonList) authRefList;
575 DocumentFilter docFilter = this.getDocumentFilter();
576 long pageSize = docFilter.getPageSize();
577 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
578 // set the page size and page number
579 commonList.setPageNum(pageNum);
580 commonList.setPageSize(pageSize);
582 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
585 int iFirstToUse = (int)(pageSize*pageNum);
586 int nFoundInPage = 0;
589 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
590 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
592 boolean releaseRepoSession = false;
593 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
594 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
595 RepositoryInstance repoSession = this.getRepositorySession();
596 if (repoSession == null) {
597 repoSession = repoClient.getRepositorySession(ctx);
598 releaseRepoSession = true;
602 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
603 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
604 // Slightly goofy pagination support - how many refs do we expect from one object?
605 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
606 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
607 if(appendToAuthRefsList(ari, list)) {
616 if (releaseRepoSession == true) {
617 repoClient.releaseRepositorySession(ctx, repoSession);
621 // Set num of items in list. this is useful to our testing framework.
622 commonList.setItemsInPage(nFoundInPage);
623 // set the total result size
624 commonList.setTotalItems(nFoundTotal);
626 } catch (PropertyException pe) {
627 String msg = "Attempted to retrieve value for invalid or missing authority field. "
628 + "Check authority field properties in tenant bindings.";
629 logger.warn(msg, pe);
631 } catch (Exception e) {
632 if (logger.isDebugEnabled()) {
633 logger.debug("Caught exception in getAuthorityRefs", e);
635 Response response = Response.status(
636 Response.Status.INTERNAL_SERVER_ERROR).entity(
637 "Failed to retrieve authority references").type(
638 "text/plain").build();
639 throw new WebApplicationException(response);
645 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
646 List<AuthorityRefList.AuthorityRefItem> list)
648 String fieldName = ari.getQualifiedDisplayName();
650 String refNameValue = (String)ari.getProperty().getValue();
651 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
652 if(item!=null) { // ignore garbage values.
656 } catch(PropertyException pe) {
657 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
662 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
664 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
666 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
667 ilistItem.setRefName(refName);
668 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
669 ilistItem.setItemDisplayName(termInfo.displayName);
670 ilistItem.setSourceField(authRefFieldName);
671 ilistItem.setUri(termInfo.getRelativeUri());
672 } catch (Exception e) {
673 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
680 * Returns the primary value from a list of values.
682 * Assumes that the first value is the primary value.
683 * This assumption may change when and if the primary value
684 * is identified explicitly.
686 * @param values a list of values.
687 * @param propertyName the name of a property through
688 * which the value can be extracted.
689 * @return the primary value.
690 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
691 String primaryValue = "";
692 if (values == null || values.size() == 0) {
695 Object value = values.get(0);
696 if (value instanceof String) {
698 primaryValue = (String) value;
700 // Multivalue group of fields
701 } else if (value instanceof Map) {
703 Map map = (Map) value;
704 if (map.values().size() > 0) {
705 if (map.get(propertyName) != null) {
706 primaryValue = (String) map.get(propertyName);
711 logger.warn("Unexpected type for property " + propertyName
712 + " in multivalue list: not String or Map.");
719 * Gets a simple property from the document.
721 * For completeness, as this duplicates DocumentModel method.
723 * @param docModel The document model to get info from
724 * @param schema The name of the schema (part)
725 * @param propertyName The simple scalar property type
726 * @return property value as String
728 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
729 String xpath = "/"+schema+":"+propName;
731 return (String)docModel.getPropertyValue(xpath);
732 } catch(PropertyException pe) {
733 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
734 +pe.getLocalizedMessage());
735 } catch(ClassCastException cce) {
736 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
737 +cce.getLocalizedMessage());
738 } catch(Exception e) {
739 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
740 +e.getLocalizedMessage());
745 * Gets first of a repeating list of scalar values, as a String, from the document.
747 * @param docModel The document model to get info from
748 * @param schema The name of the schema (part)
749 * @param listName The name of the scalar list property
750 * @return first value in list, as a String, or empty string if the list is empty
752 protected String getFirstRepeatingStringProperty(
753 DocumentModel docModel, String schema, String listName) {
754 String xpath = "/"+schema+":"+listName+"/[0]";
756 return (String)docModel.getPropertyValue(xpath);
757 } catch(PropertyException pe) {
758 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
759 +pe.getLocalizedMessage());
760 } catch(IndexOutOfBoundsException ioobe) {
761 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
762 return ""; // gracefully handle missing elements
763 } catch(ClassCastException cce) {
764 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
765 +cce.getLocalizedMessage());
766 } catch(Exception e) {
767 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
768 +e.getLocalizedMessage());
774 * Gets first of a repeating list of scalar values, as a String, from the document.
776 * @param docModel The document model to get info from
777 * @param schema The name of the schema (part)
778 * @param listName The name of the scalar list property
779 * @return first value in list, as a String, or empty string if the list is empty
781 protected String getStringValueInPrimaryRepeatingComplexProperty(
782 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
783 String result = null;
785 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
787 result = (String)docModel.getPropertyValue(xpath);
788 } catch(PropertyException pe) {
789 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
790 +pe.getLocalizedMessage());
791 } catch(IndexOutOfBoundsException ioobe) {
792 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
793 result = ""; // gracefully handle missing elements
794 } catch(ClassCastException cce) {
795 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
796 +cce.getLocalizedMessage());
797 } catch(Exception e) {
798 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
799 +e.getLocalizedMessage());
806 * Gets XPath value from schema. Note that only "/" and "[n]" are
807 * supported for xpath. Can omit grouping elements for repeating complex types,
808 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
809 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
810 * If there are no entries for a list of scalars or for a list of complex types,
811 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
812 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
813 * that many elements in the list.
814 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
816 * @param docModel The document model to get info from
817 * @param schema The name of the schema (part)
818 * @param xpath The XPath expression (without schema prefix)
819 * @return value the indicated property value as a String
821 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
822 String schema, ListResultField field) {
823 Object result = null;
825 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
830 protected String getStringValue(DocumentModel docModel,
831 String schema, ListResultField field) {
832 String result = null;
834 Object value = getListResultValue(docModel, schema, field);
835 if (value != null && value instanceof String) {
836 String strValue = (String) value;
837 if (strValue.trim().isEmpty() == false) {
845 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
849 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
851 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
853 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
855 sb.append(item.getPredicate());
857 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
861 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
862 StringBuilder sb = new StringBuilder();
864 if (list.size() > 0) {
865 sb.append("=========== " + label + " ==========" + CR);
867 for (RelationsCommonList.RelationListItem item : list) {
868 itemToString(sb, "== ", item);
871 return sb.toString();
874 /** @return null on parent not found
876 protected String getParentCSID(String thisCSID) throws Exception {
877 String parentCSID = null;
879 String predicate = RelationshipType.HAS_BROADER.value();
880 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
881 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
882 if (parentList != null) {
883 if (parentList.size() == 0) {
886 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
887 parentCSID = relationListItem.getObjectCsid();
890 } catch (Exception e) {
891 logger.error("Could not find parent for this: " + thisCSID, e);
896 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
897 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
901 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
902 MultipartServiceContext ctx) throws Exception {
903 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
904 String parentCSID = getParentCSID(thisCSID);
905 if (parentCSID == null) {
906 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
910 String predicate = RelationshipType.HAS_BROADER.value();
911 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
912 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
914 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
917 RelationsCommonList.RelationListItem item = null;
918 for (RelationsCommonList.RelationListItem sibling : siblingList) {
919 if (thisCSID.equals(sibling.getSubjectCsid())) {
920 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.
923 //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.
924 for (RelationsCommonList.RelationListItem self : toRemoveList) {
925 removeFromList(siblingList, self);
928 long siblingSize = siblingList.size();
929 siblingListOuter.setTotalItems(siblingSize);
930 siblingListOuter.setItemsInPage(siblingSize);
931 if(logger.isTraceEnabled()) {
932 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
933 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
936 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
937 ctx.addOutputPart(relationsPart);
940 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
941 MultipartServiceContext ctx) throws Exception {
942 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
944 String predicate = RelationshipType.HAS_BROADER.value();
945 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
946 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
948 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
949 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
951 if(logger.isTraceEnabled()) {
952 String dump = dumpLists(thisCSID, parentList, childrenList, null);
953 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
956 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
957 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
958 //Not optimal, but that's the current design spec.
960 for (RelationsCommonList.RelationListItem parent : parentList) {
961 childrenList.add(parent);
964 long childrenSize = childrenList.size();
965 childrenListOuter.setTotalItems(childrenSize);
966 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
968 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
969 ctx.addOutputPart(relationsPart);
972 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
973 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
975 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
976 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
978 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
979 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
981 if(logger.isTraceEnabled()) {
982 String dump = dumpLists(thisCSID, subjectList, objectList, null);
983 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
986 subjectList.addAll(objectList);
988 //now subjectList actually has records BOTH where thisCSID is subject and object.
989 long relatedSize = subjectList.size();
990 subjectListOuter.setTotalItems(relatedSize);
991 subjectListOuter.setItemsInPage(relatedSize);
993 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
994 ctx.addOutputPart(relationsPart);
997 private String dumpLists(String itemCSID,
998 List<RelationsCommonList.RelationListItem> parentList,
999 List<RelationsCommonList.RelationListItem> childList,
1000 List<RelationsCommonList.RelationListItem> actionList) {
1001 StringBuilder sb = new StringBuilder();
1002 sb.append("itemCSID: " + itemCSID + CR);
1003 if(parentList!=null) {
1004 sb.append(dumpList(parentList, "parentList"));
1006 if(childList!=null) {
1007 sb.append(dumpList(childList, "childList"));
1009 if(actionList!=null) {
1010 sb.append(dumpList(actionList, "actionList"));
1012 return sb.toString();
1015 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1016 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1017 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1018 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1019 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1020 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1021 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1023 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1024 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1025 return relationsCommonList;
1027 //============================= END TODO refactor ==========================
1029 // this method calls the RelationResource to have it create the relations and persist them.
1030 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1031 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1032 for (RelationsCommonList.RelationListItem item : inboundList) {
1033 RelationsCommon rc = new RelationsCommon();
1034 //rc.setCsid(item.getCsid());
1035 //todo: assignTo(item, rc);
1036 RelationsDocListItem itemSubject = item.getSubject();
1037 RelationsDocListItem itemObject = item.getObject();
1039 // Set at least one of CSID and refName for Subject and Object
1040 // Either value might be null for for each of Subject and Object
1041 String subjectCsid = itemSubject.getCsid();
1042 rc.setSubjectCsid(subjectCsid);
1044 String objCsid = itemObject.getCsid();
1045 rc.setObjectCsid(objCsid);
1047 rc.setSubjectRefName(itemSubject.getRefName());
1048 rc.setObjectRefName(itemObject.getRefName());
1050 rc.setRelationshipType(item.getPredicate());
1051 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1052 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1053 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1055 // This is superfluous, since it will be fetched by the Relations Create logic.
1056 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1057 rc.setObjectDocumentType(itemObject.getDocumentType());
1059 // This is superfluous, since it will be fetched by the Relations Create logic.
1060 rc.setSubjectUri(itemSubject.getUri());
1061 rc.setObjectUri(itemObject.getUri());
1062 // May not have the info here. Only really require CSID or refName.
1063 // Rest is handled in the Relation create mechanism
1064 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1066 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1067 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1068 payloadOut.addPart(outputPart);
1069 RelationResource relationResource = new RelationResource();
1070 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1071 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1075 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1076 // But item1 must not be sparse
1077 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1078 if (item1 == null || item2 == null) {
1081 RelationsDocListItem subj1 = item1.getSubject();
1082 RelationsDocListItem subj2 = item2.getSubject();
1083 RelationsDocListItem obj1 = item1.getObject();
1084 RelationsDocListItem obj2 = item2.getObject();
1085 String subj1Csid = subj1.getCsid();
1086 String subj2Csid = subj2.getCsid();
1087 String subj1RefName = subj1.getRefName();
1088 String subj2RefName = subj2.getRefName();
1090 String obj1Csid = obj1.getCsid();
1091 String obj2Csid = obj2.getCsid();
1092 String obj1RefName = obj1.getRefName();
1093 String obj2RefName = obj2.getRefName();
1096 (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1097 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1098 // predicate is proper, but still allow relationshipType
1099 && (item1.getPredicate().equals(item2.getPredicate())
1100 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1101 // Allow missing docTypes, so long as they do not conflict
1102 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1103 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1107 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1108 // But the list items must not be sparse
1109 private RelationsCommonList.RelationListItem findInList(
1110 List<RelationsCommonList.RelationListItem> list,
1111 RelationsCommonList.RelationListItem item) {
1112 RelationsCommonList.RelationListItem foundItem = null;
1113 for (RelationsCommonList.RelationListItem listItem : list) {
1114 if (itemsEqual(listItem, item)) { //equals must be defined, else
1115 foundItem = listItem;
1122 /** updateRelations strategy:
1125 go through inboundList, remove anything from childList that matches from childList
1126 go through inboundList, remove anything from parentList that matches from parentList
1127 go through parentList, delete all remaining
1128 go through childList, delete all remaining
1129 go through actionList, add all remaining.
1130 check for duplicate children
1131 check for more than one parent.
1133 inboundList parentList childList actionList
1134 ---------------- --------------- ---------------- ----------------
1135 child-a parent-c child-a child-b
1136 child-b parent-d child-c
1141 private RelationsCommonList updateRelations(
1142 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1144 if (logger.isTraceEnabled()) {
1145 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1147 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1149 return null; //nothing to do--they didn't send a list of relations.
1151 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1152 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1153 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1154 List<RelationsCommonList.RelationListItem> childList = null;
1155 List<RelationsCommonList.RelationListItem> parentList = null;
1156 DocumentModel docModel = wrapDoc.getWrappedObject();
1157 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1158 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1160 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1161 //Do magic replacement of ${itemCSID} and fix URI's.
1162 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1164 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1165 UriInfo uriInfo = ctx.getUriInfo();
1166 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1169 //Run getList() once as sent to get childListOuter:
1170 String predicate = RelationshipType.HAS_BROADER.value();
1171 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1172 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1173 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1174 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1175 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1177 RelationResource relationResource = new RelationResource();
1178 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1180 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1181 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1182 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1183 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1184 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1187 childList = childListOuter.getRelationListItem();
1188 parentList = parentListOuter.getRelationListItem();
1190 if (parentList.size() > 1) {
1191 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1194 if (logger.isTraceEnabled()) {
1195 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1199 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1200 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1201 // and so the CSID for those may be null
1202 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1203 // Look for parents and children
1204 if(itemCSID.equals(inboundItem.getObject().getCsid())
1205 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1206 //then this is an item that says we have a child. That child is inboundItem
1207 RelationsCommonList.RelationListItem childItem =
1208 (childList == null) ? null : findInList(childList, inboundItem);
1209 if (childItem != null) {
1210 if (logger.isTraceEnabled()) {
1211 StringBuilder sb = new StringBuilder();
1212 itemToString(sb, "== Child: ", childItem);
1213 logger.trace("Found inboundChild in current child list: " + sb.toString());
1215 removeFromList(childList, childItem); //exists, just take it off delete list
1217 if (logger.isTraceEnabled()) {
1218 StringBuilder sb = new StringBuilder();
1219 itemToString(sb, "== Child: ", inboundItem);
1220 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1222 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1223 String newChildCsid = inboundItem.getSubject().getCsid();
1224 if(newChildCsid == null) {
1225 String newChildRefName = inboundItem.getSubject().getRefName();
1226 if(newChildRefName==null) {
1227 throw new RuntimeException("Child with no CSID or refName!");
1229 if (logger.isTraceEnabled()) {
1230 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1232 DocumentModel newChildDocModel =
1233 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1234 newChildRefName, getServiceContext().getResourceMap());
1235 newChildCsid = getCsid(newChildDocModel);
1237 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1240 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1241 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1242 //then this is an item that says we have a parent. inboundItem is that parent.
1243 RelationsCommonList.RelationListItem parentItem =
1244 (parentList == null) ? null : findInList(parentList, inboundItem);
1245 if (parentItem != null) {
1246 removeFromList(parentList, parentItem); //exists, just take it off delete list
1248 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1251 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1254 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1257 if (logger.isTraceEnabled()) {
1258 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1259 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1262 if (logger.isTraceEnabled()) {
1263 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1264 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1266 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1267 deleteRelations(childList, ctx, "childList");
1269 if (logger.isTraceEnabled()) {
1270 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1271 + actionList.size() + " new parents and children.");
1273 createRelations(actionList, ctx);
1274 if (logger.isTraceEnabled()) {
1275 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1277 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1278 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1279 return relationsCommonListBody;
1282 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1283 * and sets URI correctly for related items.
1284 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1286 protected void fixupInboundListItems(ServiceContext ctx,
1287 List<RelationsCommonList.RelationListItem> inboundList,
1288 DocumentModel docModel,
1289 String itemCSID) throws Exception {
1290 String thisURI = this.getUri(docModel);
1291 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1292 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1293 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1294 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1295 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1297 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1298 inboundItem.setObjectCsid(itemCSID);
1299 inboundItemObject.setCsid(itemCSID);
1300 //inboundItemObject.setUri(getUri(docModel));
1303 String objectCsid = inboundItemObject.getCsid();
1304 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1305 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1306 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1307 inboundItemObject.setUri(uri); //CSPACE-4037
1310 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1312 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1313 inboundItem.setSubjectCsid(itemCSID);
1314 inboundItemSubject.setCsid(itemCSID);
1315 //inboundItemSubject.setUri(getUri(docModel));
1318 String subjectCsid = inboundItemSubject.getCsid();
1319 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1320 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1321 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1322 inboundItemSubject.setUri(uri); //CSPACE-4037
1325 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1330 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1331 MultivaluedMap<String, String> queryParams, String childCSID) {
1332 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1333 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1334 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1335 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1337 RelationResource relationResource = new RelationResource();
1338 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1339 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1340 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1341 deleteRelations(parentList, ctx, "parentList-delete");
1344 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1345 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1348 for (RelationsCommonList.RelationListItem item : list) {
1349 RelationResource relationResource = new RelationResource();
1350 if(logger.isTraceEnabled()) {
1351 StringBuilder sb = new StringBuilder();
1352 itemToString(sb, "==== TO DELETE: ", item);
1353 logger.trace(sb.toString());
1355 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1356 if (logger.isDebugEnabled()) {
1357 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1360 } catch (Throwable t) {
1361 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1366 // Note that we must do this after we have completed the Update, so that the repository has the
1367 // info for the item itself. The relations code must call into the repo to get info for each end.
1368 // This could be optimized to pass in the parent docModel, since it will often be one end.
1369 // Nevertheless, we should complete the item save before we do work on the relations, especially
1370 // since a save on Create might fail, and we would not want to create relations for something
1371 // that may not be created...
1372 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1373 ServiceContext ctx = getServiceContext();
1374 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1375 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1376 String itemCsid = documentModel.getName();
1378 //Updates relations part
1379 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1381 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
1382 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1384 //now we add part for relations list
1385 //ServiceContext ctx = getServiceContext();
1386 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1387 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1391 * Checks to see if the refName has changed, and if so,
1392 * uses utilities to find all references and update them to use the new refName.
1395 protected void handleRefNameReferencesUpdate() throws Exception {
1396 if (hasRefNameUpdate() == true) {
1397 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1398 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1399 RepositoryInstance repoSession = this.getRepositorySession();
1401 // Update all the relationship records that referred to the old refName
1402 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1403 oldRefNameOnUpdate, newRefNameOnUpdate);
1407 protected String getRefNameUpdate() {
1408 String result = null;
1410 if (hasRefNameUpdate() == true) {
1411 result = newRefNameOnUpdate;
1412 if (logger.isDebugEnabled() == true) {
1413 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1414 newRefNameOnUpdate, oldRefNameOnUpdate));
1422 * Note: The Vocabulary document handler overrides this method.
1424 protected String getRefPropName() {
1425 return ServiceBindingUtils.AUTH_REF_PROP;