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.authentication.spi.AuthNContext;
41 import org.collectionspace.services.authorization.AccountPermission;
42 import org.collectionspace.services.jaxb.AbstractCommonList;
43 import org.collectionspace.services.lifecycle.TransitionDef;
44 import org.collectionspace.services.client.CollectionSpaceClient;
45 import org.collectionspace.services.client.PayloadInputPart;
46 import org.collectionspace.services.client.PayloadOutputPart;
47 import org.collectionspace.services.client.PoxPayloadIn;
48 import org.collectionspace.services.client.PoxPayloadOut;
49 import org.collectionspace.services.client.Profiler;
50 import org.collectionspace.services.client.RelationClient;
51 import org.collectionspace.services.client.workflow.WorkflowClient;
52 import org.collectionspace.services.common.CSWebApplicationException;
53 import org.collectionspace.services.common.ResourceBase;
54 import org.collectionspace.services.common.authorityref.AuthorityRefList;
55 import org.collectionspace.services.common.config.ServiceConfigUtils;
56 import org.collectionspace.services.common.context.JaxRsContext;
57 import org.collectionspace.services.common.context.MultipartServiceContext;
58 import org.collectionspace.services.common.context.ServiceBindingUtils;
59 import org.collectionspace.services.common.context.ServiceContext;
60 import org.collectionspace.services.common.document.BadRequestException;
61 import org.collectionspace.services.common.document.DocumentException;
62 import org.collectionspace.services.common.document.DocumentUtils;
63 import org.collectionspace.services.common.document.DocumentWrapper;
64 import org.collectionspace.services.common.document.DocumentFilter;
65 import org.collectionspace.services.client.IRelationsManager;
66 import org.collectionspace.services.common.relation.RelationResource;
67 import org.collectionspace.services.common.repository.RepositoryClient;
68 import org.collectionspace.services.common.security.SecurityUtils;
69 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
70 import org.collectionspace.services.common.api.CommonAPI;
71 import org.collectionspace.services.common.api.RefNameUtils;
72 import org.collectionspace.services.common.api.Tools;
73 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
74 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
75 import org.collectionspace.services.config.service.DocHandlerParams;
76 import org.collectionspace.services.config.service.ListResultField;
77 import org.collectionspace.services.config.service.ObjectPartType;
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;
84 import org.nuxeo.ecm.core.api.DocumentModel;
85 import org.nuxeo.ecm.core.api.DocumentModelList;
86 import org.nuxeo.ecm.core.api.model.PropertyException;
87 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
88 import org.slf4j.Logger;
89 import org.slf4j.LoggerFactory;
92 * RemoteDocumentModelHandler
94 * $LastChangedRevision: $
99 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
100 extends DocumentModelHandler<T, TL> {
103 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
104 private final static String CR = "\r\n";
105 private final static String EMPTYSTR = "";
108 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
111 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
112 if (ctx instanceof MultipartServiceContext) {
113 super.setServiceContext(ctx);
115 throw new IllegalArgumentException("setServiceContext requires instance of "
116 + MultipartServiceContext.class.getName());
121 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
122 return getRefnameDisplayName(docWrapper.getWrappedObject());
125 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
126 String result = null;
127 ServiceContext ctx = this.getServiceContext();
129 DocHandlerParams.Params params = null;
131 params = ServiceConfigUtils.getDocHandlerParams(ctx);
132 ListResultField field = params.getRefnameDisplayNameField();
134 String schema = field.getSchema();
135 if (schema == null || schema.trim().isEmpty()) {
136 schema = ctx.getCommonPartLabel();
139 result = getStringValue(docModel, schema, field);
140 } catch (Exception e) {
141 if (logger.isWarnEnabled()) {
142 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
150 public boolean supportsHierarchy() {
151 boolean result = false;
153 DocHandlerParams.Params params = null;
155 ServiceContext ctx = this.getServiceContext();
156 params = ServiceConfigUtils.getDocHandlerParams(ctx);
157 Boolean bool = params.isSupportsHierarchy();
159 result = bool.booleanValue();
161 } catch (DocumentException e) {
162 // TODO Auto-generated catch block
163 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
164 if (logger.isWarnEnabled() == true) {
173 public boolean supportsVersioning() {
174 boolean result = false;
176 DocHandlerParams.Params params = null;
178 ServiceContext ctx = this.getServiceContext();
179 params = ServiceConfigUtils.getDocHandlerParams(ctx);
180 Boolean bool = params.isSupportsVersioning();
182 result = bool.booleanValue();
184 } catch (DocumentException e) {
185 // TODO Auto-generated catch block
186 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
187 if (logger.isWarnEnabled() == true) {
197 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
199 // Do nothing by default, but children can override if they want. The real workflow transition happens in the WorkflowDocumemtModelHandler class
203 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
204 super.completeCreate(wrapDoc);
205 if (supportsHierarchy() == true) {
206 handleRelationsPayload(wrapDoc, false);
210 /* NOTE: The authority item doc handler overrides (after calling) this method. It performs refName updates. In this
211 * method we just update any and all relationship records that use refNames that have changed.
213 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
216 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
217 DocumentModel docModel = wrapDoc.getWrappedObject();
218 // We need to return at least those document part(s) and corresponding payloads that were received
219 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
220 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
221 PoxPayloadIn input = ctx.getInput();
223 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
224 for (PayloadInputPart part : inputParts) {
225 String partLabel = part.getLabel();
227 ObjectPartType partMeta = partsMetaMap.get(partLabel);
228 // CSPACE-4030 - generates NPE if the part is missing.
230 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
231 if(unQObjectProperties!=null) {
232 addOutputPart(unQObjectProperties, partLabel, partMeta);
235 } catch (Throwable t){
236 logger.error("Unable to addOutputPart: " + partLabel
237 + " in serviceContextPath: "+this.getServiceContextPath()
238 + " with URI: " + this.getServiceContext().getUriInfo().getPath()
243 if (logger.isWarnEnabled() == true) {
244 logger.warn("MultipartInput part was null for document id = " +
249 // If the resource's service supports hierarchy then we need to perform a little more work
251 if (supportsHierarchy() == true) {
252 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
253 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
258 * Adds the output part.
260 * @param unQObjectProperties the un q object properties
261 * @param schema the schema
262 * @param partMeta the part meta
263 * @throws Exception the exception
264 * MediaType.APPLICATION_XML_TYPE
266 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
268 Element doc = DocumentUtils.buildDocument(partMeta, schema,
269 unQObjectProperties);
270 if (logger.isTraceEnabled() == true) {
271 logger.trace(doc.asXML());
273 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
274 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
278 * Extract paging info.
280 * @param commonsList the commons list
282 * @throws Exception the exception
284 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
286 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
288 DocumentFilter docFilter = this.getDocumentFilter();
289 long pageSize = docFilter.getPageSize();
290 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
291 // set the page size and page number
292 commonList.setPageNum(pageNum);
293 commonList.setPageSize(pageSize);
294 DocumentModelList docList = wrapDoc.getWrappedObject();
295 // Set num of items in list. this is useful to our testing framework.
296 commonList.setItemsInPage(docList.size());
297 // set the total result size
298 commonList.setTotalItems(docList.totalSize());
300 return (TL) commonList;
304 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
307 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
310 DocumentModel docModel = wrapDoc.getWrappedObject();
311 String[] schemas = docModel.getDeclaredSchemas();
312 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
313 for (String schema : schemas) {
314 ObjectPartType partMeta = partsMetaMap.get(schema);
315 if (partMeta == null) {
316 continue; // unknown part, ignore
318 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
319 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
320 addExtraCoreValues(docModel, unQObjectProperties);
322 addOutputPart(unQObjectProperties, schema, partMeta);
325 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
327 if (supportsHierarchy() == true) {
328 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
329 if (Tools.isTrue(showSiblings)) {
330 showSiblings(wrapDoc, ctx);
331 return; // actual result is returned on ctx.addOutputPart();
334 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
335 if (Tools.isTrue(showRelations)) {
336 showRelations(wrapDoc, ctx);
337 return; // actual result is returned on ctx.addOutputPart();
340 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
341 if (Tools.isTrue(showAllRelations)) {
342 showAllRelations(wrapDoc, ctx);
343 return; // actual result is returned on ctx.addOutputPart();
347 String currentUser = ctx.getUserId();
348 if (currentUser.equalsIgnoreCase(AuthNContext.ANONYMOUS_USER) == false) {
349 addAccountPermissionsPart();
353 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
355 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
358 private void addAccountPermissionsPart() throws Exception {
359 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
362 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
363 String currentServiceName = ctx.getServiceName();
364 String workflowSubResource = "/";
365 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
366 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
367 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
368 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
370 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
372 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
373 currentServiceName, workflowSubResource);
374 org.collectionspace.services.authorization.ObjectFactory objectFactory =
375 new org.collectionspace.services.authorization.ObjectFactory();
376 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
377 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
378 ctx.addOutputPart(accountPermissionPart);
384 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
387 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
389 //TODO filling extension parts should be dynamic
390 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
391 //not an ideal way of populating objects.
392 DocumentModel docModel = wrapDoc.getWrappedObject();
393 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
394 PoxPayloadIn input = ctx.getInput();
395 if (input.getParts().isEmpty()) {
396 String msg = "No payload found!";
397 logger.error(msg + "Ctx=" + getServiceContext().toString());
398 throw new BadRequestException(msg);
401 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
403 //iterate over parts received and fill those parts
404 List<PayloadInputPart> inputParts = input.getParts();
405 for (PayloadInputPart part : inputParts) {
407 String partLabel = part.getLabel();
408 if (partLabel == null) {
409 String msg = "Part label is missing or empty!";
410 logger.error(msg + "Ctx=" + getServiceContext().toString());
411 throw new BadRequestException(msg);
414 //skip if the part is not in metadata
415 ObjectPartType partMeta = partsMetaMap.get(partLabel);
416 if (partMeta == null) {
419 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 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)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 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
622 RepositoryInstance 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)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(
780 DocumentModel docModel, String schema, String listName) {
781 String xpath = "/"+schema+":"+listName+"/[0]";
783 return (String)docModel.getPropertyValue(xpath);
784 } catch(PropertyException pe) {
785 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
786 +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+"} as String. Not a repeating String property?"
792 +cce.getLocalizedMessage());
793 } catch(Exception e) {
794 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
795 +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)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(Exception e) {
825 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
826 +e.getLocalizedMessage());
833 * Gets XPath value from schema. Note that only "/" and "[n]" are
834 * supported for xpath. Can omit grouping elements for repeating complex types,
835 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
836 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
837 * If there are no entries for a list of scalars or for a list of complex types,
838 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
839 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
840 * that many elements in the list.
841 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
843 * @param docModel The document model to get info from
844 * @param schema The name of the schema (part)
845 * @param xpath The XPath expression (without schema prefix)
846 * @return value the indicated property value as a String
848 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
849 String schema, ListResultField field) {
850 Object result = null;
852 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
857 protected String getStringValue(DocumentModel docModel,
858 String schema, ListResultField field) {
859 String result = null;
861 Object value = getListResultValue(docModel, schema, field);
862 if (value != null && value instanceof String) {
863 String strValue = (String) value;
864 if (strValue.trim().isEmpty() == false) {
872 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
876 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
878 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
880 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
882 sb.append(item.getPredicate());
884 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
888 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
889 StringBuilder sb = new StringBuilder();
891 if (list.size() > 0) {
892 sb.append("=========== " + label + " ==========" + CR);
894 for (RelationsCommonList.RelationListItem item : list) {
895 itemToString(sb, "== ", item);
898 return sb.toString();
901 /** @return null on parent not found
903 protected String getParentCSID(String thisCSID) throws Exception {
904 String parentCSID = null;
906 String predicate = RelationshipType.HAS_BROADER.value();
907 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
908 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
909 if (parentList != null) {
910 if (parentList.size() == 0) {
913 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
914 parentCSID = relationListItem.getObjectCsid();
917 } catch (Exception e) {
918 logger.error("Could not find parent for this: " + thisCSID, e);
923 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
924 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
928 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
929 MultipartServiceContext ctx) throws Exception {
930 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
931 String parentCSID = getParentCSID(thisCSID);
932 if (parentCSID == null) {
933 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
937 String predicate = RelationshipType.HAS_BROADER.value();
938 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
939 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
941 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
944 RelationsCommonList.RelationListItem item = null;
945 for (RelationsCommonList.RelationListItem sibling : siblingList) {
946 if (thisCSID.equals(sibling.getSubjectCsid())) {
947 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.
950 //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.
951 for (RelationsCommonList.RelationListItem self : toRemoveList) {
952 removeFromList(siblingList, self);
955 long siblingSize = siblingList.size();
956 siblingListOuter.setTotalItems(siblingSize);
957 siblingListOuter.setItemsInPage(siblingSize);
958 if(logger.isTraceEnabled()) {
959 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
960 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
963 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
964 ctx.addOutputPart(relationsPart);
967 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
968 MultipartServiceContext ctx) throws Exception {
969 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
971 String predicate = RelationshipType.HAS_BROADER.value();
972 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
973 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
975 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
976 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
978 if(logger.isTraceEnabled()) {
979 String dump = dumpLists(thisCSID, parentList, childrenList, null);
980 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
983 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
984 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
985 //Not optimal, but that's the current design spec.
987 for (RelationsCommonList.RelationListItem parent : parentList) {
988 childrenList.add(parent);
991 long childrenSize = childrenList.size();
992 childrenListOuter.setTotalItems(childrenSize);
993 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
995 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
996 ctx.addOutputPart(relationsPart);
999 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1000 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1002 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1003 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1005 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1006 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1008 if(logger.isTraceEnabled()) {
1009 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1010 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1013 subjectList.addAll(objectList);
1015 //now subjectList actually has records BOTH where thisCSID is subject and object.
1016 long relatedSize = subjectList.size();
1017 subjectListOuter.setTotalItems(relatedSize);
1018 subjectListOuter.setItemsInPage(relatedSize);
1020 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1021 ctx.addOutputPart(relationsPart);
1024 private String dumpLists(String itemCSID,
1025 List<RelationsCommonList.RelationListItem> parentList,
1026 List<RelationsCommonList.RelationListItem> childList,
1027 List<RelationsCommonList.RelationListItem> actionList) {
1028 StringBuilder sb = new StringBuilder();
1029 sb.append("itemCSID: " + itemCSID + CR);
1030 if(parentList!=null) {
1031 sb.append(dumpList(parentList, "parentList"));
1033 if(childList!=null) {
1034 sb.append(dumpList(childList, "childList"));
1036 if(actionList!=null) {
1037 sb.append(dumpList(actionList, "actionList"));
1039 return sb.toString();
1042 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1043 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1044 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1045 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1046 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1047 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1048 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1050 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1051 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1052 return relationsCommonList;
1054 //============================= END TODO refactor ==========================
1056 // this method calls the RelationResource to have it create the relations and persist them.
1057 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1058 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1059 for (RelationsCommonList.RelationListItem item : inboundList) {
1060 RelationsCommon rc = new RelationsCommon();
1061 //rc.setCsid(item.getCsid());
1062 //todo: assignTo(item, rc);
1063 RelationsDocListItem itemSubject = item.getSubject();
1064 RelationsDocListItem itemObject = item.getObject();
1066 // Set at least one of CSID and refName for Subject and Object
1067 // Either value might be null for for each of Subject and Object
1068 String subjectCsid = itemSubject.getCsid();
1069 rc.setSubjectCsid(subjectCsid);
1071 String objCsid = itemObject.getCsid();
1072 rc.setObjectCsid(objCsid);
1074 rc.setSubjectRefName(itemSubject.getRefName());
1075 rc.setObjectRefName(itemObject.getRefName());
1077 rc.setRelationshipType(item.getPredicate());
1078 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1079 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1080 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1082 // This is superfluous, since it will be fetched by the Relations Create logic.
1083 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1084 rc.setObjectDocumentType(itemObject.getDocumentType());
1086 // This is superfluous, since it will be fetched by the Relations Create logic.
1087 rc.setSubjectUri(itemSubject.getUri());
1088 rc.setObjectUri(itemObject.getUri());
1089 // May not have the info here. Only really require CSID or refName.
1090 // Rest is handled in the Relation create mechanism
1091 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1093 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1094 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1095 payloadOut.addPart(outputPart);
1096 RelationResource relationResource = new RelationResource();
1097 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1098 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1102 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1103 // But item1 must not be sparse
1104 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1105 if (item1 == null || item2 == null) {
1108 RelationsDocListItem subj1 = item1.getSubject();
1109 RelationsDocListItem subj2 = item2.getSubject();
1110 RelationsDocListItem obj1 = item1.getObject();
1111 RelationsDocListItem obj2 = item2.getObject();
1113 String subj1Csid = subj1.getCsid();
1114 String subj2Csid = subj2.getCsid();
1115 String subj1RefName = subj1.getRefName();
1116 String subj2RefName = subj2.getRefName();
1118 String obj1Csid = obj1.getCsid();
1119 String obj2Csid = obj2.getCsid();
1120 String obj1RefName = obj1.getRefName();
1121 String obj2RefName = obj2.getRefName();
1123 String item1Metatype = item1.getRelationshipMetaType();
1124 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1126 String item2Metatype = item2.getRelationshipMetaType();
1127 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1129 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1130 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1131 // predicate is proper, but still allow relationshipType
1132 && (item1.getPredicate().equals(item2.getPredicate())
1133 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1134 // Allow missing docTypes, so long as they do not conflict
1135 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1136 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1137 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1141 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1142 // But the list items must not be sparse
1143 private RelationsCommonList.RelationListItem findInList(
1144 List<RelationsCommonList.RelationListItem> list,
1145 RelationsCommonList.RelationListItem item) {
1146 RelationsCommonList.RelationListItem foundItem = null;
1147 for (RelationsCommonList.RelationListItem listItem : list) {
1148 if (itemsEqual(listItem, item)) { //equals must be defined, else
1149 foundItem = listItem;
1156 /** updateRelations strategy:
1159 go through inboundList, remove anything from childList that matches from childList
1160 go through inboundList, remove anything from parentList that matches from parentList
1161 go through parentList, delete all remaining
1162 go through childList, delete all remaining
1163 go through actionList, add all remaining.
1164 check for duplicate children
1165 check for more than one parent.
1167 inboundList parentList childList actionList
1168 ---------------- --------------- ---------------- ----------------
1169 child-a parent-c child-a child-b
1170 child-b parent-d child-c
1175 private RelationsCommonList updateRelations(
1176 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1178 if (logger.isTraceEnabled()) {
1179 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1181 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1183 return null; //nothing to do--they didn't send a list of relations.
1185 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1186 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1187 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1188 List<RelationsCommonList.RelationListItem> childList = null;
1189 List<RelationsCommonList.RelationListItem> parentList = null;
1190 DocumentModel docModel = wrapDoc.getWrappedObject();
1191 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1192 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1194 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1195 //Do magic replacement of ${itemCSID} and fix URI's.
1196 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1198 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1199 UriInfo uriInfo = ctx.getUriInfo();
1200 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1203 //Run getList() once as sent to get childListOuter:
1204 String predicate = RelationshipType.HAS_BROADER.value();
1205 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1206 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1207 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1208 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1209 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1211 RelationResource relationResource = new RelationResource();
1212 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1214 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1215 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1216 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1217 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1218 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1221 childList = childListOuter.getRelationListItem();
1222 parentList = parentListOuter.getRelationListItem();
1224 if (parentList.size() > 1) {
1225 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1228 if (logger.isTraceEnabled()) {
1229 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1233 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1234 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1235 // and so the CSID for those may be null
1236 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1237 // Look for parents and children
1238 if(itemCSID.equals(inboundItem.getObject().getCsid())
1239 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1240 //then this is an item that says we have a child. That child is inboundItem
1241 RelationsCommonList.RelationListItem childItem =
1242 (childList == null) ? null : findInList(childList, inboundItem);
1243 if (childItem != null) {
1244 if (logger.isTraceEnabled()) {
1245 StringBuilder sb = new StringBuilder();
1246 itemToString(sb, "== Child: ", childItem);
1247 logger.trace("Found inboundChild in current child list: " + sb.toString());
1249 removeFromList(childList, childItem); //exists, just take it off delete list
1251 if (logger.isTraceEnabled()) {
1252 StringBuilder sb = new StringBuilder();
1253 itemToString(sb, "== Child: ", inboundItem);
1254 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1256 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1257 String newChildCsid = inboundItem.getSubject().getCsid();
1258 if(newChildCsid == null) {
1259 String newChildRefName = inboundItem.getSubject().getRefName();
1260 if(newChildRefName==null) {
1261 throw new RuntimeException("Child with no CSID or refName!");
1263 if (logger.isTraceEnabled()) {
1264 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1266 DocumentModel newChildDocModel =
1267 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1268 newChildRefName, getServiceContext().getResourceMap());
1269 newChildCsid = getCsid(newChildDocModel);
1271 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1274 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1275 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1276 //then this is an item that says we have a parent. inboundItem is that parent.
1277 RelationsCommonList.RelationListItem parentItem =
1278 (parentList == null) ? null : findInList(parentList, inboundItem);
1279 if (parentItem != null) {
1280 removeFromList(parentList, parentItem); //exists, just take it off delete list
1282 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1285 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1288 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1291 if (logger.isTraceEnabled()) {
1292 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1293 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1296 if (logger.isTraceEnabled()) {
1297 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1298 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1300 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1301 deleteRelations(childList, ctx, "childList");
1303 if (logger.isTraceEnabled()) {
1304 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1305 + actionList.size() + " new parents and children.");
1307 createRelations(actionList, ctx);
1308 if (logger.isTraceEnabled()) {
1309 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1311 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1312 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1313 return relationsCommonListBody;
1316 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1317 * and sets URI correctly for related items.
1318 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1320 protected void fixupInboundListItems(ServiceContext ctx,
1321 List<RelationsCommonList.RelationListItem> inboundList,
1322 DocumentModel docModel,
1323 String itemCSID) throws Exception {
1324 String thisURI = this.getUri(docModel);
1325 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1326 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1327 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1328 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1329 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1331 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1332 inboundItem.setObjectCsid(itemCSID);
1333 inboundItemObject.setCsid(itemCSID);
1334 //inboundItemObject.setUri(getUri(docModel));
1337 String objectCsid = inboundItemObject.getCsid();
1338 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1339 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1340 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1341 inboundItemObject.setUri(uri); //CSPACE-4037
1344 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1346 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1347 inboundItem.setSubjectCsid(itemCSID);
1348 inboundItemSubject.setCsid(itemCSID);
1349 //inboundItemSubject.setUri(getUri(docModel));
1352 String subjectCsid = inboundItemSubject.getCsid();
1353 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1354 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1355 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1356 inboundItemSubject.setUri(uri); //CSPACE-4037
1359 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1364 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1365 MultivaluedMap<String, String> queryParams, String childCSID) {
1366 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1367 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1368 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1369 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1371 RelationResource relationResource = new RelationResource();
1372 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1373 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1374 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1375 deleteRelations(parentList, ctx, "parentList-delete");
1378 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1379 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1382 for (RelationsCommonList.RelationListItem item : list) {
1383 RelationResource relationResource = new RelationResource();
1384 if(logger.isTraceEnabled()) {
1385 StringBuilder sb = new StringBuilder();
1386 itemToString(sb, "==== TO DELETE: ", item);
1387 logger.trace(sb.toString());
1389 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1390 if (logger.isDebugEnabled()) {
1391 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1394 } catch (Throwable t) {
1395 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1400 // Note that we must do this after we have completed the Update, so that the repository has the
1401 // info for the item itself. The relations code must call into the repo to get info for each end.
1402 // This could be optimized to pass in the parent docModel, since it will often be one end.
1403 // Nevertheless, we should complete the item save before we do work on the relations, especially
1404 // since a save on Create might fail, and we would not want to create relations for something
1405 // that may not be created...
1406 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1407 ServiceContext ctx = getServiceContext();
1408 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1409 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1410 String itemCsid = documentModel.getName();
1412 //Updates relations part
1413 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1415 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
1416 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1418 //now we add part for relations list
1419 //ServiceContext ctx = getServiceContext();
1420 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1421 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1425 * Checks to see if the refName has changed, and if so,
1426 * uses utilities to find all references and update them to use the new refName.
1429 protected void handleRefNameReferencesUpdate() throws Exception {
1430 if (hasRefNameUpdate() == true) {
1431 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1432 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1433 RepositoryInstance repoSession = this.getRepositorySession();
1435 // Update all the relationship records that referred to the old refName
1436 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1437 oldRefNameOnUpdate, newRefNameOnUpdate);
1441 protected String getRefNameUpdate() {
1442 String result = null;
1444 if (hasRefNameUpdate() == true) {
1445 result = newRefNameOnUpdate;
1446 if (logger.isDebugEnabled() == true) {
1447 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1448 newRefNameOnUpdate, oldRefNameOnUpdate));
1456 * Note: The Vocabulary document handler overrides this method.
1458 protected String getRefPropName() {
1459 return ServiceBindingUtils.AUTH_REF_PROP;