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.core.MediaType;
34 import javax.ws.rs.core.MultivaluedMap;
35 import javax.ws.rs.core.Response;
36 import javax.ws.rs.core.UriInfo;
37 import javax.xml.bind.JAXBElement;
39 import org.collectionspace.authentication.spi.AuthNContext;
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.CSWebApplicationException;
52 import org.collectionspace.services.common.NuxeoBasedResource;
53 import org.collectionspace.services.common.authorityref.AuthorityRefList;
54 import org.collectionspace.services.common.config.ServiceConfigUtils;
55 import org.collectionspace.services.common.context.JaxRsContext;
56 import org.collectionspace.services.common.context.MultipartServiceContext;
57 import org.collectionspace.services.common.context.ServiceBindingUtils;
58 import org.collectionspace.services.common.context.ServiceContext;
59 import org.collectionspace.services.common.document.BadRequestException;
60 import org.collectionspace.services.common.document.DocumentException;
61 import org.collectionspace.services.common.document.DocumentUtils;
62 import org.collectionspace.services.common.document.DocumentWrapper;
63 import org.collectionspace.services.common.document.DocumentFilter;
64 import org.collectionspace.services.client.IRelationsManager;
65 import org.collectionspace.services.common.relation.RelationResource;
66 import org.collectionspace.services.common.repository.RepositoryClient;
67 import org.collectionspace.services.common.security.SecurityUtils;
68 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
69 import org.collectionspace.services.common.api.CommonAPI;
70 import org.collectionspace.services.common.api.RefNameUtils;
71 import org.collectionspace.services.common.api.Tools;
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.nuxeo.util.NuxeoUtils;
78 import org.collectionspace.services.relation.RelationsCommon;
79 import org.collectionspace.services.relation.RelationsCommonList;
80 import org.collectionspace.services.relation.RelationsDocListItem;
81 import org.collectionspace.services.relation.RelationshipType;
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;
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
93 * RemoteDocumentModelHandler
95 * $LastChangedRevision: $
100 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
101 extends DocumentModelHandler<T, TL> {
104 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
105 private final static String CR = "\r\n";
106 private final static String EMPTYSTR = "";
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<PoxPayloadIn, PoxPayloadOut> 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<PoxPayloadIn, PoxPayloadOut> 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 boolean supportsVersioning() {
175 boolean result = false;
177 DocHandlerParams.Params params = null;
179 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
180 params = ServiceConfigUtils.getDocHandlerParams(ctx);
181 Boolean bool = params.isSupportsVersioning();
183 result = bool.booleanValue();
185 } catch (DocumentException e) {
186 // TODO Auto-generated catch block
187 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
188 if (logger.isWarnEnabled() == true) {
198 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
200 // Do nothing by default, but children can override if they want. The real workflow transition happens in the WorkflowDocumemtModelHandler class
204 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
205 super.completeCreate(wrapDoc);
206 if (supportsHierarchy() == true) {
207 handleRelationsPayload(wrapDoc, false);
211 /* NOTE: The authority item doc handler overrides (after calling) this method. It performs refName updates. In this
212 * method we just update any and all relationship records that use refNames that have changed.
214 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
217 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
218 DocumentModel docModel = wrapDoc.getWrappedObject();
219 // We need to return at least those document part(s) and corresponding payloads that were received
220 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
221 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
222 PoxPayloadIn input = ctx.getInput();
224 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
225 for (PayloadInputPart part : inputParts) {
226 String partLabel = part.getLabel();
228 ObjectPartType partMeta = partsMetaMap.get(partLabel);
229 // CSPACE-4030 - generates NPE if the part is missing.
231 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
232 if(unQObjectProperties!=null) {
233 addOutputPart(unQObjectProperties, partLabel, partMeta);
236 } catch (Throwable t){
237 logger.error("Unable to addOutputPart: " + partLabel
238 + " in serviceContextPath: "+this.getServiceContextPath()
239 + " with URI: " + this.getServiceContext().getUriInfo().getPath()
244 if (logger.isWarnEnabled() == true) {
245 logger.warn("MultipartInput part was null for document id = " +
250 // If the resource's service supports hierarchy then we need to perform a little more work
252 if (supportsHierarchy() == true) {
253 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
254 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
259 * Adds the output part.
261 * @param unQObjectProperties the un q object properties
262 * @param schema the schema
263 * @param partMeta the part meta
264 * @throws Exception the exception
265 * MediaType.APPLICATION_XML_TYPE
267 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
269 Element doc = DocumentUtils.buildDocument(partMeta, schema,
270 unQObjectProperties);
271 if (logger.isTraceEnabled() == true) {
272 logger.trace(doc.asXML());
274 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
275 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
279 * Extract paging info.
281 * @param commonsList the commons list
283 * @throws Exception the exception
285 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
287 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
289 DocumentFilter docFilter = this.getDocumentFilter();
290 long pageSize = docFilter.getPageSize();
291 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
292 // set the page size and page number
293 commonList.setPageNum(pageNum);
294 commonList.setPageSize(pageSize);
295 DocumentModelList docList = wrapDoc.getWrappedObject();
296 // Set num of items in list. this is useful to our testing framework.
297 commonList.setItemsInPage(docList.size());
298 // set the total result size
299 commonList.setTotalItems(docList.totalSize());
301 return (TL) commonList;
305 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
308 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
311 DocumentModel docModel = wrapDoc.getWrappedObject();
312 String[] schemas = docModel.getDeclaredSchemas();
313 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
314 for (String schema : schemas) {
315 ObjectPartType partMeta = partsMetaMap.get(schema);
316 if (partMeta == null) {
317 continue; // unknown part, ignore
319 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
320 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
321 addExtraCoreValues(docModel, unQObjectProperties);
323 addOutputPart(unQObjectProperties, schema, partMeta);
326 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
328 if (supportsHierarchy() == true) {
329 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
330 if (Tools.isTrue(showSiblings)) {
331 showSiblings(wrapDoc, ctx);
332 return; // actual result is returned on ctx.addOutputPart();
335 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
336 if (Tools.isTrue(showRelations)) {
337 showRelations(wrapDoc, ctx);
338 return; // actual result is returned on ctx.addOutputPart();
341 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
342 if (Tools.isTrue(showAllRelations)) {
343 showAllRelations(wrapDoc, ctx);
344 return; // actual result is returned on ctx.addOutputPart();
348 String currentUser = ctx.getUserId();
349 if (currentUser.equalsIgnoreCase(AuthNContext.ANONYMOUS_USER) == false) {
350 addAccountPermissionsPart();
354 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
356 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
359 private void addAccountPermissionsPart() throws Exception {
360 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
363 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
364 String currentServiceName = ctx.getServiceName();
365 String workflowSubResource = "/";
366 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
367 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
368 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
369 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
371 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
373 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
374 currentServiceName, workflowSubResource);
375 org.collectionspace.services.authorization.ObjectFactory objectFactory =
376 new org.collectionspace.services.authorization.ObjectFactory();
377 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
378 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
379 ctx.addOutputPart(accountPermissionPart);
385 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
388 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
390 //TODO filling extension parts should be dynamic
391 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
392 //not an ideal way of populating objects.
393 DocumentModel docModel = wrapDoc.getWrappedObject();
394 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
395 PoxPayloadIn input = ctx.getInput();
396 if (input.getParts().isEmpty()) {
397 String msg = "No payload found!";
398 logger.error(msg + "Ctx=" + getServiceContext().toString());
399 throw new BadRequestException(msg);
402 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
404 //iterate over parts received and fill those parts
405 List<PayloadInputPart> inputParts = input.getParts();
406 for (PayloadInputPart part : inputParts) {
408 String partLabel = part.getLabel();
409 if (partLabel == null) {
410 String msg = "Part label is missing or empty!";
411 logger.error(msg + "Ctx=" + getServiceContext().toString());
412 throw new BadRequestException(msg);
415 //skip if the part is not in metadata
416 ObjectPartType partMeta = partsMetaMap.get(partLabel);
417 if (partMeta == null) {
420 fillPart(part, docModel, partMeta, action, ctx);
426 * fillPart fills an XML part into given document model
427 * @param part to fill
428 * @param docModel for the given object
429 * @param partMeta metadata for the object to fill
432 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
433 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
435 //check if this is an xml part
436 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
437 Element element = part.getElementBody();
438 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
439 if (action == Action.UPDATE) {
440 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
442 docModel.setProperties(partMeta.getLabel(), objectProps);
447 * Filters out read only properties, so they cannot be set on update.
448 * TODO: add configuration support to do this generally
449 * @param objectProps the properties parsed from the update payload
450 * @param partMeta metadata for the object to fill
452 public void filterReadOnlyPropertiesForPart(
453 Map<String, Object> objectProps, ObjectPartType partMeta) {
454 // Should add in logic to filter most of the core items on update
455 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
456 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
457 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
458 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
459 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
460 // Note that the updatedAt/updatedBy fields are set internally
461 // in DocumentModelHandler.handleCoreValues().
466 * extractPart extracts an XML object from given DocumentModel
468 * @param schema of the object to extract
469 * @param partMeta metadata for the object to extract
472 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
474 return extractPart(docModel, schema, (Map<String, Object>)null);
478 * extractPart extracts an XML object from given DocumentModel
480 * @param schema of the object to extract
481 * @param partMeta metadata for the object to extract
485 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
487 return extractPart(docModel, schema, partMeta, null);
491 * extractPart extracts an XML object from given DocumentModel
493 * @param schema of the object to extract
494 * @param partMeta metadata for the object to extract
497 protected Map<String, Object> extractPart(
498 DocumentModel docModel,
500 Map<String, Object> addToMap)
502 Map<String, Object> result = null;
504 Map<String, Object> objectProps = docModel.getProperties(schema);
505 if (objectProps != null) {
506 //unqualify properties before sending the doc over the wire (to save bandwidh)
507 //FIXME: is there a better way to avoid duplication of a Map/Collection?
508 Map<String, Object> unQObjectProperties =
509 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
510 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
511 for (Entry<String, Object> entry : qualifiedEntries) {
512 String unqProp = getUnQProperty(entry.getKey());
513 unQObjectProperties.put(unqProp, entry.getValue());
515 result = unQObjectProperties;
522 * extractPart extracts an XML object from given DocumentModel
524 * @param schema of the object to extract
525 * @param partMeta metadata for the object to extract
529 protected Map<String, Object> extractPart(
530 DocumentModel docModel, String schema, ObjectPartType partMeta,
531 Map<String, Object> addToMap)
533 Map<String, Object> result = null;
535 result = this.extractPart(docModel, schema, addToMap);
541 public String getStringPropertyFromDoc(
544 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
545 RepositoryInstance repoSession = null;
546 boolean releaseRepoSession = false;
547 String returnValue = null;
550 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
551 repoSession = this.getRepositorySession();
552 if (repoSession == null) {
553 repoSession = repoClient.getRepositorySession();
554 releaseRepoSession = true;
558 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
559 DocumentModel docModel = wrapper.getWrappedObject();
560 returnValue = (String) docModel.getPropertyValue(propertyXPath);
561 } catch (PropertyException pe) {
563 } catch (DocumentException de) {
565 } catch (Exception e) {
566 if (logger.isDebugEnabled()) {
567 logger.debug("Caught exception ", e);
569 throw new DocumentException(e);
571 if (releaseRepoSession && repoSession != null) {
572 repoClient.releaseRepositorySession(repoSession);
575 } catch (Exception e) {
576 if (logger.isDebugEnabled()) {
577 logger.debug("Caught exception ", e);
579 throw new DocumentException(e);
583 if (logger.isWarnEnabled() == true) {
584 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
593 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
596 public AuthorityRefList getAuthorityRefs(
598 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException, Exception {
600 AuthorityRefList authRefList = new AuthorityRefList();
601 AbstractCommonList commonList = (AbstractCommonList) authRefList;
603 DocumentFilter docFilter = this.getDocumentFilter();
604 long pageSize = docFilter.getPageSize();
605 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
606 // set the page size and page number
607 commonList.setPageNum(pageNum);
608 commonList.setPageSize(pageSize);
610 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
613 int iFirstToUse = (int)(pageSize*pageNum);
614 int nFoundInPage = 0;
617 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
618 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
620 boolean releaseRepoSession = false;
621 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
622 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
623 CoreSessionInterface repoSession = this.getRepositorySession();
624 if (repoSession == null) {
625 repoSession = repoClient.getRepositorySession(ctx);
626 releaseRepoSession = true;
630 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
631 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
632 // Slightly goofy pagination support - how many refs do we expect from one object?
633 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
634 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
635 if(appendToAuthRefsList(ari, list)) {
644 if (releaseRepoSession == true) {
645 repoClient.releaseRepositorySession(ctx, repoSession);
649 // Set num of items in list. this is useful to our testing framework.
650 commonList.setItemsInPage(nFoundInPage);
651 // set the total result size
652 commonList.setTotalItems(nFoundTotal);
654 } catch (PropertyException pe) {
655 String msg = "Attempted to retrieve value for invalid or missing authority field. "
656 + "Check authority field properties in tenant bindings.";
657 logger.warn(msg, pe);
659 } catch (Exception e) {
660 if (logger.isDebugEnabled()) {
661 logger.debug("Caught exception in getAuthorityRefs", e);
663 Response response = Response.status(
664 Response.Status.INTERNAL_SERVER_ERROR).entity(
665 "Failed to retrieve authority references").type(
666 "text/plain").build();
667 throw new CSWebApplicationException(e, response);
673 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
674 List<AuthorityRefList.AuthorityRefItem> list)
676 String fieldName = ari.getQualifiedDisplayName();
678 String refNameValue = (String)ari.getProperty().getValue();
679 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
680 if(item!=null) { // ignore garbage values.
684 } catch(PropertyException pe) {
685 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
690 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
692 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
694 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
695 ilistItem.setRefName(refName);
696 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
697 ilistItem.setItemDisplayName(termInfo.displayName);
698 ilistItem.setSourceField(authRefFieldName);
699 ilistItem.setUri(termInfo.getRelativeUri());
700 } catch (Exception e) {
701 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
708 * Returns the primary value from a list of values.
710 * Assumes that the first value is the primary value.
711 * This assumption may change when and if the primary value
712 * is identified explicitly.
714 * @param values a list of values.
715 * @param propertyName the name of a property through
716 * which the value can be extracted.
717 * @return the primary value.
718 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
719 String primaryValue = "";
720 if (values == null || values.size() == 0) {
723 Object value = values.get(0);
724 if (value instanceof String) {
726 primaryValue = (String) value;
728 // Multivalue group of fields
729 } else if (value instanceof Map) {
731 Map map = (Map) value;
732 if (map.values().size() > 0) {
733 if (map.get(propertyName) != null) {
734 primaryValue = (String) map.get(propertyName);
739 logger.warn("Unexpected type for property " + propertyName
740 + " in multivalue list: not String or Map.");
747 * Gets a simple property from the document.
749 * For completeness, as this duplicates DocumentModel method.
751 * @param docModel The document model to get info from
752 * @param schema The name of the schema (part)
753 * @param propertyName The simple scalar property type
754 * @return property value as String
756 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
757 String xpath = "/"+schema+":"+propName;
759 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
760 } catch(PropertyException pe) {
761 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
762 +pe.getLocalizedMessage());
763 } catch(ClassCastException cce) {
764 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
765 +cce.getLocalizedMessage());
766 } catch(Exception e) {
767 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
768 +e.getLocalizedMessage());
773 * Gets first of a repeating list of scalar values, as a String, from the document.
775 * @param docModel The document model to get info from
776 * @param schema The name of the schema (part)
777 * @param listName The name of the scalar list property
778 * @return first value in list, as a String, or empty string if the list is empty
780 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
781 String schema, String listName) {
782 String xpath = "/" + schema + ":" + listName + "/[0]";
784 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
785 } catch (PropertyException pe) {
786 throw new RuntimeException("Problem retrieving property {" + xpath
787 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
788 } catch (IndexOutOfBoundsException ioobe) {
789 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
790 return ""; // gracefully handle missing elements
791 } catch (ClassCastException cce) {
792 throw new RuntimeException("Problem retrieving property {" + xpath
793 + "} as String. Not a repeating String property?"
794 + cce.getLocalizedMessage());
795 } catch (Exception e) {
796 throw new RuntimeException("Unknown problem retrieving property {"
797 + xpath + "}." + e.getLocalizedMessage());
802 * Gets first of a repeating list of scalar values, as a String, from the document.
804 * @param docModel The document model to get info from
805 * @param schema The name of the schema (part)
806 * @param listName The name of the scalar list property
807 * @return first value in list, as a String, or empty string if the list is empty
809 protected String getStringValueInPrimaryRepeatingComplexProperty(
810 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
811 String result = null;
813 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
815 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
816 } catch(PropertyException pe) {
817 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
818 +pe.getLocalizedMessage());
819 } catch(IndexOutOfBoundsException ioobe) {
820 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
821 result = ""; // gracefully handle missing elements
822 } catch(ClassCastException cce) {
823 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
824 +cce.getLocalizedMessage());
825 } catch(NullPointerException npe) {
826 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
827 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
829 } catch(Exception e) {
830 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
831 +e.getLocalizedMessage());
838 * Gets XPath value from schema. Note that only "/" and "[n]" are
839 * supported for xpath. Can omit grouping elements for repeating complex types,
840 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
841 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
842 * If there are no entries for a list of scalars or for a list of complex types,
843 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
844 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
845 * that many elements in the list.
846 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
848 * @param docModel The document model to get info from
849 * @param schema The name of the schema (part)
850 * @param xpath The XPath expression (without schema prefix)
851 * @return value the indicated property value as a String
852 * @throws DocumentException
854 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
855 String schema, ListResultField field) throws DocumentException {
856 Object result = null;
858 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
863 protected String getStringValue(DocumentModel docModel,
864 String schema, ListResultField field) throws DocumentException {
865 String result = null;
867 Object value = getListResultValue(docModel, schema, field);
868 if (value != null && value instanceof String) {
869 String strValue = (String) value;
870 if (strValue.trim().isEmpty() == false) {
878 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
882 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
884 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
886 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
888 sb.append(item.getPredicate());
890 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
894 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
895 StringBuilder sb = new StringBuilder();
897 if (list.size() > 0) {
898 sb.append("=========== " + label + " ==========" + CR);
900 for (RelationsCommonList.RelationListItem item : list) {
901 itemToString(sb, "== ", item);
904 return sb.toString();
907 /** @return null on parent not found
909 protected String getParentCSID(String thisCSID) throws Exception {
910 String parentCSID = null;
912 String predicate = RelationshipType.HAS_BROADER.value();
913 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
914 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
915 if (parentList != null) {
916 if (parentList.size() == 0) {
919 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
920 parentCSID = relationListItem.getObjectCsid();
923 } catch (Exception e) {
924 logger.error("Could not find parent for this: " + thisCSID, e);
929 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
930 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
934 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
935 MultipartServiceContext ctx) throws Exception {
936 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
937 String parentCSID = getParentCSID(thisCSID);
938 if (parentCSID == null) {
939 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
943 String predicate = RelationshipType.HAS_BROADER.value();
944 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
945 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
947 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
950 RelationsCommonList.RelationListItem item = null;
951 for (RelationsCommonList.RelationListItem sibling : siblingList) {
952 if (thisCSID.equals(sibling.getSubjectCsid())) {
953 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.
956 //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.
957 for (RelationsCommonList.RelationListItem self : toRemoveList) {
958 removeFromList(siblingList, self);
961 long siblingSize = siblingList.size();
962 siblingListOuter.setTotalItems(siblingSize);
963 siblingListOuter.setItemsInPage(siblingSize);
964 if(logger.isTraceEnabled()) {
965 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
966 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
969 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
970 ctx.addOutputPart(relationsPart);
973 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
974 MultipartServiceContext ctx) throws Exception {
975 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
977 String predicate = RelationshipType.HAS_BROADER.value();
978 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
979 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
981 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
982 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
984 if(logger.isTraceEnabled()) {
985 String dump = dumpLists(thisCSID, parentList, childrenList, null);
986 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
989 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
990 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
991 //Not optimal, but that's the current design spec.
993 for (RelationsCommonList.RelationListItem parent : parentList) {
994 childrenList.add(parent);
997 long childrenSize = childrenList.size();
998 childrenListOuter.setTotalItems(childrenSize);
999 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1001 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1002 ctx.addOutputPart(relationsPart);
1005 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1006 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1008 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1009 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1011 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1012 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1014 if(logger.isTraceEnabled()) {
1015 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1016 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1019 subjectList.addAll(objectList);
1021 //now subjectList actually has records BOTH where thisCSID is subject and object.
1022 long relatedSize = subjectList.size();
1023 subjectListOuter.setTotalItems(relatedSize);
1024 subjectListOuter.setItemsInPage(relatedSize);
1026 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1027 ctx.addOutputPart(relationsPart);
1030 private String dumpLists(String itemCSID,
1031 List<RelationsCommonList.RelationListItem> parentList,
1032 List<RelationsCommonList.RelationListItem> childList,
1033 List<RelationsCommonList.RelationListItem> actionList) {
1034 StringBuilder sb = new StringBuilder();
1035 sb.append("itemCSID: " + itemCSID + CR);
1036 if(parentList!=null) {
1037 sb.append(dumpList(parentList, "parentList"));
1039 if(childList!=null) {
1040 sb.append(dumpList(childList, "childList"));
1042 if(actionList!=null) {
1043 sb.append(dumpList(actionList, "actionList"));
1045 return sb.toString();
1048 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1049 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1050 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1051 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1052 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1053 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1054 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1056 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1057 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1058 return relationsCommonList;
1060 //============================= END TODO refactor ==========================
1062 // this method calls the RelationResource to have it create the relations and persist them.
1063 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1064 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1065 for (RelationsCommonList.RelationListItem item : inboundList) {
1066 RelationsCommon rc = new RelationsCommon();
1067 //rc.setCsid(item.getCsid());
1068 //todo: assignTo(item, rc);
1069 RelationsDocListItem itemSubject = item.getSubject();
1070 RelationsDocListItem itemObject = item.getObject();
1072 // Set at least one of CSID and refName for Subject and Object
1073 // Either value might be null for for each of Subject and Object
1074 String subjectCsid = itemSubject.getCsid();
1075 rc.setSubjectCsid(subjectCsid);
1077 String objCsid = itemObject.getCsid();
1078 rc.setObjectCsid(objCsid);
1080 rc.setSubjectRefName(itemSubject.getRefName());
1081 rc.setObjectRefName(itemObject.getRefName());
1083 rc.setRelationshipType(item.getPredicate());
1084 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1085 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1086 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1088 // This is superfluous, since it will be fetched by the Relations Create logic.
1089 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1090 rc.setObjectDocumentType(itemObject.getDocumentType());
1092 // This is superfluous, since it will be fetched by the Relations Create logic.
1093 rc.setSubjectUri(itemSubject.getUri());
1094 rc.setObjectUri(itemObject.getUri());
1095 // May not have the info here. Only really require CSID or refName.
1096 // Rest is handled in the Relation create mechanism
1097 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1099 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1100 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1101 payloadOut.addPart(outputPart);
1102 RelationResource relationResource = new RelationResource();
1103 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1104 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1108 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1109 // But item1 must not be sparse
1110 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1111 if (item1 == null || item2 == null) {
1114 RelationsDocListItem subj1 = item1.getSubject();
1115 RelationsDocListItem subj2 = item2.getSubject();
1116 RelationsDocListItem obj1 = item1.getObject();
1117 RelationsDocListItem obj2 = item2.getObject();
1119 String subj1Csid = subj1.getCsid();
1120 String subj2Csid = subj2.getCsid();
1121 String subj1RefName = subj1.getRefName();
1122 String subj2RefName = subj2.getRefName();
1124 String obj1Csid = obj1.getCsid();
1125 String obj2Csid = obj2.getCsid();
1126 String obj1RefName = obj1.getRefName();
1127 String obj2RefName = obj2.getRefName();
1129 String item1Metatype = item1.getRelationshipMetaType();
1130 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1132 String item2Metatype = item2.getRelationshipMetaType();
1133 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1135 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1136 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1137 // predicate is proper, but still allow relationshipType
1138 && (item1.getPredicate().equals(item2.getPredicate())
1139 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1140 // Allow missing docTypes, so long as they do not conflict
1141 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1142 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1143 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1147 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1148 // But the list items must not be sparse
1149 private RelationsCommonList.RelationListItem findInList(
1150 List<RelationsCommonList.RelationListItem> list,
1151 RelationsCommonList.RelationListItem item) {
1152 RelationsCommonList.RelationListItem foundItem = null;
1153 for (RelationsCommonList.RelationListItem listItem : list) {
1154 if (itemsEqual(listItem, item)) { //equals must be defined, else
1155 foundItem = listItem;
1162 /** updateRelations strategy:
1165 go through inboundList, remove anything from childList that matches from childList
1166 go through inboundList, remove anything from parentList that matches from parentList
1167 go through parentList, delete all remaining
1168 go through childList, delete all remaining
1169 go through actionList, add all remaining.
1170 check for duplicate children
1171 check for more than one parent.
1173 inboundList parentList childList actionList
1174 ---------------- --------------- ---------------- ----------------
1175 child-a parent-c child-a child-b
1176 child-b parent-d child-c
1181 private RelationsCommonList updateRelations(
1182 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1184 if (logger.isTraceEnabled()) {
1185 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1187 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1189 return null; //nothing to do--they didn't send a list of relations.
1191 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1192 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1193 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1194 List<RelationsCommonList.RelationListItem> childList = null;
1195 List<RelationsCommonList.RelationListItem> parentList = null;
1196 DocumentModel docModel = wrapDoc.getWrappedObject();
1197 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1198 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1200 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1201 //Do magic replacement of ${itemCSID} and fix URI's.
1202 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1204 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1205 UriInfo uriInfo = ctx.getUriInfo();
1206 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1209 //Run getList() once as sent to get childListOuter:
1210 String predicate = RelationshipType.HAS_BROADER.value();
1211 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1212 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1213 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1214 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1215 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1217 RelationResource relationResource = new RelationResource();
1218 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1220 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1221 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1222 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1223 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1224 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1227 childList = childListOuter.getRelationListItem();
1228 parentList = parentListOuter.getRelationListItem();
1230 if (parentList.size() > 1) {
1231 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1234 if (logger.isTraceEnabled()) {
1235 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1239 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1240 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1241 // and so the CSID for those may be null
1242 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1243 // Look for parents and children
1244 if(itemCSID.equals(inboundItem.getObject().getCsid())
1245 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1246 //then this is an item that says we have a child. That child is inboundItem
1247 RelationsCommonList.RelationListItem childItem =
1248 (childList == null) ? null : findInList(childList, inboundItem);
1249 if (childItem != null) {
1250 if (logger.isTraceEnabled()) {
1251 StringBuilder sb = new StringBuilder();
1252 itemToString(sb, "== Child: ", childItem);
1253 logger.trace("Found inboundChild in current child list: " + sb.toString());
1255 removeFromList(childList, childItem); //exists, just take it off delete list
1257 if (logger.isTraceEnabled()) {
1258 StringBuilder sb = new StringBuilder();
1259 itemToString(sb, "== Child: ", inboundItem);
1260 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1262 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1263 String newChildCsid = inboundItem.getSubject().getCsid();
1264 if(newChildCsid == null) {
1265 String newChildRefName = inboundItem.getSubject().getRefName();
1266 if(newChildRefName==null) {
1267 throw new RuntimeException("Child with no CSID or refName!");
1269 if (logger.isTraceEnabled()) {
1270 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1272 DocumentModel newChildDocModel =
1273 NuxeoBasedResource.getDocModelForRefName(this.getRepositorySession(),
1274 newChildRefName, getServiceContext().getResourceMap());
1275 newChildCsid = getCsid(newChildDocModel);
1277 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1280 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1281 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1282 //then this is an item that says we have a parent. inboundItem is that parent.
1283 RelationsCommonList.RelationListItem parentItem =
1284 (parentList == null) ? null : findInList(parentList, inboundItem);
1285 if (parentItem != null) {
1286 removeFromList(parentList, parentItem); //exists, just take it off delete list
1288 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1291 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1294 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1297 if (logger.isTraceEnabled()) {
1298 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1299 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1302 if (logger.isTraceEnabled()) {
1303 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1304 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1306 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1307 deleteRelations(childList, ctx, "childList");
1309 if (logger.isTraceEnabled()) {
1310 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1311 + actionList.size() + " new parents and children.");
1313 createRelations(actionList, ctx);
1314 if (logger.isTraceEnabled()) {
1315 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1317 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1318 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1319 return relationsCommonListBody;
1322 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1323 * and sets URI correctly for related items.
1324 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1326 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1327 List<RelationsCommonList.RelationListItem> inboundList,
1328 DocumentModel docModel,
1329 String itemCSID) throws Exception {
1330 String thisURI = this.getUri(docModel);
1331 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1332 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1333 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1334 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1335 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1337 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1338 inboundItem.setObjectCsid(itemCSID);
1339 inboundItemObject.setCsid(itemCSID);
1340 //inboundItemObject.setUri(getUri(docModel));
1343 String objectCsid = inboundItemObject.getCsid();
1344 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1345 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1346 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1347 inboundItemObject.setUri(uri); //CSPACE-4037
1350 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1352 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1353 inboundItem.setSubjectCsid(itemCSID);
1354 inboundItemSubject.setCsid(itemCSID);
1355 //inboundItemSubject.setUri(getUri(docModel));
1358 String subjectCsid = inboundItemSubject.getCsid();
1359 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1360 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1361 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1362 inboundItemSubject.setUri(uri); //CSPACE-4037
1365 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1370 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1371 MultivaluedMap<String, String> queryParams, String childCSID) {
1372 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1373 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1374 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1375 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1377 RelationResource relationResource = new RelationResource();
1378 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1379 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1380 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1381 deleteRelations(parentList, ctx, "parentList-delete");
1384 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1385 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1388 for (RelationsCommonList.RelationListItem item : list) {
1389 RelationResource relationResource = new RelationResource();
1390 if(logger.isTraceEnabled()) {
1391 StringBuilder sb = new StringBuilder();
1392 itemToString(sb, "==== TO DELETE: ", item);
1393 logger.trace(sb.toString());
1395 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1396 if (logger.isDebugEnabled()) {
1397 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1400 } catch (Throwable t) {
1401 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1406 // Note that we must do this after we have completed the Update, so that the repository has the
1407 // info for the item itself. The relations code must call into the repo to get info for each end.
1408 // This could be optimized to pass in the parent docModel, since it will often be one end.
1409 // Nevertheless, we should complete the item save before we do work on the relations, especially
1410 // since a save on Create might fail, and we would not want to create relations for something
1411 // that may not be created...
1412 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1413 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1414 PoxPayloadIn input = ctx.getInput();
1415 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1416 String itemCsid = documentModel.getName();
1418 //Updates relations part
1419 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1421 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
1422 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1424 //now we add part for relations list
1425 //ServiceContext ctx = getServiceContext();
1426 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1427 ctx.getOutput().addPart(payloadOutputPart);
1431 * Checks to see if the refName has changed, and if so,
1432 * uses utilities to find all references and update them to use the new refName.
1435 protected void handleRefNameReferencesUpdate() throws Exception {
1436 if (hasRefNameUpdate() == true) {
1437 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1438 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1439 CoreSessionInterface repoSession = this.getRepositorySession();
1441 // Update all the relationship records that referred to the old refName
1442 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1443 oldRefNameOnUpdate, newRefNameOnUpdate);
1447 protected String getRefNameUpdate() {
1448 String result = null;
1450 if (hasRefNameUpdate() == true) {
1451 result = newRefNameOnUpdate;
1452 if (logger.isDebugEnabled() == true) {
1453 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1454 newRefNameOnUpdate, oldRefNameOnUpdate));
1462 * Note: The Vocabulary document handler overrides this method.
1464 protected String getRefPropName() {
1465 return ServiceBindingUtils.AUTH_REF_PROP;