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 == null || input.getParts().isEmpty()) {
397 String msg = String.format("No payload found for '%s' action.", action);
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);
425 * fillPart fills an XML part into given document model
426 * @param part to fill
427 * @param docModel for the given object
428 * @param partMeta metadata for the object to fill
431 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
432 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
434 //check if this is an xml part
435 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
436 Element element = part.getElementBody();
437 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
438 if (action == Action.UPDATE) {
439 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
441 docModel.setProperties(partMeta.getLabel(), objectProps);
446 * Filters out read only properties, so they cannot be set on update.
447 * TODO: add configuration support to do this generally
448 * @param objectProps the properties parsed from the update payload
449 * @param partMeta metadata for the object to fill
451 public void filterReadOnlyPropertiesForPart(
452 Map<String, Object> objectProps, ObjectPartType partMeta) {
453 // Should add in logic to filter most of the core items on update
454 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
455 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
456 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
457 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
458 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
459 // Note that the updatedAt/updatedBy fields are set internally
460 // in DocumentModelHandler.handleCoreValues().
465 * extractPart extracts an XML object from given DocumentModel
467 * @param schema of the object to extract
468 * @param partMeta metadata for the object to extract
471 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
473 return extractPart(docModel, schema, (Map<String, Object>)null);
477 * extractPart extracts an XML object from given DocumentModel
479 * @param schema of the object to extract
480 * @param partMeta metadata for the object to extract
484 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
486 return extractPart(docModel, schema, partMeta, null);
490 * extractPart extracts an XML object from given DocumentModel
492 * @param schema of the object to extract
493 * @param partMeta metadata for the object to extract
496 protected Map<String, Object> extractPart(
497 DocumentModel docModel,
499 Map<String, Object> addToMap)
501 Map<String, Object> result = null;
503 Map<String, Object> objectProps = docModel.getProperties(schema);
504 if (objectProps != null) {
505 //unqualify properties before sending the doc over the wire (to save bandwidh)
506 //FIXME: is there a better way to avoid duplication of a Map/Collection?
507 Map<String, Object> unQObjectProperties =
508 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
509 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
510 for (Entry<String, Object> entry : qualifiedEntries) {
511 String unqProp = getUnQProperty(entry.getKey());
512 unQObjectProperties.put(unqProp, entry.getValue());
514 result = unQObjectProperties;
521 * extractPart extracts an XML object from given DocumentModel
523 * @param schema of the object to extract
524 * @param partMeta metadata for the object to extract
528 protected Map<String, Object> extractPart(
529 DocumentModel docModel, String schema, ObjectPartType partMeta,
530 Map<String, Object> addToMap)
532 Map<String, Object> result = null;
534 result = this.extractPart(docModel, schema, addToMap);
540 public String getStringPropertyFromDoc(
543 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
544 RepositoryInstance repoSession = null;
545 boolean releaseRepoSession = false;
546 String returnValue = null;
549 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
550 repoSession = this.getRepositorySession();
551 if (repoSession == null) {
552 repoSession = repoClient.getRepositorySession();
553 releaseRepoSession = true;
557 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
558 DocumentModel docModel = wrapper.getWrappedObject();
559 returnValue = (String) docModel.getPropertyValue(propertyXPath);
560 } catch (PropertyException pe) {
562 } catch (DocumentException de) {
564 } catch (Exception e) {
565 if (logger.isDebugEnabled()) {
566 logger.debug("Caught exception ", e);
568 throw new DocumentException(e);
570 if (releaseRepoSession && repoSession != null) {
571 repoClient.releaseRepositorySession(repoSession);
574 } catch (Exception e) {
575 if (logger.isDebugEnabled()) {
576 logger.debug("Caught exception ", e);
578 throw new DocumentException(e);
582 if (logger.isWarnEnabled() == true) {
583 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
592 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
595 public AuthorityRefList getAuthorityRefs(
597 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException, Exception {
599 AuthorityRefList authRefList = new AuthorityRefList();
600 AbstractCommonList commonList = (AbstractCommonList) authRefList;
602 DocumentFilter docFilter = this.getDocumentFilter();
603 long pageSize = docFilter.getPageSize();
604 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
605 // set the page size and page number
606 commonList.setPageNum(pageNum);
607 commonList.setPageSize(pageSize);
609 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
612 int iFirstToUse = (int)(pageSize*pageNum);
613 int nFoundInPage = 0;
616 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
617 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
619 boolean releaseRepoSession = false;
620 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
621 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
622 CoreSessionInterface repoSession = this.getRepositorySession();
623 if (repoSession == null) {
624 repoSession = repoClient.getRepositorySession(ctx);
625 releaseRepoSession = true;
629 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
630 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
631 // Slightly goofy pagination support - how many refs do we expect from one object?
632 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
633 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
634 if(appendToAuthRefsList(ari, list)) {
643 if (releaseRepoSession == true) {
644 repoClient.releaseRepositorySession(ctx, repoSession);
648 // Set num of items in list. this is useful to our testing framework.
649 commonList.setItemsInPage(nFoundInPage);
650 // set the total result size
651 commonList.setTotalItems(nFoundTotal);
653 } catch (PropertyException pe) {
654 String msg = "Attempted to retrieve value for invalid or missing authority field. "
655 + "Check authority field properties in tenant bindings.";
656 logger.warn(msg, pe);
658 } catch (Exception e) {
659 if (logger.isDebugEnabled()) {
660 logger.debug("Caught exception in getAuthorityRefs", e);
662 Response response = Response.status(
663 Response.Status.INTERNAL_SERVER_ERROR).entity(
664 "Failed to retrieve authority references").type(
665 "text/plain").build();
666 throw new CSWebApplicationException(e, response);
672 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
673 List<AuthorityRefList.AuthorityRefItem> list)
675 String fieldName = ari.getQualifiedDisplayName();
677 String refNameValue = (String)ari.getProperty().getValue();
678 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
679 if(item!=null) { // ignore garbage values.
683 } catch(PropertyException pe) {
684 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
689 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
691 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
693 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
694 ilistItem.setRefName(refName);
695 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
696 ilistItem.setItemDisplayName(termInfo.displayName);
697 ilistItem.setSourceField(authRefFieldName);
698 ilistItem.setUri(termInfo.getRelativeUri());
699 } catch (Exception e) {
700 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
707 * Returns the primary value from a list of values.
709 * Assumes that the first value is the primary value.
710 * This assumption may change when and if the primary value
711 * is identified explicitly.
713 * @param values a list of values.
714 * @param propertyName the name of a property through
715 * which the value can be extracted.
716 * @return the primary value.
717 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
718 String primaryValue = "";
719 if (values == null || values.size() == 0) {
722 Object value = values.get(0);
723 if (value instanceof String) {
725 primaryValue = (String) value;
727 // Multivalue group of fields
728 } else if (value instanceof Map) {
730 Map map = (Map) value;
731 if (map.values().size() > 0) {
732 if (map.get(propertyName) != null) {
733 primaryValue = (String) map.get(propertyName);
738 logger.warn("Unexpected type for property " + propertyName
739 + " in multivalue list: not String or Map.");
746 * Gets a simple property from the document.
748 * For completeness, as this duplicates DocumentModel method.
750 * @param docModel The document model to get info from
751 * @param schema The name of the schema (part)
752 * @param propertyName The simple scalar property type
753 * @return property value as String
755 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
756 String xpath = "/"+schema+":"+propName;
758 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
759 } catch(PropertyException pe) {
760 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
761 +pe.getLocalizedMessage());
762 } catch(ClassCastException cce) {
763 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
764 +cce.getLocalizedMessage());
765 } catch(Exception e) {
766 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
767 +e.getLocalizedMessage());
772 * Gets first of a repeating list of scalar values, as a String, from the document.
774 * @param docModel The document model to get info from
775 * @param schema The name of the schema (part)
776 * @param listName The name of the scalar list property
777 * @return first value in list, as a String, or empty string if the list is empty
779 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
780 String schema, String listName) {
781 String xpath = "/" + schema + ":" + listName + "/[0]";
783 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
784 } catch (PropertyException pe) {
785 throw new RuntimeException("Problem retrieving property {" + xpath
786 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
787 } catch (IndexOutOfBoundsException ioobe) {
788 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
789 return ""; // gracefully handle missing elements
790 } catch (ClassCastException cce) {
791 throw new RuntimeException("Problem retrieving property {" + xpath
792 + "} as String. Not a repeating String property?"
793 + cce.getLocalizedMessage());
794 } catch (Exception e) {
795 throw new RuntimeException("Unknown problem retrieving property {"
796 + xpath + "}." + e.getLocalizedMessage());
801 * Gets first of a repeating list of scalar values, as a String, from the document.
803 * @param docModel The document model to get info from
804 * @param schema The name of the schema (part)
805 * @param listName The name of the scalar list property
806 * @return first value in list, as a String, or empty string if the list is empty
808 protected String getStringValueInPrimaryRepeatingComplexProperty(
809 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
810 String result = null;
812 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
814 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
815 } catch(PropertyException pe) {
816 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
817 +pe.getLocalizedMessage());
818 } catch(IndexOutOfBoundsException ioobe) {
819 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
820 result = ""; // gracefully handle missing elements
821 } catch(ClassCastException cce) {
822 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
823 +cce.getLocalizedMessage());
824 } catch(NullPointerException npe) {
825 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
826 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
828 } catch(Exception e) {
829 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
830 +e.getLocalizedMessage());
837 * Gets XPath value from schema. Note that only "/" and "[n]" are
838 * supported for xpath. Can omit grouping elements for repeating complex types,
839 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
840 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
841 * If there are no entries for a list of scalars or for a list of complex types,
842 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
843 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
844 * that many elements in the list.
845 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
847 * @param docModel The document model to get info from
848 * @param schema The name of the schema (part)
849 * @param xpath The XPath expression (without schema prefix)
850 * @return value the indicated property value as a String
851 * @throws DocumentException
853 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
854 String schema, ListResultField field) throws DocumentException {
855 Object result = null;
857 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
862 protected String getStringValue(DocumentModel docModel,
863 String schema, ListResultField field) throws DocumentException {
864 String result = null;
866 Object value = getListResultValue(docModel, schema, field);
867 if (value != null && value instanceof String) {
868 String strValue = (String) value;
869 if (strValue.trim().isEmpty() == false) {
877 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
881 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
883 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
885 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
887 sb.append(item.getPredicate());
889 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
893 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
894 StringBuilder sb = new StringBuilder();
896 if (list.size() > 0) {
897 sb.append("=========== " + label + " ==========" + CR);
899 for (RelationsCommonList.RelationListItem item : list) {
900 itemToString(sb, "== ", item);
903 return sb.toString();
906 /** @return null on parent not found
908 protected String getParentCSID(String thisCSID) throws Exception {
909 String parentCSID = null;
911 String predicate = RelationshipType.HAS_BROADER.value();
912 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
913 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
914 if (parentList != null) {
915 if (parentList.size() == 0) {
918 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
919 parentCSID = relationListItem.getObjectCsid();
922 } catch (Exception e) {
923 logger.error("Could not find parent for this: " + thisCSID, e);
928 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
929 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
933 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
934 MultipartServiceContext ctx) throws Exception {
935 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
936 String parentCSID = getParentCSID(thisCSID);
937 if (parentCSID == null) {
938 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
942 String predicate = RelationshipType.HAS_BROADER.value();
943 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
944 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
946 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
949 RelationsCommonList.RelationListItem item = null;
950 for (RelationsCommonList.RelationListItem sibling : siblingList) {
951 if (thisCSID.equals(sibling.getSubjectCsid())) {
952 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.
955 //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.
956 for (RelationsCommonList.RelationListItem self : toRemoveList) {
957 removeFromList(siblingList, self);
960 long siblingSize = siblingList.size();
961 siblingListOuter.setTotalItems(siblingSize);
962 siblingListOuter.setItemsInPage(siblingSize);
963 if(logger.isTraceEnabled()) {
964 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
965 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
968 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
969 ctx.addOutputPart(relationsPart);
972 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
973 MultipartServiceContext ctx) throws Exception {
974 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
976 String predicate = RelationshipType.HAS_BROADER.value();
977 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
978 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
980 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
981 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
983 if(logger.isTraceEnabled()) {
984 String dump = dumpLists(thisCSID, parentList, childrenList, null);
985 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
988 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
989 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
990 //Not optimal, but that's the current design spec.
992 for (RelationsCommonList.RelationListItem parent : parentList) {
993 childrenList.add(parent);
996 long childrenSize = childrenList.size();
997 childrenListOuter.setTotalItems(childrenSize);
998 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1000 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1001 ctx.addOutputPart(relationsPart);
1004 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1005 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1007 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1008 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1010 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1011 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1013 if(logger.isTraceEnabled()) {
1014 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1015 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1018 subjectList.addAll(objectList);
1020 //now subjectList actually has records BOTH where thisCSID is subject and object.
1021 long relatedSize = subjectList.size();
1022 subjectListOuter.setTotalItems(relatedSize);
1023 subjectListOuter.setItemsInPage(relatedSize);
1025 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1026 ctx.addOutputPart(relationsPart);
1029 private String dumpLists(String itemCSID,
1030 List<RelationsCommonList.RelationListItem> parentList,
1031 List<RelationsCommonList.RelationListItem> childList,
1032 List<RelationsCommonList.RelationListItem> actionList) {
1033 StringBuilder sb = new StringBuilder();
1034 sb.append("itemCSID: " + itemCSID + CR);
1035 if(parentList!=null) {
1036 sb.append(dumpList(parentList, "parentList"));
1038 if(childList!=null) {
1039 sb.append(dumpList(childList, "childList"));
1041 if(actionList!=null) {
1042 sb.append(dumpList(actionList, "actionList"));
1044 return sb.toString();
1047 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1048 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1049 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1050 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1051 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1052 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1053 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1055 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1056 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1057 return relationsCommonList;
1059 //============================= END TODO refactor ==========================
1061 // this method calls the RelationResource to have it create the relations and persist them.
1062 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1063 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1064 for (RelationsCommonList.RelationListItem item : inboundList) {
1065 RelationsCommon rc = new RelationsCommon();
1066 //rc.setCsid(item.getCsid());
1067 //todo: assignTo(item, rc);
1068 RelationsDocListItem itemSubject = item.getSubject();
1069 RelationsDocListItem itemObject = item.getObject();
1071 // Set at least one of CSID and refName for Subject and Object
1072 // Either value might be null for for each of Subject and Object
1073 String subjectCsid = itemSubject.getCsid();
1074 rc.setSubjectCsid(subjectCsid);
1076 String objCsid = itemObject.getCsid();
1077 rc.setObjectCsid(objCsid);
1079 rc.setSubjectRefName(itemSubject.getRefName());
1080 rc.setObjectRefName(itemObject.getRefName());
1082 rc.setRelationshipType(item.getPredicate());
1083 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1084 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1085 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1087 // This is superfluous, since it will be fetched by the Relations Create logic.
1088 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1089 rc.setObjectDocumentType(itemObject.getDocumentType());
1091 // This is superfluous, since it will be fetched by the Relations Create logic.
1092 rc.setSubjectUri(itemSubject.getUri());
1093 rc.setObjectUri(itemObject.getUri());
1094 // May not have the info here. Only really require CSID or refName.
1095 // Rest is handled in the Relation create mechanism
1096 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1098 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1099 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1100 payloadOut.addPart(outputPart);
1101 RelationResource relationResource = new RelationResource();
1102 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1103 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1107 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1108 // But item1 must not be sparse
1109 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1110 if (item1 == null || item2 == null) {
1113 RelationsDocListItem subj1 = item1.getSubject();
1114 RelationsDocListItem subj2 = item2.getSubject();
1115 RelationsDocListItem obj1 = item1.getObject();
1116 RelationsDocListItem obj2 = item2.getObject();
1118 String subj1Csid = subj1.getCsid();
1119 String subj2Csid = subj2.getCsid();
1120 String subj1RefName = subj1.getRefName();
1121 String subj2RefName = subj2.getRefName();
1123 String obj1Csid = obj1.getCsid();
1124 String obj2Csid = obj2.getCsid();
1125 String obj1RefName = obj1.getRefName();
1126 String obj2RefName = obj2.getRefName();
1128 String item1Metatype = item1.getRelationshipMetaType();
1129 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1131 String item2Metatype = item2.getRelationshipMetaType();
1132 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1134 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1135 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1136 // predicate is proper, but still allow relationshipType
1137 && (item1.getPredicate().equals(item2.getPredicate())
1138 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1139 // Allow missing docTypes, so long as they do not conflict
1140 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1141 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1142 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1146 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1147 // But the list items must not be sparse
1148 private RelationsCommonList.RelationListItem findInList(
1149 List<RelationsCommonList.RelationListItem> list,
1150 RelationsCommonList.RelationListItem item) {
1151 RelationsCommonList.RelationListItem foundItem = null;
1152 for (RelationsCommonList.RelationListItem listItem : list) {
1153 if (itemsEqual(listItem, item)) { //equals must be defined, else
1154 foundItem = listItem;
1161 /** updateRelations strategy:
1164 go through inboundList, remove anything from childList that matches from childList
1165 go through inboundList, remove anything from parentList that matches from parentList
1166 go through parentList, delete all remaining
1167 go through childList, delete all remaining
1168 go through actionList, add all remaining.
1169 check for duplicate children
1170 check for more than one parent.
1172 inboundList parentList childList actionList
1173 ---------------- --------------- ---------------- ----------------
1174 child-a parent-c child-a child-b
1175 child-b parent-d child-c
1180 private RelationsCommonList updateRelations(
1181 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1183 if (logger.isTraceEnabled()) {
1184 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1186 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1188 return null; //nothing to do--they didn't send a list of relations.
1190 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1191 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1192 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1193 List<RelationsCommonList.RelationListItem> childList = null;
1194 List<RelationsCommonList.RelationListItem> parentList = null;
1195 DocumentModel docModel = wrapDoc.getWrappedObject();
1196 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1197 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1199 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1200 //Do magic replacement of ${itemCSID} and fix URI's.
1201 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1203 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1204 UriInfo uriInfo = ctx.getUriInfo();
1205 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1208 //Run getList() once as sent to get childListOuter:
1209 String predicate = RelationshipType.HAS_BROADER.value();
1210 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1211 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1212 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1213 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1214 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1216 RelationResource relationResource = new RelationResource();
1217 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1219 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1220 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1221 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1222 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1223 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1226 childList = childListOuter.getRelationListItem();
1227 parentList = parentListOuter.getRelationListItem();
1229 if (parentList.size() > 1) {
1230 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1233 if (logger.isTraceEnabled()) {
1234 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1238 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1239 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1240 // and so the CSID for those may be null
1241 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1242 // Look for parents and children
1243 if(itemCSID.equals(inboundItem.getObject().getCsid())
1244 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1245 //then this is an item that says we have a child. That child is inboundItem
1246 RelationsCommonList.RelationListItem childItem =
1247 (childList == null) ? null : findInList(childList, inboundItem);
1248 if (childItem != null) {
1249 if (logger.isTraceEnabled()) {
1250 StringBuilder sb = new StringBuilder();
1251 itemToString(sb, "== Child: ", childItem);
1252 logger.trace("Found inboundChild in current child list: " + sb.toString());
1254 removeFromList(childList, childItem); //exists, just take it off delete list
1256 if (logger.isTraceEnabled()) {
1257 StringBuilder sb = new StringBuilder();
1258 itemToString(sb, "== Child: ", inboundItem);
1259 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1261 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1262 String newChildCsid = inboundItem.getSubject().getCsid();
1263 if(newChildCsid == null) {
1264 String newChildRefName = inboundItem.getSubject().getRefName();
1265 if(newChildRefName==null) {
1266 throw new RuntimeException("Child with no CSID or refName!");
1268 if (logger.isTraceEnabled()) {
1269 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1271 DocumentModel newChildDocModel =
1272 NuxeoBasedResource.getDocModelForRefName(this.getRepositorySession(),
1273 newChildRefName, getServiceContext().getResourceMap());
1274 newChildCsid = getCsid(newChildDocModel);
1276 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1279 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1280 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1281 //then this is an item that says we have a parent. inboundItem is that parent.
1282 RelationsCommonList.RelationListItem parentItem =
1283 (parentList == null) ? null : findInList(parentList, inboundItem);
1284 if (parentItem != null) {
1285 removeFromList(parentList, parentItem); //exists, just take it off delete list
1287 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1290 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1293 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1296 if (logger.isTraceEnabled()) {
1297 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1298 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1301 if (logger.isTraceEnabled()) {
1302 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1303 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1305 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1306 deleteRelations(childList, ctx, "childList");
1308 if (logger.isTraceEnabled()) {
1309 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1310 + actionList.size() + " new parents and children.");
1312 createRelations(actionList, ctx);
1313 if (logger.isTraceEnabled()) {
1314 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1316 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1317 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1318 return relationsCommonListBody;
1321 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1322 * and sets URI correctly for related items.
1323 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1325 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1326 List<RelationsCommonList.RelationListItem> inboundList,
1327 DocumentModel docModel,
1328 String itemCSID) throws Exception {
1329 String thisURI = this.getUri(docModel);
1330 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1331 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1332 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1333 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1334 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1336 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1337 inboundItem.setObjectCsid(itemCSID);
1338 inboundItemObject.setCsid(itemCSID);
1339 //inboundItemObject.setUri(getUri(docModel));
1342 String objectCsid = inboundItemObject.getCsid();
1343 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1344 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1345 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1346 inboundItemObject.setUri(uri); //CSPACE-4037
1349 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1351 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1352 inboundItem.setSubjectCsid(itemCSID);
1353 inboundItemSubject.setCsid(itemCSID);
1354 //inboundItemSubject.setUri(getUri(docModel));
1357 String subjectCsid = inboundItemSubject.getCsid();
1358 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1359 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1360 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1361 inboundItemSubject.setUri(uri); //CSPACE-4037
1364 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1369 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1370 MultivaluedMap<String, String> queryParams, String childCSID) {
1371 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1372 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1373 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1374 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1376 RelationResource relationResource = new RelationResource();
1377 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1378 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1379 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1380 deleteRelations(parentList, ctx, "parentList-delete");
1383 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1384 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1387 for (RelationsCommonList.RelationListItem item : list) {
1388 RelationResource relationResource = new RelationResource();
1389 if(logger.isTraceEnabled()) {
1390 StringBuilder sb = new StringBuilder();
1391 itemToString(sb, "==== TO DELETE: ", item);
1392 logger.trace(sb.toString());
1394 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1395 if (logger.isDebugEnabled()) {
1396 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1399 } catch (Throwable t) {
1400 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1405 // Note that we must do this after we have completed the Update, so that the repository has the
1406 // info for the item itself. The relations code must call into the repo to get info for each end.
1407 // This could be optimized to pass in the parent docModel, since it will often be one end.
1408 // Nevertheless, we should complete the item save before we do work on the relations, especially
1409 // since a save on Create might fail, and we would not want to create relations for something
1410 // that may not be created...
1411 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1412 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1413 PoxPayloadIn input = ctx.getInput();
1414 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1415 String itemCsid = documentModel.getName();
1417 //Updates relations part
1418 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1420 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
1421 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1423 //now we add part for relations list
1424 //ServiceContext ctx = getServiceContext();
1425 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1426 ctx.getOutput().addPart(payloadOutputPart);
1430 * Checks to see if the refName has changed, and if so,
1431 * uses utilities to find all references and update them to use the new refName.
1434 protected void handleRefNameReferencesUpdate() throws Exception {
1435 if (hasRefNameUpdate() == true) {
1436 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1437 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1438 CoreSessionInterface repoSession = this.getRepositorySession();
1440 // Update all the relationship records that referred to the old refName
1441 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1442 oldRefNameOnUpdate, newRefNameOnUpdate);
1446 protected String getRefNameUpdate() {
1447 String result = null;
1449 if (hasRefNameUpdate() == true) {
1450 result = newRefNameOnUpdate;
1451 if (logger.isDebugEnabled() == true) {
1452 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1453 newRefNameOnUpdate, oldRefNameOnUpdate));
1461 * Note: The Vocabulary document handler overrides this method.
1463 protected String getRefPropName() {
1464 return ServiceBindingUtils.AUTH_REF_PROP;