2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.nuxeo.client.java;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
30 import java.util.Map.Entry;
33 import javax.ws.rs.WebApplicationException;
34 import javax.ws.rs.core.MediaType;
35 import javax.ws.rs.core.MultivaluedMap;
36 import javax.ws.rs.core.Response;
37 import javax.ws.rs.core.UriInfo;
38 import javax.xml.bind.JAXBElement;
40 import org.collectionspace.services.authorization.AccountPermission;
41 import org.collectionspace.services.jaxb.AbstractCommonList;
42 import org.collectionspace.services.lifecycle.TransitionDef;
43 import org.collectionspace.services.client.CollectionSpaceClient;
44 import org.collectionspace.services.client.PayloadInputPart;
45 import org.collectionspace.services.client.PayloadOutputPart;
46 import org.collectionspace.services.client.PoxPayloadIn;
47 import org.collectionspace.services.client.PoxPayloadOut;
48 import org.collectionspace.services.client.Profiler;
49 import org.collectionspace.services.client.RelationClient;
50 import org.collectionspace.services.client.workflow.WorkflowClient;
51 import org.collectionspace.services.common.ResourceBase;
52 import org.collectionspace.services.common.authorityref.AuthorityRefList;
53 import org.collectionspace.services.common.config.ServiceConfigUtils;
54 import org.collectionspace.services.common.context.JaxRsContext;
55 import org.collectionspace.services.common.context.MultipartServiceContext;
56 import org.collectionspace.services.common.context.ServiceBindingUtils;
57 import org.collectionspace.services.common.context.ServiceContext;
58 import org.collectionspace.services.common.document.BadRequestException;
59 import org.collectionspace.services.common.document.DocumentException;
60 import org.collectionspace.services.common.document.DocumentUtils;
61 import org.collectionspace.services.common.document.DocumentWrapper;
62 import org.collectionspace.services.common.document.DocumentFilter;
63 import org.collectionspace.services.client.IRelationsManager;
64 import org.collectionspace.services.common.relation.RelationResource;
65 import org.collectionspace.services.common.repository.RepositoryClient;
66 import org.collectionspace.services.common.security.SecurityUtils;
67 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
68 import org.collectionspace.services.common.api.CommonAPI;
69 import org.collectionspace.services.common.api.RefNameUtils;
70 import org.collectionspace.services.common.api.Tools;
71 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
72 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
73 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
74 import org.collectionspace.services.config.service.DocHandlerParams;
75 import org.collectionspace.services.config.service.ListResultField;
76 import org.collectionspace.services.config.service.ObjectPartType;
77 import org.collectionspace.services.config.service.ServiceBindingType;
78 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
79 import org.collectionspace.services.relation.RelationsCommon;
80 import org.collectionspace.services.relation.RelationsCommonList;
81 import org.collectionspace.services.relation.RelationsDocListItem;
82 import org.collectionspace.services.relation.RelationshipType;
83 import org.dom4j.Element;
85 import org.nuxeo.ecm.core.api.DocumentModel;
86 import org.nuxeo.ecm.core.api.DocumentModelList;
87 import org.nuxeo.ecm.core.api.model.PropertyException;
88 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
90 import org.slf4j.Logger;
91 import org.slf4j.LoggerFactory;
94 * RemoteDocumentModelHandler
96 * $LastChangedRevision: $
101 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
102 extends DocumentModelHandler<T, TL> {
105 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
106 private final static String CR = "\r\n";
107 private final static String EMPTYSTR = "";
110 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
113 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
114 if (ctx instanceof MultipartServiceContext) {
115 super.setServiceContext(ctx);
117 throw new IllegalArgumentException("setServiceContext requires instance of "
118 + MultipartServiceContext.class.getName());
123 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
124 return getRefnameDisplayName(docWrapper.getWrappedObject());
127 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
128 String result = null;
129 ServiceContext ctx = this.getServiceContext();
131 DocHandlerParams.Params params = null;
133 params = ServiceConfigUtils.getDocHandlerParams(ctx);
134 ListResultField field = params.getRefnameDisplayNameField();
136 String schema = field.getSchema();
137 if (schema == null || schema.trim().isEmpty()) {
138 schema = ctx.getCommonPartLabel();
141 result = getStringValue(docModel, schema, field);
142 } catch (Exception e) {
143 if (logger.isWarnEnabled()) {
144 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
152 public boolean supportsHierarchy() {
153 boolean result = false;
155 DocHandlerParams.Params params = null;
157 ServiceContext ctx = this.getServiceContext();
158 params = ServiceConfigUtils.getDocHandlerParams(ctx);
159 Boolean bool = params.isSupportsHierarchy();
161 result = bool.booleanValue();
163 } catch (DocumentException e) {
164 // TODO Auto-generated catch block
165 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
166 if (logger.isWarnEnabled() == true) {
175 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
177 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
181 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
182 super.completeCreate(wrapDoc);
183 if (supportsHierarchy() == true) {
184 handleRelationsPayload(wrapDoc, false);
188 /* NOTE: The authority item doc handler overrides (after calling) this method. It performs refName updates. In this
189 * method we just update any and all relationship records that use refNames that have changed.
191 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
194 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
195 DocumentModel docModel = wrapDoc.getWrappedObject();
196 // We need to return at least those document part(s) and corresponding payloads that were received
197 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
198 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
199 PoxPayloadIn input = ctx.getInput();
201 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
202 for (PayloadInputPart part : inputParts) {
203 String partLabel = part.getLabel();
205 ObjectPartType partMeta = partsMetaMap.get(partLabel);
206 // CSPACE-4030 - generates NPE if the part is missing.
208 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
209 if(unQObjectProperties!=null) {
210 addOutputPart(unQObjectProperties, partLabel, partMeta);
213 } catch (Throwable t){
214 logger.error("Unable to addOutputPart: " + partLabel
215 + " in serviceContextPath: "+this.getServiceContextPath()
216 + " with URI: " + this.getServiceContext().getUriInfo().getPath()
221 if (logger.isWarnEnabled() == true) {
222 logger.warn("MultipartInput part was null for document id = " +
227 // If the resource's service supports hierarchy then we need to perform a little more work
229 if (supportsHierarchy() == true) {
230 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
231 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
236 * Adds the output part.
238 * @param unQObjectProperties the un q object properties
239 * @param schema the schema
240 * @param partMeta the part meta
241 * @throws Exception the exception
242 * MediaType.APPLICATION_XML_TYPE
244 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
246 Element doc = DocumentUtils.buildDocument(partMeta, schema,
247 unQObjectProperties);
248 if (logger.isTraceEnabled() == true) {
249 logger.trace(doc.asXML());
251 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
252 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
256 * Extract paging info.
258 * @param commonsList the commons list
260 * @throws Exception the exception
262 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
264 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
266 DocumentFilter docFilter = this.getDocumentFilter();
267 long pageSize = docFilter.getPageSize();
268 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
269 // set the page size and page number
270 commonList.setPageNum(pageNum);
271 commonList.setPageSize(pageSize);
272 DocumentModelList docList = wrapDoc.getWrappedObject();
273 // Set num of items in list. this is useful to our testing framework.
274 commonList.setItemsInPage(docList.size());
275 // set the total result size
276 commonList.setTotalItems(docList.totalSize());
278 return (TL) commonList;
282 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
285 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
288 DocumentModel docModel = wrapDoc.getWrappedObject();
289 String[] schemas = docModel.getDeclaredSchemas();
290 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
291 for (String schema : schemas) {
292 ObjectPartType partMeta = partsMetaMap.get(schema);
293 if (partMeta == null) {
294 continue; // unknown part, ignore
296 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
297 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
298 addExtraCoreValues(docModel, unQObjectProperties);
300 addOutputPart(unQObjectProperties, schema, partMeta);
303 if (supportsHierarchy() == true) {
304 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
305 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
306 if (Tools.isTrue(showSiblings)) {
307 showSiblings(wrapDoc, ctx);
308 return; // actual result is returned on ctx.addOutputPart();
311 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
312 if (Tools.isTrue(showRelations)) {
313 showRelations(wrapDoc, ctx);
314 return; // actual result is returned on ctx.addOutputPart();
317 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
318 if (Tools.isTrue(showAllRelations)) {
319 showAllRelations(wrapDoc, ctx);
320 return; // actual result is returned on ctx.addOutputPart();
324 addAccountPermissionsPart();
327 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
329 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
332 private void addAccountPermissionsPart() throws Exception {
333 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
336 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
337 String currentServiceName = ctx.getServiceName();
338 String workflowSubResource = "/";
339 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
340 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
341 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
342 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
344 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
346 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
347 currentServiceName, workflowSubResource);
348 org.collectionspace.services.authorization.ObjectFactory objectFactory =
349 new org.collectionspace.services.authorization.ObjectFactory();
350 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
351 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
352 ctx.addOutputPart(accountPermissionPart);
358 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
361 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
363 //TODO filling extension parts should be dynamic
364 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
365 //not an ideal way of populating objects.
366 DocumentModel docModel = wrapDoc.getWrappedObject();
367 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
368 PoxPayloadIn input = ctx.getInput();
369 if (input.getParts().isEmpty()) {
370 String msg = "No payload found!";
371 logger.error(msg + "Ctx=" + getServiceContext().toString());
372 throw new BadRequestException(msg);
375 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
377 //iterate over parts received and fill those parts
378 List<PayloadInputPart> inputParts = input.getParts();
379 for (PayloadInputPart part : inputParts) {
381 String partLabel = part.getLabel();
382 if (partLabel == null) {
383 String msg = "Part label is missing or empty!";
384 logger.error(msg + "Ctx=" + getServiceContext().toString());
385 throw new BadRequestException(msg);
388 //skip if the part is not in metadata
389 ObjectPartType partMeta = partsMetaMap.get(partLabel);
390 if (partMeta == null) {
393 fillPart(part, docModel, partMeta, action, ctx);
399 * fillPart fills an XML part into given document model
400 * @param part to fill
401 * @param docModel for the given object
402 * @param partMeta metadata for the object to fill
405 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
406 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
408 //check if this is an xml part
409 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
410 Element element = part.getElementBody();
411 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
412 if (action == Action.UPDATE) {
413 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
415 docModel.setProperties(partMeta.getLabel(), objectProps);
420 * Filters out read only properties, so they cannot be set on update.
421 * TODO: add configuration support to do this generally
422 * @param objectProps the properties parsed from the update payload
423 * @param partMeta metadata for the object to fill
425 public void filterReadOnlyPropertiesForPart(
426 Map<String, Object> objectProps, ObjectPartType partMeta) {
427 // Should add in logic to filter most of the core items on update
428 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
429 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
430 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
431 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
432 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
433 // Note that the updatedAt/updatedBy fields are set internally
434 // in DocumentModelHandler.handleCoreValues().
439 * extractPart extracts an XML object from given DocumentModel
441 * @param schema of the object to extract
442 * @param partMeta metadata for the object to extract
445 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
447 return extractPart(docModel, schema, (Map<String, Object>)null);
451 * extractPart extracts an XML object from given DocumentModel
453 * @param schema of the object to extract
454 * @param partMeta metadata for the object to extract
458 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
460 return extractPart(docModel, schema, partMeta, null);
464 * extractPart extracts an XML object from given DocumentModel
466 * @param schema of the object to extract
467 * @param partMeta metadata for the object to extract
470 protected Map<String, Object> extractPart(
471 DocumentModel docModel,
473 Map<String, Object> addToMap)
475 Map<String, Object> result = null;
477 Map<String, Object> objectProps = docModel.getProperties(schema);
478 if (objectProps != null) {
479 //unqualify properties before sending the doc over the wire (to save bandwidh)
480 //FIXME: is there a better way to avoid duplication of a Map/Collection?
481 Map<String, Object> unQObjectProperties =
482 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
483 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
484 for (Entry<String, Object> entry : qualifiedEntries) {
485 String unqProp = getUnQProperty(entry.getKey());
486 unQObjectProperties.put(unqProp, entry.getValue());
488 result = unQObjectProperties;
495 * extractPart extracts an XML object from given DocumentModel
497 * @param schema of the object to extract
498 * @param partMeta metadata for the object to extract
502 protected Map<String, Object> extractPart(
503 DocumentModel docModel, String schema, ObjectPartType partMeta,
504 Map<String, Object> addToMap)
506 Map<String, Object> result = null;
508 result = this.extractPart(docModel, schema, addToMap);
514 public String getStringPropertyFromDoc(
517 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
518 RepositoryInstance repoSession = null;
519 boolean releaseRepoSession = false;
520 String returnValue = null;
523 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
524 repoSession = this.getRepositorySession();
525 if (repoSession == null) {
526 repoSession = repoClient.getRepositorySession();
527 releaseRepoSession = true;
531 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
532 DocumentModel docModel = wrapper.getWrappedObject();
533 returnValue = (String) docModel.getPropertyValue(propertyXPath);
534 } catch (PropertyException pe) {
536 } catch (DocumentException de) {
538 } catch (Exception e) {
539 if (logger.isDebugEnabled()) {
540 logger.debug("Caught exception ", e);
542 throw new DocumentException(e);
544 if (releaseRepoSession && repoSession != null) {
545 repoClient.releaseRepositorySession(repoSession);
548 } catch (Exception e) {
549 if (logger.isDebugEnabled()) {
550 logger.debug("Caught exception ", e);
552 throw new DocumentException(e);
556 if (logger.isWarnEnabled() == true) {
557 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
566 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
569 public AuthorityRefList getAuthorityRefs(
571 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
573 AuthorityRefList authRefList = new AuthorityRefList();
574 AbstractCommonList commonList = (AbstractCommonList) authRefList;
576 DocumentFilter docFilter = this.getDocumentFilter();
577 long pageSize = docFilter.getPageSize();
578 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
579 // set the page size and page number
580 commonList.setPageNum(pageNum);
581 commonList.setPageSize(pageSize);
583 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
586 int iFirstToUse = (int)(pageSize*pageNum);
587 int nFoundInPage = 0;
590 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
591 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
593 boolean releaseRepoSession = false;
594 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
595 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
596 RepositoryInstance repoSession = this.getRepositorySession();
597 if (repoSession == null) {
598 repoSession = repoClient.getRepositorySession(ctx);
599 releaseRepoSession = true;
603 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
604 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
605 // Slightly goofy pagination support - how many refs do we expect from one object?
606 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
607 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
608 if(appendToAuthRefsList(ari, list)) {
617 if (releaseRepoSession == true) {
618 repoClient.releaseRepositorySession(ctx, repoSession);
622 // Set num of items in list. this is useful to our testing framework.
623 commonList.setItemsInPage(nFoundInPage);
624 // set the total result size
625 commonList.setTotalItems(nFoundTotal);
627 } catch (PropertyException pe) {
628 String msg = "Attempted to retrieve value for invalid or missing authority field. "
629 + "Check authority field properties in tenant bindings.";
630 logger.warn(msg, pe);
632 } catch (Exception e) {
633 if (logger.isDebugEnabled()) {
634 logger.debug("Caught exception in getAuthorityRefs", e);
636 Response response = Response.status(
637 Response.Status.INTERNAL_SERVER_ERROR).entity(
638 "Failed to retrieve authority references").type(
639 "text/plain").build();
640 throw new WebApplicationException(response);
646 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
647 List<AuthorityRefList.AuthorityRefItem> list)
649 String fieldName = ari.getQualifiedDisplayName();
651 String refNameValue = (String)ari.getProperty().getValue();
652 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
653 if(item!=null) { // ignore garbage values.
657 } catch(PropertyException pe) {
658 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
663 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
665 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
667 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
668 ilistItem.setRefName(refName);
669 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
670 ilistItem.setItemDisplayName(termInfo.displayName);
671 ilistItem.setSourceField(authRefFieldName);
672 ilistItem.setUri(termInfo.getRelativeUri());
673 } catch (Exception e) {
674 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
681 * Returns the primary value from a list of values.
683 * Assumes that the first value is the primary value.
684 * This assumption may change when and if the primary value
685 * is identified explicitly.
687 * @param values a list of values.
688 * @param propertyName the name of a property through
689 * which the value can be extracted.
690 * @return the primary value.
691 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
692 String primaryValue = "";
693 if (values == null || values.size() == 0) {
696 Object value = values.get(0);
697 if (value instanceof String) {
699 primaryValue = (String) value;
701 // Multivalue group of fields
702 } else if (value instanceof Map) {
704 Map map = (Map) value;
705 if (map.values().size() > 0) {
706 if (map.get(propertyName) != null) {
707 primaryValue = (String) map.get(propertyName);
712 logger.warn("Unexpected type for property " + propertyName
713 + " in multivalue list: not String or Map.");
720 * Gets a simple property from the document.
722 * For completeness, as this duplicates DocumentModel method.
724 * @param docModel The document model to get info from
725 * @param schema The name of the schema (part)
726 * @param propertyName The simple scalar property type
727 * @return property value as String
729 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
730 String xpath = "/"+schema+":"+propName;
732 return (String)docModel.getPropertyValue(xpath);
733 } catch(PropertyException pe) {
734 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
735 +pe.getLocalizedMessage());
736 } catch(ClassCastException cce) {
737 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
738 +cce.getLocalizedMessage());
739 } catch(Exception e) {
740 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
741 +e.getLocalizedMessage());
746 * Gets first of a repeating list of scalar values, as a String, from the document.
748 * @param docModel The document model to get info from
749 * @param schema The name of the schema (part)
750 * @param listName The name of the scalar list property
751 * @return first value in list, as a String, or empty string if the list is empty
753 protected String getFirstRepeatingStringProperty(
754 DocumentModel docModel, String schema, String listName) {
755 String xpath = "/"+schema+":"+listName+"/[0]";
757 return (String)docModel.getPropertyValue(xpath);
758 } catch(PropertyException pe) {
759 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
760 +pe.getLocalizedMessage());
761 } catch(IndexOutOfBoundsException ioobe) {
762 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
763 return ""; // gracefully handle missing elements
764 } catch(ClassCastException cce) {
765 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
766 +cce.getLocalizedMessage());
767 } catch(Exception e) {
768 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
769 +e.getLocalizedMessage());
775 * Gets first of a repeating list of scalar values, as a String, from the document.
777 * @param docModel The document model to get info from
778 * @param schema The name of the schema (part)
779 * @param listName The name of the scalar list property
780 * @return first value in list, as a String, or empty string if the list is empty
782 protected String getStringValueInPrimaryRepeatingComplexProperty(
783 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
784 String result = null;
786 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
788 result = (String)docModel.getPropertyValue(xpath);
789 } catch(PropertyException pe) {
790 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
791 +pe.getLocalizedMessage());
792 } catch(IndexOutOfBoundsException ioobe) {
793 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
794 result = ""; // gracefully handle missing elements
795 } catch(ClassCastException cce) {
796 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
797 +cce.getLocalizedMessage());
798 } catch(Exception e) {
799 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
800 +e.getLocalizedMessage());
807 * Gets XPath value from schema. Note that only "/" and "[n]" are
808 * supported for xpath. Can omit grouping elements for repeating complex types,
809 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
810 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
811 * If there are no entries for a list of scalars or for a list of complex types,
812 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
813 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
814 * that many elements in the list.
815 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
817 * @param docModel The document model to get info from
818 * @param schema The name of the schema (part)
819 * @param xpath The XPath expression (without schema prefix)
820 * @return value the indicated property value as a String
822 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
823 String schema, ListResultField field) {
824 Object result = null;
826 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
831 protected String getStringValue(DocumentModel docModel,
832 String schema, ListResultField field) {
833 String result = null;
835 Object value = getListResultValue(docModel, schema, field);
836 if (value != null && value instanceof String) {
837 String strValue = (String) value;
838 if (strValue.trim().isEmpty() == false) {
846 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
850 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
852 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
854 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
856 sb.append(item.getPredicate());
858 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
862 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
863 StringBuilder sb = new StringBuilder();
865 if (list.size() > 0) {
866 sb.append("=========== " + label + " ==========" + CR);
868 for (RelationsCommonList.RelationListItem item : list) {
869 itemToString(sb, "== ", item);
872 return sb.toString();
875 /** @return null on parent not found
877 protected String getParentCSID(String thisCSID) throws Exception {
878 String parentCSID = null;
880 String predicate = RelationshipType.HAS_BROADER.value();
881 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
882 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
883 if (parentList != null) {
884 if (parentList.size() == 0) {
887 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
888 parentCSID = relationListItem.getObjectCsid();
891 } catch (Exception e) {
892 logger.error("Could not find parent for this: " + thisCSID, e);
897 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
898 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
902 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
903 MultipartServiceContext ctx) throws Exception {
904 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
905 String parentCSID = getParentCSID(thisCSID);
906 if (parentCSID == null) {
907 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
911 String predicate = RelationshipType.HAS_BROADER.value();
912 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
913 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
915 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
918 RelationsCommonList.RelationListItem item = null;
919 for (RelationsCommonList.RelationListItem sibling : siblingList) {
920 if (thisCSID.equals(sibling.getSubjectCsid())) {
921 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.
924 //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.
925 for (RelationsCommonList.RelationListItem self : toRemoveList) {
926 removeFromList(siblingList, self);
929 long siblingSize = siblingList.size();
930 siblingListOuter.setTotalItems(siblingSize);
931 siblingListOuter.setItemsInPage(siblingSize);
932 if(logger.isTraceEnabled()) {
933 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
934 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
937 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
938 ctx.addOutputPart(relationsPart);
941 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
942 MultipartServiceContext ctx) throws Exception {
943 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
945 String predicate = RelationshipType.HAS_BROADER.value();
946 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
947 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
949 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
950 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
952 if(logger.isTraceEnabled()) {
953 String dump = dumpLists(thisCSID, parentList, childrenList, null);
954 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
957 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
958 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
959 //Not optimal, but that's the current design spec.
961 for (RelationsCommonList.RelationListItem parent : parentList) {
962 childrenList.add(parent);
965 long childrenSize = childrenList.size();
966 childrenListOuter.setTotalItems(childrenSize);
967 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
969 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
970 ctx.addOutputPart(relationsPart);
973 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
974 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
976 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
977 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
979 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
980 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
982 if(logger.isTraceEnabled()) {
983 String dump = dumpLists(thisCSID, subjectList, objectList, null);
984 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
987 subjectList.addAll(objectList);
989 //now subjectList actually has records BOTH where thisCSID is subject and object.
990 long relatedSize = subjectList.size();
991 subjectListOuter.setTotalItems(relatedSize);
992 subjectListOuter.setItemsInPage(relatedSize);
994 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
995 ctx.addOutputPart(relationsPart);
998 private String dumpLists(String itemCSID,
999 List<RelationsCommonList.RelationListItem> parentList,
1000 List<RelationsCommonList.RelationListItem> childList,
1001 List<RelationsCommonList.RelationListItem> actionList) {
1002 StringBuilder sb = new StringBuilder();
1003 sb.append("itemCSID: " + itemCSID + CR);
1004 if(parentList!=null) {
1005 sb.append(dumpList(parentList, "parentList"));
1007 if(childList!=null) {
1008 sb.append(dumpList(childList, "childList"));
1010 if(actionList!=null) {
1011 sb.append(dumpList(actionList, "actionList"));
1013 return sb.toString();
1016 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1017 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1018 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1019 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1020 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1021 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1022 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1024 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1025 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1026 return relationsCommonList;
1028 //============================= END TODO refactor ==========================
1030 // this method calls the RelationResource to have it create the relations and persist them.
1031 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1032 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1033 for (RelationsCommonList.RelationListItem item : inboundList) {
1034 RelationsCommon rc = new RelationsCommon();
1035 //rc.setCsid(item.getCsid());
1036 //todo: assignTo(item, rc);
1037 RelationsDocListItem itemSubject = item.getSubject();
1038 RelationsDocListItem itemObject = item.getObject();
1040 // Set at least one of CSID and refName for Subject and Object
1041 // Either value might be null for for each of Subject and Object
1042 String subjectCsid = itemSubject.getCsid();
1043 rc.setSubjectCsid(subjectCsid);
1045 String objCsid = itemObject.getCsid();
1046 rc.setObjectCsid(objCsid);
1048 rc.setSubjectRefName(itemSubject.getRefName());
1049 rc.setObjectRefName(itemObject.getRefName());
1051 rc.setRelationshipType(item.getPredicate());
1052 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1053 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1054 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1056 // This is superfluous, since it will be fetched by the Relations Create logic.
1057 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1058 rc.setObjectDocumentType(itemObject.getDocumentType());
1060 // This is superfluous, since it will be fetched by the Relations Create logic.
1061 rc.setSubjectUri(itemSubject.getUri());
1062 rc.setObjectUri(itemObject.getUri());
1063 // May not have the info here. Only really require CSID or refName.
1064 // Rest is handled in the Relation create mechanism
1065 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1067 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1068 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1069 payloadOut.addPart(outputPart);
1070 RelationResource relationResource = new RelationResource();
1071 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1072 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1076 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1077 // But item1 must not be sparse
1078 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1079 if (item1 == null || item2 == null) {
1082 RelationsDocListItem subj1 = item1.getSubject();
1083 RelationsDocListItem subj2 = item2.getSubject();
1084 RelationsDocListItem obj1 = item1.getObject();
1085 RelationsDocListItem obj2 = item2.getObject();
1087 String subj1Csid = subj1.getCsid();
1088 String subj2Csid = subj2.getCsid();
1089 String subj1RefName = subj1.getRefName();
1090 String subj2RefName = subj2.getRefName();
1092 String obj1Csid = obj1.getCsid();
1093 String obj2Csid = obj2.getCsid();
1094 String obj1RefName = obj1.getRefName();
1095 String obj2RefName = obj2.getRefName();
1097 String item1Metatype = item1.getRelationshipMetaType();
1098 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1100 String item2Metatype = item2.getRelationshipMetaType();
1101 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1103 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1104 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1105 // predicate is proper, but still allow relationshipType
1106 && (item1.getPredicate().equals(item2.getPredicate())
1107 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1108 // Allow missing docTypes, so long as they do not conflict
1109 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1110 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1111 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1115 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1116 // But the list items must not be sparse
1117 private RelationsCommonList.RelationListItem findInList(
1118 List<RelationsCommonList.RelationListItem> list,
1119 RelationsCommonList.RelationListItem item) {
1120 RelationsCommonList.RelationListItem foundItem = null;
1121 for (RelationsCommonList.RelationListItem listItem : list) {
1122 if (itemsEqual(listItem, item)) { //equals must be defined, else
1123 foundItem = listItem;
1130 /** updateRelations strategy:
1133 go through inboundList, remove anything from childList that matches from childList
1134 go through inboundList, remove anything from parentList that matches from parentList
1135 go through parentList, delete all remaining
1136 go through childList, delete all remaining
1137 go through actionList, add all remaining.
1138 check for duplicate children
1139 check for more than one parent.
1141 inboundList parentList childList actionList
1142 ---------------- --------------- ---------------- ----------------
1143 child-a parent-c child-a child-b
1144 child-b parent-d child-c
1149 private RelationsCommonList updateRelations(
1150 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1152 if (logger.isTraceEnabled()) {
1153 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1155 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1157 return null; //nothing to do--they didn't send a list of relations.
1159 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1160 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1161 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1162 List<RelationsCommonList.RelationListItem> childList = null;
1163 List<RelationsCommonList.RelationListItem> parentList = null;
1164 DocumentModel docModel = wrapDoc.getWrappedObject();
1165 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1166 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1168 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1169 //Do magic replacement of ${itemCSID} and fix URI's.
1170 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1172 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1173 UriInfo uriInfo = ctx.getUriInfo();
1174 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1177 //Run getList() once as sent to get childListOuter:
1178 String predicate = RelationshipType.HAS_BROADER.value();
1179 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1180 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1181 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1182 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1183 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1185 RelationResource relationResource = new RelationResource();
1186 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1188 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1189 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1190 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1191 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1192 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1195 childList = childListOuter.getRelationListItem();
1196 parentList = parentListOuter.getRelationListItem();
1198 if (parentList.size() > 1) {
1199 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1202 if (logger.isTraceEnabled()) {
1203 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1207 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1208 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1209 // and so the CSID for those may be null
1210 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1211 // Look for parents and children
1212 if(itemCSID.equals(inboundItem.getObject().getCsid())
1213 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1214 //then this is an item that says we have a child. That child is inboundItem
1215 RelationsCommonList.RelationListItem childItem =
1216 (childList == null) ? null : findInList(childList, inboundItem);
1217 if (childItem != null) {
1218 if (logger.isTraceEnabled()) {
1219 StringBuilder sb = new StringBuilder();
1220 itemToString(sb, "== Child: ", childItem);
1221 logger.trace("Found inboundChild in current child list: " + sb.toString());
1223 removeFromList(childList, childItem); //exists, just take it off delete list
1225 if (logger.isTraceEnabled()) {
1226 StringBuilder sb = new StringBuilder();
1227 itemToString(sb, "== Child: ", inboundItem);
1228 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1230 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1231 String newChildCsid = inboundItem.getSubject().getCsid();
1232 if(newChildCsid == null) {
1233 String newChildRefName = inboundItem.getSubject().getRefName();
1234 if(newChildRefName==null) {
1235 throw new RuntimeException("Child with no CSID or refName!");
1237 if (logger.isTraceEnabled()) {
1238 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1240 DocumentModel newChildDocModel =
1241 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1242 newChildRefName, getServiceContext().getResourceMap());
1243 newChildCsid = getCsid(newChildDocModel);
1245 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1248 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1249 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1250 //then this is an item that says we have a parent. inboundItem is that parent.
1251 RelationsCommonList.RelationListItem parentItem =
1252 (parentList == null) ? null : findInList(parentList, inboundItem);
1253 if (parentItem != null) {
1254 removeFromList(parentList, parentItem); //exists, just take it off delete list
1256 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1259 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1262 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1265 if (logger.isTraceEnabled()) {
1266 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1267 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1270 if (logger.isTraceEnabled()) {
1271 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1272 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1274 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1275 deleteRelations(childList, ctx, "childList");
1277 if (logger.isTraceEnabled()) {
1278 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1279 + actionList.size() + " new parents and children.");
1281 createRelations(actionList, ctx);
1282 if (logger.isTraceEnabled()) {
1283 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1285 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1286 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1287 return relationsCommonListBody;
1290 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1291 * and sets URI correctly for related items.
1292 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1294 protected void fixupInboundListItems(ServiceContext ctx,
1295 List<RelationsCommonList.RelationListItem> inboundList,
1296 DocumentModel docModel,
1297 String itemCSID) throws Exception {
1298 String thisURI = this.getUri(docModel);
1299 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1300 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1301 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1302 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1303 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1305 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1306 inboundItem.setObjectCsid(itemCSID);
1307 inboundItemObject.setCsid(itemCSID);
1308 //inboundItemObject.setUri(getUri(docModel));
1311 String objectCsid = inboundItemObject.getCsid();
1312 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1313 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1314 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1315 inboundItemObject.setUri(uri); //CSPACE-4037
1318 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1320 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1321 inboundItem.setSubjectCsid(itemCSID);
1322 inboundItemSubject.setCsid(itemCSID);
1323 //inboundItemSubject.setUri(getUri(docModel));
1326 String subjectCsid = inboundItemSubject.getCsid();
1327 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1328 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1329 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1330 inboundItemSubject.setUri(uri); //CSPACE-4037
1333 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1338 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1339 MultivaluedMap<String, String> queryParams, String childCSID) {
1340 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1341 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1342 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1343 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1345 RelationResource relationResource = new RelationResource();
1346 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1347 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1348 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1349 deleteRelations(parentList, ctx, "parentList-delete");
1352 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1353 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1356 for (RelationsCommonList.RelationListItem item : list) {
1357 RelationResource relationResource = new RelationResource();
1358 if(logger.isTraceEnabled()) {
1359 StringBuilder sb = new StringBuilder();
1360 itemToString(sb, "==== TO DELETE: ", item);
1361 logger.trace(sb.toString());
1363 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1364 if (logger.isDebugEnabled()) {
1365 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1368 } catch (Throwable t) {
1369 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1374 // Note that we must do this after we have completed the Update, so that the repository has the
1375 // info for the item itself. The relations code must call into the repo to get info for each end.
1376 // This could be optimized to pass in the parent docModel, since it will often be one end.
1377 // Nevertheless, we should complete the item save before we do work on the relations, especially
1378 // since a save on Create might fail, and we would not want to create relations for something
1379 // that may not be created...
1380 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1381 ServiceContext ctx = getServiceContext();
1382 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1383 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1384 String itemCsid = documentModel.getName();
1386 //Updates relations part
1387 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1389 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
1390 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1392 //now we add part for relations list
1393 //ServiceContext ctx = getServiceContext();
1394 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1395 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1399 * Checks to see if the refName has changed, and if so,
1400 * uses utilities to find all references and update them to use the new refName.
1403 protected void handleRefNameReferencesUpdate() throws Exception {
1404 if (hasRefNameUpdate() == true) {
1405 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1406 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1407 RepositoryInstance repoSession = this.getRepositorySession();
1409 // Update all the relationship records that referred to the old refName
1410 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1411 oldRefNameOnUpdate, newRefNameOnUpdate);
1415 protected String getRefNameUpdate() {
1416 String result = null;
1418 if (hasRefNameUpdate() == true) {
1419 result = newRefNameOnUpdate;
1420 if (logger.isDebugEnabled() == true) {
1421 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1422 newRefNameOnUpdate, oldRefNameOnUpdate));
1430 * Note: The Vocabulary document handler overrides this method.
1432 protected String getRefPropName() {
1433 return ServiceBindingUtils.AUTH_REF_PROP;