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 boolean supportsVersioning() {
176 boolean result = false;
178 DocHandlerParams.Params params = null;
180 ServiceContext ctx = this.getServiceContext();
181 params = ServiceConfigUtils.getDocHandlerParams(ctx);
182 Boolean bool = params.isSupportsVersioning();
184 result = bool.booleanValue();
186 } catch (DocumentException e) {
187 // TODO Auto-generated catch block
188 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
189 if (logger.isWarnEnabled() == true) {
199 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
201 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
205 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
206 super.completeCreate(wrapDoc);
207 if (supportsHierarchy() == true) {
208 handleRelationsPayload(wrapDoc, false);
212 /* NOTE: The authority item doc handler overrides (after calling) this method. It performs refName updates. In this
213 * method we just update any and all relationship records that use refNames that have changed.
215 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
218 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
219 DocumentModel docModel = wrapDoc.getWrappedObject();
220 // We need to return at least those document part(s) and corresponding payloads that were received
221 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
222 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
223 PoxPayloadIn input = ctx.getInput();
225 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
226 for (PayloadInputPart part : inputParts) {
227 String partLabel = part.getLabel();
229 ObjectPartType partMeta = partsMetaMap.get(partLabel);
230 // CSPACE-4030 - generates NPE if the part is missing.
232 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
233 if(unQObjectProperties!=null) {
234 addOutputPart(unQObjectProperties, partLabel, partMeta);
237 } catch (Throwable t){
238 logger.error("Unable to addOutputPart: " + partLabel
239 + " in serviceContextPath: "+this.getServiceContextPath()
240 + " with URI: " + this.getServiceContext().getUriInfo().getPath()
245 if (logger.isWarnEnabled() == true) {
246 logger.warn("MultipartInput part was null for document id = " +
251 // If the resource's service supports hierarchy then we need to perform a little more work
253 if (supportsHierarchy() == true) {
254 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
255 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
260 * Adds the output part.
262 * @param unQObjectProperties the un q object properties
263 * @param schema the schema
264 * @param partMeta the part meta
265 * @throws Exception the exception
266 * MediaType.APPLICATION_XML_TYPE
268 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
270 Element doc = DocumentUtils.buildDocument(partMeta, schema,
271 unQObjectProperties);
272 if (logger.isTraceEnabled() == true) {
273 logger.trace(doc.asXML());
275 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
276 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
280 * Extract paging info.
282 * @param commonsList the commons list
284 * @throws Exception the exception
286 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
288 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
290 DocumentFilter docFilter = this.getDocumentFilter();
291 long pageSize = docFilter.getPageSize();
292 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
293 // set the page size and page number
294 commonList.setPageNum(pageNum);
295 commonList.setPageSize(pageSize);
296 DocumentModelList docList = wrapDoc.getWrappedObject();
297 // Set num of items in list. this is useful to our testing framework.
298 commonList.setItemsInPage(docList.size());
299 // set the total result size
300 commonList.setTotalItems(docList.totalSize());
302 return (TL) commonList;
306 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
309 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
312 DocumentModel docModel = wrapDoc.getWrappedObject();
313 String[] schemas = docModel.getDeclaredSchemas();
314 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
315 for (String schema : schemas) {
316 ObjectPartType partMeta = partsMetaMap.get(schema);
317 if (partMeta == null) {
318 continue; // unknown part, ignore
320 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
321 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
322 addExtraCoreValues(docModel, unQObjectProperties);
324 addOutputPart(unQObjectProperties, schema, partMeta);
327 if (supportsHierarchy() == true) {
328 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
329 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
330 if (Tools.isTrue(showSiblings)) {
331 showSiblings(wrapDoc, ctx);
332 return; // actual result is returned on ctx.addOutputPart();
335 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
336 if (Tools.isTrue(showRelations)) {
337 showRelations(wrapDoc, ctx);
338 return; // actual result is returned on ctx.addOutputPart();
341 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
342 if (Tools.isTrue(showAllRelations)) {
343 showAllRelations(wrapDoc, ctx);
344 return; // actual result is returned on ctx.addOutputPart();
348 addAccountPermissionsPart();
351 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
353 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
356 private void addAccountPermissionsPart() throws Exception {
357 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
360 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
361 String currentServiceName = ctx.getServiceName();
362 String workflowSubResource = "/";
363 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
364 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
365 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
366 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
368 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
370 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
371 currentServiceName, workflowSubResource);
372 org.collectionspace.services.authorization.ObjectFactory objectFactory =
373 new org.collectionspace.services.authorization.ObjectFactory();
374 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
375 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
376 ctx.addOutputPart(accountPermissionPart);
382 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
385 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
387 //TODO filling extension parts should be dynamic
388 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
389 //not an ideal way of populating objects.
390 DocumentModel docModel = wrapDoc.getWrappedObject();
391 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
392 PoxPayloadIn input = ctx.getInput();
393 if (input.getParts().isEmpty()) {
394 String msg = "No payload found!";
395 logger.error(msg + "Ctx=" + getServiceContext().toString());
396 throw new BadRequestException(msg);
399 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
401 //iterate over parts received and fill those parts
402 List<PayloadInputPart> inputParts = input.getParts();
403 for (PayloadInputPart part : inputParts) {
405 String partLabel = part.getLabel();
406 if (partLabel == null) {
407 String msg = "Part label is missing or empty!";
408 logger.error(msg + "Ctx=" + getServiceContext().toString());
409 throw new BadRequestException(msg);
412 //skip if the part is not in metadata
413 ObjectPartType partMeta = partsMetaMap.get(partLabel);
414 if (partMeta == null) {
417 fillPart(part, docModel, partMeta, action, ctx);
423 * fillPart fills an XML part into given document model
424 * @param part to fill
425 * @param docModel for the given object
426 * @param partMeta metadata for the object to fill
429 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
430 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
432 //check if this is an xml part
433 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
434 Element element = part.getElementBody();
435 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
436 if (action == Action.UPDATE) {
437 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
439 docModel.setProperties(partMeta.getLabel(), objectProps);
444 * Filters out read only properties, so they cannot be set on update.
445 * TODO: add configuration support to do this generally
446 * @param objectProps the properties parsed from the update payload
447 * @param partMeta metadata for the object to fill
449 public void filterReadOnlyPropertiesForPart(
450 Map<String, Object> objectProps, ObjectPartType partMeta) {
451 // Should add in logic to filter most of the core items on update
452 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
453 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
454 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
455 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
456 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
457 // Note that the updatedAt/updatedBy fields are set internally
458 // in DocumentModelHandler.handleCoreValues().
463 * extractPart extracts an XML object from given DocumentModel
465 * @param schema of the object to extract
466 * @param partMeta metadata for the object to extract
469 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
471 return extractPart(docModel, schema, (Map<String, Object>)null);
475 * extractPart extracts an XML object from given DocumentModel
477 * @param schema of the object to extract
478 * @param partMeta metadata for the object to extract
482 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
484 return extractPart(docModel, schema, partMeta, null);
488 * extractPart extracts an XML object from given DocumentModel
490 * @param schema of the object to extract
491 * @param partMeta metadata for the object to extract
494 protected Map<String, Object> extractPart(
495 DocumentModel docModel,
497 Map<String, Object> addToMap)
499 Map<String, Object> result = null;
501 Map<String, Object> objectProps = docModel.getProperties(schema);
502 if (objectProps != null) {
503 //unqualify properties before sending the doc over the wire (to save bandwidh)
504 //FIXME: is there a better way to avoid duplication of a Map/Collection?
505 Map<String, Object> unQObjectProperties =
506 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
507 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
508 for (Entry<String, Object> entry : qualifiedEntries) {
509 String unqProp = getUnQProperty(entry.getKey());
510 unQObjectProperties.put(unqProp, entry.getValue());
512 result = unQObjectProperties;
519 * extractPart extracts an XML object from given DocumentModel
521 * @param schema of the object to extract
522 * @param partMeta metadata for the object to extract
526 protected Map<String, Object> extractPart(
527 DocumentModel docModel, String schema, ObjectPartType partMeta,
528 Map<String, Object> addToMap)
530 Map<String, Object> result = null;
532 result = this.extractPart(docModel, schema, addToMap);
538 public String getStringPropertyFromDoc(
541 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
542 RepositoryInstance repoSession = null;
543 boolean releaseRepoSession = false;
544 String returnValue = null;
547 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
548 repoSession = this.getRepositorySession();
549 if (repoSession == null) {
550 repoSession = repoClient.getRepositorySession();
551 releaseRepoSession = true;
555 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
556 DocumentModel docModel = wrapper.getWrappedObject();
557 returnValue = (String) docModel.getPropertyValue(propertyXPath);
558 } catch (PropertyException pe) {
560 } catch (DocumentException de) {
562 } catch (Exception e) {
563 if (logger.isDebugEnabled()) {
564 logger.debug("Caught exception ", e);
566 throw new DocumentException(e);
568 if (releaseRepoSession && repoSession != null) {
569 repoClient.releaseRepositorySession(repoSession);
572 } catch (Exception e) {
573 if (logger.isDebugEnabled()) {
574 logger.debug("Caught exception ", e);
576 throw new DocumentException(e);
580 if (logger.isWarnEnabled() == true) {
581 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
590 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
593 public AuthorityRefList getAuthorityRefs(
595 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
597 AuthorityRefList authRefList = new AuthorityRefList();
598 AbstractCommonList commonList = (AbstractCommonList) authRefList;
600 DocumentFilter docFilter = this.getDocumentFilter();
601 long pageSize = docFilter.getPageSize();
602 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
603 // set the page size and page number
604 commonList.setPageNum(pageNum);
605 commonList.setPageSize(pageSize);
607 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
610 int iFirstToUse = (int)(pageSize*pageNum);
611 int nFoundInPage = 0;
614 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
615 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
617 boolean releaseRepoSession = false;
618 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
619 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
620 RepositoryInstance repoSession = this.getRepositorySession();
621 if (repoSession == null) {
622 repoSession = repoClient.getRepositorySession(ctx);
623 releaseRepoSession = true;
627 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
628 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
629 // Slightly goofy pagination support - how many refs do we expect from one object?
630 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
631 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
632 if(appendToAuthRefsList(ari, list)) {
641 if (releaseRepoSession == true) {
642 repoClient.releaseRepositorySession(ctx, repoSession);
646 // Set num of items in list. this is useful to our testing framework.
647 commonList.setItemsInPage(nFoundInPage);
648 // set the total result size
649 commonList.setTotalItems(nFoundTotal);
651 } catch (PropertyException pe) {
652 String msg = "Attempted to retrieve value for invalid or missing authority field. "
653 + "Check authority field properties in tenant bindings.";
654 logger.warn(msg, pe);
656 } catch (Exception e) {
657 if (logger.isDebugEnabled()) {
658 logger.debug("Caught exception in getAuthorityRefs", e);
660 Response response = Response.status(
661 Response.Status.INTERNAL_SERVER_ERROR).entity(
662 "Failed to retrieve authority references").type(
663 "text/plain").build();
664 throw new WebApplicationException(response);
670 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
671 List<AuthorityRefList.AuthorityRefItem> list)
673 String fieldName = ari.getQualifiedDisplayName();
675 String refNameValue = (String)ari.getProperty().getValue();
676 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
677 if(item!=null) { // ignore garbage values.
681 } catch(PropertyException pe) {
682 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
687 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
689 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
691 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
692 ilistItem.setRefName(refName);
693 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
694 ilistItem.setItemDisplayName(termInfo.displayName);
695 ilistItem.setSourceField(authRefFieldName);
696 ilistItem.setUri(termInfo.getRelativeUri());
697 } catch (Exception e) {
698 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
705 * Returns the primary value from a list of values.
707 * Assumes that the first value is the primary value.
708 * This assumption may change when and if the primary value
709 * is identified explicitly.
711 * @param values a list of values.
712 * @param propertyName the name of a property through
713 * which the value can be extracted.
714 * @return the primary value.
715 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
716 String primaryValue = "";
717 if (values == null || values.size() == 0) {
720 Object value = values.get(0);
721 if (value instanceof String) {
723 primaryValue = (String) value;
725 // Multivalue group of fields
726 } else if (value instanceof Map) {
728 Map map = (Map) value;
729 if (map.values().size() > 0) {
730 if (map.get(propertyName) != null) {
731 primaryValue = (String) map.get(propertyName);
736 logger.warn("Unexpected type for property " + propertyName
737 + " in multivalue list: not String or Map.");
744 * Gets a simple property from the document.
746 * For completeness, as this duplicates DocumentModel method.
748 * @param docModel The document model to get info from
749 * @param schema The name of the schema (part)
750 * @param propertyName The simple scalar property type
751 * @return property value as String
753 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
754 String xpath = "/"+schema+":"+propName;
756 return (String)docModel.getPropertyValue(xpath);
757 } catch(PropertyException pe) {
758 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
759 +pe.getLocalizedMessage());
760 } catch(ClassCastException cce) {
761 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
762 +cce.getLocalizedMessage());
763 } catch(Exception e) {
764 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
765 +e.getLocalizedMessage());
770 * Gets first of a repeating list of scalar values, as a String, from the document.
772 * @param docModel The document model to get info from
773 * @param schema The name of the schema (part)
774 * @param listName The name of the scalar list property
775 * @return first value in list, as a String, or empty string if the list is empty
777 protected String getFirstRepeatingStringProperty(
778 DocumentModel docModel, String schema, String listName) {
779 String xpath = "/"+schema+":"+listName+"/[0]";
781 return (String)docModel.getPropertyValue(xpath);
782 } catch(PropertyException pe) {
783 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
784 +pe.getLocalizedMessage());
785 } catch(IndexOutOfBoundsException ioobe) {
786 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
787 return ""; // gracefully handle missing elements
788 } catch(ClassCastException cce) {
789 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
790 +cce.getLocalizedMessage());
791 } catch(Exception e) {
792 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
793 +e.getLocalizedMessage());
799 * Gets first of a repeating list of scalar values, as a String, from the document.
801 * @param docModel The document model to get info from
802 * @param schema The name of the schema (part)
803 * @param listName The name of the scalar list property
804 * @return first value in list, as a String, or empty string if the list is empty
806 protected String getStringValueInPrimaryRepeatingComplexProperty(
807 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
808 String result = null;
810 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
812 result = (String)docModel.getPropertyValue(xpath);
813 } catch(PropertyException pe) {
814 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
815 +pe.getLocalizedMessage());
816 } catch(IndexOutOfBoundsException ioobe) {
817 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
818 result = ""; // gracefully handle missing elements
819 } catch(ClassCastException cce) {
820 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
821 +cce.getLocalizedMessage());
822 } catch(Exception e) {
823 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
824 +e.getLocalizedMessage());
831 * Gets XPath value from schema. Note that only "/" and "[n]" are
832 * supported for xpath. Can omit grouping elements for repeating complex types,
833 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
834 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
835 * If there are no entries for a list of scalars or for a list of complex types,
836 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
837 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
838 * that many elements in the list.
839 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
841 * @param docModel The document model to get info from
842 * @param schema The name of the schema (part)
843 * @param xpath The XPath expression (without schema prefix)
844 * @return value the indicated property value as a String
846 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
847 String schema, ListResultField field) {
848 Object result = null;
850 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
855 protected String getStringValue(DocumentModel docModel,
856 String schema, ListResultField field) {
857 String result = null;
859 Object value = getListResultValue(docModel, schema, field);
860 if (value != null && value instanceof String) {
861 String strValue = (String) value;
862 if (strValue.trim().isEmpty() == false) {
870 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
874 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
876 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
878 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
880 sb.append(item.getPredicate());
882 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
886 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
887 StringBuilder sb = new StringBuilder();
889 if (list.size() > 0) {
890 sb.append("=========== " + label + " ==========" + CR);
892 for (RelationsCommonList.RelationListItem item : list) {
893 itemToString(sb, "== ", item);
896 return sb.toString();
899 /** @return null on parent not found
901 protected String getParentCSID(String thisCSID) throws Exception {
902 String parentCSID = null;
904 String predicate = RelationshipType.HAS_BROADER.value();
905 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
906 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
907 if (parentList != null) {
908 if (parentList.size() == 0) {
911 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
912 parentCSID = relationListItem.getObjectCsid();
915 } catch (Exception e) {
916 logger.error("Could not find parent for this: " + thisCSID, e);
921 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
922 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
926 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
927 MultipartServiceContext ctx) throws Exception {
928 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
929 String parentCSID = getParentCSID(thisCSID);
930 if (parentCSID == null) {
931 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
935 String predicate = RelationshipType.HAS_BROADER.value();
936 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
937 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
939 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
942 RelationsCommonList.RelationListItem item = null;
943 for (RelationsCommonList.RelationListItem sibling : siblingList) {
944 if (thisCSID.equals(sibling.getSubjectCsid())) {
945 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.
948 //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.
949 for (RelationsCommonList.RelationListItem self : toRemoveList) {
950 removeFromList(siblingList, self);
953 long siblingSize = siblingList.size();
954 siblingListOuter.setTotalItems(siblingSize);
955 siblingListOuter.setItemsInPage(siblingSize);
956 if(logger.isTraceEnabled()) {
957 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
958 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
961 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
962 ctx.addOutputPart(relationsPart);
965 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
966 MultipartServiceContext ctx) throws Exception {
967 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
969 String predicate = RelationshipType.HAS_BROADER.value();
970 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
971 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
973 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
974 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
976 if(logger.isTraceEnabled()) {
977 String dump = dumpLists(thisCSID, parentList, childrenList, null);
978 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
981 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
982 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
983 //Not optimal, but that's the current design spec.
985 for (RelationsCommonList.RelationListItem parent : parentList) {
986 childrenList.add(parent);
989 long childrenSize = childrenList.size();
990 childrenListOuter.setTotalItems(childrenSize);
991 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
993 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
994 ctx.addOutputPart(relationsPart);
997 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
998 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1000 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1001 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1003 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1004 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1006 if(logger.isTraceEnabled()) {
1007 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1008 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1011 subjectList.addAll(objectList);
1013 //now subjectList actually has records BOTH where thisCSID is subject and object.
1014 long relatedSize = subjectList.size();
1015 subjectListOuter.setTotalItems(relatedSize);
1016 subjectListOuter.setItemsInPage(relatedSize);
1018 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1019 ctx.addOutputPart(relationsPart);
1022 private String dumpLists(String itemCSID,
1023 List<RelationsCommonList.RelationListItem> parentList,
1024 List<RelationsCommonList.RelationListItem> childList,
1025 List<RelationsCommonList.RelationListItem> actionList) {
1026 StringBuilder sb = new StringBuilder();
1027 sb.append("itemCSID: " + itemCSID + CR);
1028 if(parentList!=null) {
1029 sb.append(dumpList(parentList, "parentList"));
1031 if(childList!=null) {
1032 sb.append(dumpList(childList, "childList"));
1034 if(actionList!=null) {
1035 sb.append(dumpList(actionList, "actionList"));
1037 return sb.toString();
1040 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1041 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1042 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1043 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1044 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1045 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1046 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1048 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1049 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1050 return relationsCommonList;
1052 //============================= END TODO refactor ==========================
1054 // this method calls the RelationResource to have it create the relations and persist them.
1055 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1056 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1057 for (RelationsCommonList.RelationListItem item : inboundList) {
1058 RelationsCommon rc = new RelationsCommon();
1059 //rc.setCsid(item.getCsid());
1060 //todo: assignTo(item, rc);
1061 RelationsDocListItem itemSubject = item.getSubject();
1062 RelationsDocListItem itemObject = item.getObject();
1064 // Set at least one of CSID and refName for Subject and Object
1065 // Either value might be null for for each of Subject and Object
1066 String subjectCsid = itemSubject.getCsid();
1067 rc.setSubjectCsid(subjectCsid);
1069 String objCsid = itemObject.getCsid();
1070 rc.setObjectCsid(objCsid);
1072 rc.setSubjectRefName(itemSubject.getRefName());
1073 rc.setObjectRefName(itemObject.getRefName());
1075 rc.setRelationshipType(item.getPredicate());
1076 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1077 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1078 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1080 // This is superfluous, since it will be fetched by the Relations Create logic.
1081 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1082 rc.setObjectDocumentType(itemObject.getDocumentType());
1084 // This is superfluous, since it will be fetched by the Relations Create logic.
1085 rc.setSubjectUri(itemSubject.getUri());
1086 rc.setObjectUri(itemObject.getUri());
1087 // May not have the info here. Only really require CSID or refName.
1088 // Rest is handled in the Relation create mechanism
1089 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1091 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1092 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1093 payloadOut.addPart(outputPart);
1094 RelationResource relationResource = new RelationResource();
1095 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1096 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1100 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1101 // But item1 must not be sparse
1102 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1103 if (item1 == null || item2 == null) {
1106 RelationsDocListItem subj1 = item1.getSubject();
1107 RelationsDocListItem subj2 = item2.getSubject();
1108 RelationsDocListItem obj1 = item1.getObject();
1109 RelationsDocListItem obj2 = item2.getObject();
1111 String subj1Csid = subj1.getCsid();
1112 String subj2Csid = subj2.getCsid();
1113 String subj1RefName = subj1.getRefName();
1114 String subj2RefName = subj2.getRefName();
1116 String obj1Csid = obj1.getCsid();
1117 String obj2Csid = obj2.getCsid();
1118 String obj1RefName = obj1.getRefName();
1119 String obj2RefName = obj2.getRefName();
1121 String item1Metatype = item1.getRelationshipMetaType();
1122 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1124 String item2Metatype = item2.getRelationshipMetaType();
1125 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1127 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1128 && (obj1Csid.equals(obj1Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1129 // predicate is proper, but still allow relationshipType
1130 && (item1.getPredicate().equals(item2.getPredicate())
1131 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1132 // Allow missing docTypes, so long as they do not conflict
1133 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1134 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1135 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1139 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1140 // But the list items must not be sparse
1141 private RelationsCommonList.RelationListItem findInList(
1142 List<RelationsCommonList.RelationListItem> list,
1143 RelationsCommonList.RelationListItem item) {
1144 RelationsCommonList.RelationListItem foundItem = null;
1145 for (RelationsCommonList.RelationListItem listItem : list) {
1146 if (itemsEqual(listItem, item)) { //equals must be defined, else
1147 foundItem = listItem;
1154 /** updateRelations strategy:
1157 go through inboundList, remove anything from childList that matches from childList
1158 go through inboundList, remove anything from parentList that matches from parentList
1159 go through parentList, delete all remaining
1160 go through childList, delete all remaining
1161 go through actionList, add all remaining.
1162 check for duplicate children
1163 check for more than one parent.
1165 inboundList parentList childList actionList
1166 ---------------- --------------- ---------------- ----------------
1167 child-a parent-c child-a child-b
1168 child-b parent-d child-c
1173 private RelationsCommonList updateRelations(
1174 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1176 if (logger.isTraceEnabled()) {
1177 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1179 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1181 return null; //nothing to do--they didn't send a list of relations.
1183 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1184 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1185 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1186 List<RelationsCommonList.RelationListItem> childList = null;
1187 List<RelationsCommonList.RelationListItem> parentList = null;
1188 DocumentModel docModel = wrapDoc.getWrappedObject();
1189 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1190 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1192 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1193 //Do magic replacement of ${itemCSID} and fix URI's.
1194 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1196 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1197 UriInfo uriInfo = ctx.getUriInfo();
1198 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1201 //Run getList() once as sent to get childListOuter:
1202 String predicate = RelationshipType.HAS_BROADER.value();
1203 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1204 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1205 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1206 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1207 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1209 RelationResource relationResource = new RelationResource();
1210 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1212 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1213 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1214 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1215 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1216 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1219 childList = childListOuter.getRelationListItem();
1220 parentList = parentListOuter.getRelationListItem();
1222 if (parentList.size() > 1) {
1223 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1226 if (logger.isTraceEnabled()) {
1227 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1231 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1232 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1233 // and so the CSID for those may be null
1234 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1235 // Look for parents and children
1236 if(itemCSID.equals(inboundItem.getObject().getCsid())
1237 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1238 //then this is an item that says we have a child. That child is inboundItem
1239 RelationsCommonList.RelationListItem childItem =
1240 (childList == null) ? null : findInList(childList, inboundItem);
1241 if (childItem != null) {
1242 if (logger.isTraceEnabled()) {
1243 StringBuilder sb = new StringBuilder();
1244 itemToString(sb, "== Child: ", childItem);
1245 logger.trace("Found inboundChild in current child list: " + sb.toString());
1247 removeFromList(childList, childItem); //exists, just take it off delete list
1249 if (logger.isTraceEnabled()) {
1250 StringBuilder sb = new StringBuilder();
1251 itemToString(sb, "== Child: ", inboundItem);
1252 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1254 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1255 String newChildCsid = inboundItem.getSubject().getCsid();
1256 if(newChildCsid == null) {
1257 String newChildRefName = inboundItem.getSubject().getRefName();
1258 if(newChildRefName==null) {
1259 throw new RuntimeException("Child with no CSID or refName!");
1261 if (logger.isTraceEnabled()) {
1262 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1264 DocumentModel newChildDocModel =
1265 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1266 newChildRefName, getServiceContext().getResourceMap());
1267 newChildCsid = getCsid(newChildDocModel);
1269 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1272 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1273 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1274 //then this is an item that says we have a parent. inboundItem is that parent.
1275 RelationsCommonList.RelationListItem parentItem =
1276 (parentList == null) ? null : findInList(parentList, inboundItem);
1277 if (parentItem != null) {
1278 removeFromList(parentList, parentItem); //exists, just take it off delete list
1280 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1283 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1286 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1289 if (logger.isTraceEnabled()) {
1290 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1291 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1294 if (logger.isTraceEnabled()) {
1295 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1296 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1298 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1299 deleteRelations(childList, ctx, "childList");
1301 if (logger.isTraceEnabled()) {
1302 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1303 + actionList.size() + " new parents and children.");
1305 createRelations(actionList, ctx);
1306 if (logger.isTraceEnabled()) {
1307 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1309 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1310 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1311 return relationsCommonListBody;
1314 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1315 * and sets URI correctly for related items.
1316 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1318 protected void fixupInboundListItems(ServiceContext ctx,
1319 List<RelationsCommonList.RelationListItem> inboundList,
1320 DocumentModel docModel,
1321 String itemCSID) throws Exception {
1322 String thisURI = this.getUri(docModel);
1323 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1324 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1325 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1326 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1327 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1329 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1330 inboundItem.setObjectCsid(itemCSID);
1331 inboundItemObject.setCsid(itemCSID);
1332 //inboundItemObject.setUri(getUri(docModel));
1335 String objectCsid = inboundItemObject.getCsid();
1336 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1337 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1338 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1339 inboundItemObject.setUri(uri); //CSPACE-4037
1342 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1344 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1345 inboundItem.setSubjectCsid(itemCSID);
1346 inboundItemSubject.setCsid(itemCSID);
1347 //inboundItemSubject.setUri(getUri(docModel));
1350 String subjectCsid = inboundItemSubject.getCsid();
1351 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1352 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1353 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1354 inboundItemSubject.setUri(uri); //CSPACE-4037
1357 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1362 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1363 MultivaluedMap<String, String> queryParams, String childCSID) {
1364 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1365 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1366 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1367 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1369 RelationResource relationResource = new RelationResource();
1370 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1371 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1372 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1373 deleteRelations(parentList, ctx, "parentList-delete");
1376 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1377 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1380 for (RelationsCommonList.RelationListItem item : list) {
1381 RelationResource relationResource = new RelationResource();
1382 if(logger.isTraceEnabled()) {
1383 StringBuilder sb = new StringBuilder();
1384 itemToString(sb, "==== TO DELETE: ", item);
1385 logger.trace(sb.toString());
1387 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1388 if (logger.isDebugEnabled()) {
1389 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1392 } catch (Throwable t) {
1393 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1398 // Note that we must do this after we have completed the Update, so that the repository has the
1399 // info for the item itself. The relations code must call into the repo to get info for each end.
1400 // This could be optimized to pass in the parent docModel, since it will often be one end.
1401 // Nevertheless, we should complete the item save before we do work on the relations, especially
1402 // since a save on Create might fail, and we would not want to create relations for something
1403 // that may not be created...
1404 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1405 ServiceContext ctx = getServiceContext();
1406 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1407 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1408 String itemCsid = documentModel.getName();
1410 //Updates relations part
1411 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1413 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
1414 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1416 //now we add part for relations list
1417 //ServiceContext ctx = getServiceContext();
1418 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1419 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1423 * Checks to see if the refName has changed, and if so,
1424 * uses utilities to find all references and update them to use the new refName.
1427 protected void handleRefNameReferencesUpdate() throws Exception {
1428 if (hasRefNameUpdate() == true) {
1429 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1430 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1431 RepositoryInstance repoSession = this.getRepositorySession();
1433 // Update all the relationship records that referred to the old refName
1434 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1435 oldRefNameOnUpdate, newRefNameOnUpdate);
1439 protected String getRefNameUpdate() {
1440 String result = null;
1442 if (hasRefNameUpdate() == true) {
1443 result = newRefNameOnUpdate;
1444 if (logger.isDebugEnabled() == true) {
1445 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1446 newRefNameOnUpdate, oldRefNameOnUpdate));
1454 * Note: The Vocabulary document handler overrides this method.
1456 protected String getRefPropName() {
1457 return ServiceBindingUtils.AUTH_REF_PROP;