2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.nuxeo.client.java;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
30 import java.util.Map.Entry;
33 import javax.ws.rs.core.MediaType;
34 import javax.ws.rs.core.MultivaluedMap;
35 import javax.ws.rs.core.Response;
36 import javax.ws.rs.core.UriInfo;
37 import javax.xml.bind.JAXBElement;
39 import org.collectionspace.authentication.spi.AuthNContext;
40 import org.collectionspace.services.authorization.AccountPermission;
41 import org.collectionspace.services.jaxb.AbstractCommonList;
42 import org.collectionspace.services.lifecycle.TransitionDef;
43 import org.collectionspace.services.client.AccountClient;
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.NuxeoBasedResource;
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.slf4j.Logger;
88 import org.slf4j.LoggerFactory;
91 * RemoteDocumentModelHandler
93 * $LastChangedRevision: $
98 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
99 extends DocumentModelHandler<T, TL> {
102 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
103 private final static String CR = "\r\n";
104 private final static String EMPTYSTR = "";
105 private static final String COLLECTIONSPACE_CORE_SCHEMA = CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA;
106 private static final String ACCOUNT_PERMISSION_COMMON_PART_NAME = AccountClient.SERVICE_COMMON_PART_NAME;
109 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
112 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
113 if (ctx instanceof MultipartServiceContext) {
114 super.setServiceContext(ctx);
116 throw new IllegalArgumentException("setServiceContext requires instance of "
117 + MultipartServiceContext.class.getName());
122 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
123 return getRefnameDisplayName(docWrapper.getWrappedObject());
126 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
127 String result = null;
128 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
130 DocHandlerParams.Params params = null;
132 params = ServiceConfigUtils.getDocHandlerParams(ctx);
133 ListResultField field = params.getRefnameDisplayNameField();
135 String schema = field.getSchema();
136 if (schema == null || schema.trim().isEmpty()) {
137 schema = ctx.getCommonPartLabel();
140 result = getStringValue(docModel, schema, field);
141 } catch (Exception e) {
142 if (logger.isWarnEnabled()) {
143 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
151 public boolean supportsHierarchy() {
152 boolean result = false;
154 DocHandlerParams.Params params = null;
156 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
157 params = ServiceConfigUtils.getDocHandlerParams(ctx);
158 Boolean bool = params.isSupportsHierarchy();
160 result = bool.booleanValue();
162 } catch (DocumentException e) {
163 // TODO Auto-generated catch block
164 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
165 if (logger.isWarnEnabled() == true) {
174 public boolean supportsVersioning() {
175 boolean result = false;
177 DocHandlerParams.Params params = null;
179 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
180 params = ServiceConfigUtils.getDocHandlerParams(ctx);
181 Boolean bool = params.isSupportsVersioning();
183 result = bool.booleanValue();
185 } catch (DocumentException e) {
186 // TODO Auto-generated catch block
187 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
188 if (logger.isWarnEnabled() == true) {
197 public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
199 // Do nothing by default, but children can override if they want. The real workflow transition happens in the WorkflowDocumentModelHandler 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 == null || input.getParts().isEmpty()) {
396 String msg = String.format("No payload found for '%s' action.", action);
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 or if it is a system part
415 ObjectPartType partMeta = partsMetaMap.get(partLabel);
416 if (partMeta == null || isSystemPart(partLabel)) {
419 fillPart(part, docModel, partMeta, action, ctx);
423 private boolean isSystemPart(String partLabel) {
424 boolean result = false;
426 if (partLabel != null && (partLabel.equalsIgnoreCase(COLLECTIONSPACE_CORE_SCHEMA) ||
427 partLabel.equalsIgnoreCase(ACCOUNT_PERMISSION_COMMON_PART_NAME))) {
435 * fillPart fills an XML part into given document model
436 * @param part to fill
437 * @param docModel for the given object
438 * @param partMeta metadata for the object to fill
441 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
442 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
444 //check if this is an xml part
445 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
446 Element element = part.getElementBody();
447 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
448 if (action == Action.UPDATE) {
449 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
451 docModel.setProperties(partMeta.getLabel(), objectProps);
456 * Filters out read only properties, so they cannot be set on update.
457 * TODO: add configuration support to do this generally
458 * @param objectProps the properties parsed from the update payload
459 * @param partMeta metadata for the object to fill
461 public void filterReadOnlyPropertiesForPart(
462 Map<String, Object> objectProps, ObjectPartType partMeta) {
463 // Should add in logic to filter most of the core items on update
464 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
465 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
466 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
467 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
468 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
469 // Note that the updatedAt/updatedBy fields are set internally
470 // in DocumentModelHandler.handleCoreValues().
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
481 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
483 return extractPart(docModel, schema, (Map<String, Object>)null);
487 * extractPart extracts an XML object from given DocumentModel
489 * @param schema of the object to extract
490 * @param partMeta metadata for the object to extract
494 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
496 return extractPart(docModel, schema, partMeta, null);
500 * extractPart extracts an XML object from given DocumentModel
502 * @param schema of the object to extract
503 * @param partMeta metadata for the object to extract
506 protected Map<String, Object> extractPart(
507 DocumentModel docModel,
509 Map<String, Object> addToMap)
511 Map<String, Object> result = null;
513 Map<String, Object> objectProps = docModel.getProperties(schema);
514 if (objectProps != null) {
515 //unqualify properties before sending the doc over the wire (to save bandwidh)
516 //FIXME: is there a better way to avoid duplication of a Map/Collection?
517 Map<String, Object> unQObjectProperties =
518 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
519 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
520 for (Entry<String, Object> entry : qualifiedEntries) {
521 String unqProp = getUnQProperty(entry.getKey());
522 unQObjectProperties.put(unqProp, entry.getValue());
524 result = unQObjectProperties;
531 * extractPart extracts an XML object from given DocumentModel
533 * @param schema of the object to extract
534 * @param partMeta metadata for the object to extract
538 protected Map<String, Object> extractPart(
539 DocumentModel docModel, String schema, ObjectPartType partMeta,
540 Map<String, Object> addToMap)
542 Map<String, Object> result = null;
544 result = this.extractPart(docModel, schema, addToMap);
550 public String getStringPropertyFromDoc(
553 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
554 RepositoryInstance repoSession = null;
555 boolean releaseRepoSession = false;
556 String returnValue = null;
559 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
560 repoSession = this.getRepositorySession();
561 if (repoSession == null) {
562 repoSession = repoClient.getRepositorySession();
563 releaseRepoSession = true;
567 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
568 DocumentModel docModel = wrapper.getWrappedObject();
569 returnValue = (String) docModel.getPropertyValue(propertyXPath);
570 } catch (PropertyException pe) {
572 } catch (DocumentException de) {
574 } catch (Exception e) {
575 if (logger.isDebugEnabled()) {
576 logger.debug("Caught exception ", e);
578 throw new DocumentException(e);
580 if (releaseRepoSession && repoSession != null) {
581 repoClient.releaseRepositorySession(repoSession);
584 } catch (Exception e) {
585 if (logger.isDebugEnabled()) {
586 logger.debug("Caught exception ", e);
588 throw new DocumentException(e);
592 if (logger.isWarnEnabled() == true) {
593 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
602 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
605 public AuthorityRefList getAuthorityRefs(
607 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException, Exception {
609 AuthorityRefList authRefList = new AuthorityRefList();
610 AbstractCommonList commonList = (AbstractCommonList) authRefList;
612 DocumentFilter docFilter = this.getDocumentFilter();
613 long pageSize = docFilter.getPageSize();
614 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
615 // set the page size and page number
616 commonList.setPageNum(pageNum);
617 commonList.setPageSize(pageSize);
619 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
622 int iFirstToUse = (int)(pageSize*pageNum);
623 int nFoundInPage = 0;
626 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
627 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
629 boolean releaseRepoSession = false;
630 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
631 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
632 CoreSessionInterface repoSession = this.getRepositorySession();
633 if (repoSession == null) {
634 repoSession = repoClient.getRepositorySession(ctx);
635 releaseRepoSession = true;
639 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
640 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
641 // Slightly goofy pagination support - how many refs do we expect from one object?
642 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
643 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
644 if(appendToAuthRefsList(ari, list)) {
653 if (releaseRepoSession == true) {
654 repoClient.releaseRepositorySession(ctx, repoSession);
658 // Set num of items in list. this is useful to our testing framework.
659 commonList.setItemsInPage(nFoundInPage);
660 // set the total result size
661 commonList.setTotalItems(nFoundTotal);
663 } catch (PropertyException pe) {
664 String msg = "Attempted to retrieve value for invalid or missing authority field. "
665 + "Check authority field properties in tenant bindings.";
666 logger.warn(msg, pe);
668 } catch (Exception e) {
669 if (logger.isDebugEnabled()) {
670 logger.debug("Caught exception in getAuthorityRefs", e);
672 Response response = Response.status(
673 Response.Status.INTERNAL_SERVER_ERROR).entity(
674 "Failed to retrieve authority references").type(
675 "text/plain").build();
676 throw new CSWebApplicationException(e, response);
682 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
683 List<AuthorityRefList.AuthorityRefItem> list)
685 String fieldName = ari.getQualifiedDisplayName();
687 String refNameValue = (String)ari.getProperty().getValue();
688 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
689 if(item!=null) { // ignore garbage values.
693 } catch(PropertyException pe) {
694 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
699 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
701 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
703 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
704 ilistItem.setRefName(refName);
705 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
706 ilistItem.setItemDisplayName(termInfo.displayName);
707 ilistItem.setSourceField(authRefFieldName);
708 ilistItem.setUri(termInfo.getRelativeUri());
709 } catch (Exception e) {
710 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
717 * Returns the primary value from a list of values.
719 * Assumes that the first value is the primary value.
720 * This assumption may change when and if the primary value
721 * is identified explicitly.
723 * @param values a list of values.
724 * @param propertyName the name of a property through
725 * which the value can be extracted.
726 * @return the primary value.
727 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
728 String primaryValue = "";
729 if (values == null || values.size() == 0) {
732 Object value = values.get(0);
733 if (value instanceof String) {
735 primaryValue = (String) value;
737 // Multivalue group of fields
738 } else if (value instanceof Map) {
740 Map map = (Map) value;
741 if (map.values().size() > 0) {
742 if (map.get(propertyName) != null) {
743 primaryValue = (String) map.get(propertyName);
748 logger.warn("Unexpected type for property " + propertyName
749 + " in multivalue list: not String or Map.");
756 * Gets a simple property from the document.
758 * For completeness, as this duplicates DocumentModel method.
760 * @param docModel The document model to get info from
761 * @param schema The name of the schema (part)
762 * @param propertyName The simple scalar property type
763 * @return property value as String
765 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
766 String xpath = "/"+schema+":"+propName;
768 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
769 } catch(PropertyException pe) {
770 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
771 +pe.getLocalizedMessage());
772 } catch(ClassCastException cce) {
773 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
774 +cce.getLocalizedMessage());
775 } catch(Exception e) {
776 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
777 +e.getLocalizedMessage());
782 * Gets first of a repeating list of scalar values, as a String, from the document.
784 * @param docModel The document model to get info from
785 * @param schema The name of the schema (part)
786 * @param listName The name of the scalar list property
787 * @return first value in list, as a String, or empty string if the list is empty
789 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
790 String schema, String listName) {
791 String xpath = "/" + schema + ":" + listName + "/[0]";
793 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
794 } catch (PropertyException pe) {
795 throw new RuntimeException("Problem retrieving property {" + xpath
796 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
797 } catch (IndexOutOfBoundsException ioobe) {
798 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
799 return ""; // gracefully handle missing elements
800 } catch (ClassCastException cce) {
801 throw new RuntimeException("Problem retrieving property {" + xpath
802 + "} as String. Not a repeating String property?"
803 + cce.getLocalizedMessage());
804 } catch (Exception e) {
805 throw new RuntimeException("Unknown problem retrieving property {"
806 + xpath + "}." + e.getLocalizedMessage());
811 * Gets first of a repeating list of scalar values, as a String, from the document.
813 * @param docModel The document model to get info from
814 * @param schema The name of the schema (part)
815 * @param listName The name of the scalar list property
816 * @return first value in list, as a String, or empty string if the list is empty
818 protected String getStringValueInPrimaryRepeatingComplexProperty(
819 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
820 String result = null;
822 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
824 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
825 } catch(PropertyException pe) {
826 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
827 +pe.getLocalizedMessage());
828 } catch(IndexOutOfBoundsException ioobe) {
829 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
830 result = ""; // gracefully handle missing elements
831 } catch(ClassCastException cce) {
832 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
833 +cce.getLocalizedMessage());
834 } catch(NullPointerException npe) {
835 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
836 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
838 } catch(Exception e) {
839 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
840 +e.getLocalizedMessage());
847 * Gets XPath value from schema. Note that only "/" and "[n]" are
848 * supported for xpath. Can omit grouping elements for repeating complex types,
849 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
850 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
851 * If there are no entries for a list of scalars or for a list of complex types,
852 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
853 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
854 * that many elements in the list.
855 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
857 * @param docModel The document model to get info from
858 * @param schema The name of the schema (part)
859 * @param xpath The XPath expression (without schema prefix)
860 * @return value the indicated property value as a String
861 * @throws DocumentException
863 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
864 String schema, ListResultField field) throws DocumentException {
865 Object result = null;
867 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
872 protected String getStringValue(DocumentModel docModel,
873 String schema, ListResultField field) throws DocumentException {
874 String result = null;
876 Object value = getListResultValue(docModel, schema, field);
877 if (value != null && value instanceof String) {
878 String strValue = (String) value;
879 if (strValue.trim().isEmpty() == false) {
887 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
891 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
893 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
895 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
897 sb.append(item.getPredicate());
899 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
903 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
904 StringBuilder sb = new StringBuilder();
906 if (list.size() > 0) {
907 sb.append("=========== " + label + " ==========" + CR);
909 for (RelationsCommonList.RelationListItem item : list) {
910 itemToString(sb, "== ", item);
913 return sb.toString();
916 /** @return null on parent not found
918 protected String getParentCSID(String thisCSID) throws Exception {
919 String parentCSID = null;
921 String predicate = RelationshipType.HAS_BROADER.value();
922 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
923 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
924 if (parentList != null) {
925 if (parentList.size() == 0) {
928 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
929 parentCSID = relationListItem.getObjectCsid();
932 } catch (Exception e) {
933 logger.error("Could not find parent for this: " + thisCSID, e);
938 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
939 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
943 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
944 MultipartServiceContext ctx) throws Exception {
945 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
946 String parentCSID = getParentCSID(thisCSID);
947 if (parentCSID == null) {
948 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
952 String predicate = RelationshipType.HAS_BROADER.value();
953 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
954 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
956 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
959 RelationsCommonList.RelationListItem item = null;
960 for (RelationsCommonList.RelationListItem sibling : siblingList) {
961 if (thisCSID.equals(sibling.getSubjectCsid())) {
962 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.
965 //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.
966 for (RelationsCommonList.RelationListItem self : toRemoveList) {
967 removeFromList(siblingList, self);
970 long siblingSize = siblingList.size();
971 siblingListOuter.setTotalItems(siblingSize);
972 siblingListOuter.setItemsInPage(siblingSize);
973 if(logger.isTraceEnabled()) {
974 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
975 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
978 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
979 ctx.addOutputPart(relationsPart);
982 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
983 MultipartServiceContext ctx) throws Exception {
984 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
986 String predicate = RelationshipType.HAS_BROADER.value();
987 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
988 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
990 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
991 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
993 if(logger.isTraceEnabled()) {
994 String dump = dumpLists(thisCSID, parentList, childrenList, null);
995 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
998 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
999 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
1000 //Not optimal, but that's the current design spec.
1002 for (RelationsCommonList.RelationListItem parent : parentList) {
1003 childrenList.add(parent);
1006 long childrenSize = childrenList.size();
1007 childrenListOuter.setTotalItems(childrenSize);
1008 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1010 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1011 ctx.addOutputPart(relationsPart);
1014 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1015 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1017 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1018 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1020 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1021 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1023 if(logger.isTraceEnabled()) {
1024 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1025 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1028 subjectList.addAll(objectList);
1030 //now subjectList actually has records BOTH where thisCSID is subject and object.
1031 long relatedSize = subjectList.size();
1032 subjectListOuter.setTotalItems(relatedSize);
1033 subjectListOuter.setItemsInPage(relatedSize);
1035 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1036 ctx.addOutputPart(relationsPart);
1039 private String dumpLists(String itemCSID,
1040 List<RelationsCommonList.RelationListItem> parentList,
1041 List<RelationsCommonList.RelationListItem> childList,
1042 List<RelationsCommonList.RelationListItem> actionList) {
1043 StringBuilder sb = new StringBuilder();
1044 sb.append("itemCSID: " + itemCSID + CR);
1045 if(parentList!=null) {
1046 sb.append(dumpList(parentList, "parentList"));
1048 if(childList!=null) {
1049 sb.append(dumpList(childList, "childList"));
1051 if(actionList!=null) {
1052 sb.append(dumpList(actionList, "actionList"));
1054 return sb.toString();
1057 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1058 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1059 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1060 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1061 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1062 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1063 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1065 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1066 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1067 return relationsCommonList;
1069 //============================= END TODO refactor ==========================
1071 // this method calls the RelationResource to have it create the relations and persist them.
1072 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1073 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1074 for (RelationsCommonList.RelationListItem item : inboundList) {
1075 RelationsCommon rc = new RelationsCommon();
1076 //rc.setCsid(item.getCsid());
1077 //todo: assignTo(item, rc);
1078 RelationsDocListItem itemSubject = item.getSubject();
1079 RelationsDocListItem itemObject = item.getObject();
1081 // Set at least one of CSID and refName for Subject and Object
1082 // Either value might be null for for each of Subject and Object
1083 String subjectCsid = itemSubject.getCsid();
1084 rc.setSubjectCsid(subjectCsid);
1086 String objCsid = itemObject.getCsid();
1087 rc.setObjectCsid(objCsid);
1089 rc.setSubjectRefName(itemSubject.getRefName());
1090 rc.setObjectRefName(itemObject.getRefName());
1092 rc.setRelationshipType(item.getPredicate());
1093 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1094 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1095 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1097 // This is superfluous, since it will be fetched by the Relations Create logic.
1098 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1099 rc.setObjectDocumentType(itemObject.getDocumentType());
1101 // This is superfluous, since it will be fetched by the Relations Create logic.
1102 rc.setSubjectUri(itemSubject.getUri());
1103 rc.setObjectUri(itemObject.getUri());
1104 // May not have the info here. Only really require CSID or refName.
1105 // Rest is handled in the Relation create mechanism
1106 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1108 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1109 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1110 payloadOut.addPart(outputPart);
1111 RelationResource relationResource = new RelationResource();
1112 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1113 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1117 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1118 // But item1 must not be sparse
1119 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1120 if (item1 == null || item2 == null) {
1123 RelationsDocListItem subj1 = item1.getSubject();
1124 RelationsDocListItem subj2 = item2.getSubject();
1125 RelationsDocListItem obj1 = item1.getObject();
1126 RelationsDocListItem obj2 = item2.getObject();
1128 String subj1Csid = subj1.getCsid();
1129 String subj2Csid = subj2.getCsid();
1130 String subj1RefName = subj1.getRefName();
1131 String subj2RefName = subj2.getRefName();
1133 String obj1Csid = obj1.getCsid();
1134 String obj2Csid = obj2.getCsid();
1135 String obj1RefName = obj1.getRefName();
1136 String obj2RefName = obj2.getRefName();
1138 String item1Metatype = item1.getRelationshipMetaType();
1139 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1141 String item2Metatype = item2.getRelationshipMetaType();
1142 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1144 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1145 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1146 // predicate is proper, but still allow relationshipType
1147 && (item1.getPredicate().equals(item2.getPredicate())
1148 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1149 // Allow missing docTypes, so long as they do not conflict
1150 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1151 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1152 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1156 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1157 // But the list items must not be sparse
1158 private RelationsCommonList.RelationListItem findInList(
1159 List<RelationsCommonList.RelationListItem> list,
1160 RelationsCommonList.RelationListItem item) {
1161 RelationsCommonList.RelationListItem foundItem = null;
1162 for (RelationsCommonList.RelationListItem listItem : list) {
1163 if (itemsEqual(listItem, item)) { //equals must be defined, else
1164 foundItem = listItem;
1171 /** updateRelations strategy:
1174 go through inboundList, remove anything from childList that matches from childList
1175 go through inboundList, remove anything from parentList that matches from parentList
1176 go through parentList, delete all remaining
1177 go through childList, delete all remaining
1178 go through actionList, add all remaining.
1179 check for duplicate children
1180 check for more than one parent.
1182 inboundList parentList childList actionList
1183 ---------------- --------------- ---------------- ----------------
1184 child-a parent-c child-a child-b
1185 child-b parent-d child-c
1190 private RelationsCommonList updateRelations(
1191 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate)
1193 if (logger.isTraceEnabled()) {
1194 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1196 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1198 return null; //nothing to do--they didn't send a list of relations.
1200 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1201 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1202 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1203 List<RelationsCommonList.RelationListItem> childList = null;
1204 List<RelationsCommonList.RelationListItem> parentList = null;
1205 DocumentModel docModel = wrapDoc.getWrappedObject();
1206 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1207 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1209 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1210 //Do magic replacement of ${itemCSID} and fix URI's.
1211 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1213 final String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1214 UriInfo uriInfo = ctx.getUriInfo();
1215 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1218 //Run getList() once as sent to get childListOuter:
1219 String predicate = RelationshipType.HAS_BROADER.value();
1220 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1221 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1222 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1223 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1224 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1226 RelationResource relationResource = new RelationResource();
1227 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1229 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1230 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1231 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1232 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1233 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1236 childList = childListOuter.getRelationListItem();
1237 parentList = parentListOuter.getRelationListItem();
1239 if (parentList.size() > 1) {
1240 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1243 if (logger.isTraceEnabled()) {
1244 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1248 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1249 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1250 // and so the CSID for those may be null
1251 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1252 // Look for parents and children
1253 if(itemCSID.equals(inboundItem.getObject().getCsid())
1254 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1255 //then this is an item that says we have a child. That child is inboundItem
1256 RelationsCommonList.RelationListItem childItem =
1257 (childList == null) ? null : findInList(childList, inboundItem);
1258 if (childItem != null) {
1259 if (logger.isTraceEnabled()) {
1260 StringBuilder sb = new StringBuilder();
1261 itemToString(sb, "== Child: ", childItem);
1262 logger.trace("Found inboundChild in current child list: " + sb.toString());
1264 removeFromList(childList, childItem); //exists, just take it off delete list
1266 if (logger.isTraceEnabled()) {
1267 StringBuilder sb = new StringBuilder();
1268 itemToString(sb, "== Child: ", inboundItem);
1269 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1271 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1272 String newChildCsid = inboundItem.getSubject().getCsid();
1273 if(newChildCsid == null) {
1274 String newChildRefName = inboundItem.getSubject().getRefName();
1275 if (newChildRefName == null) {
1276 throw new RuntimeException("Child with no CSID or refName!");
1278 if (logger.isTraceEnabled()) {
1279 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1281 DocumentModel newChildDocModel =
1282 NuxeoBasedResource.getDocModelForRefName(this.getRepositorySession(),
1283 newChildRefName, getServiceContext().getResourceMap());
1284 newChildCsid = getCsid(newChildDocModel);
1286 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1289 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1290 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1291 //then this is an item that says we have a parent. inboundItem is that parent.
1292 RelationsCommonList.RelationListItem parentItem =
1293 (parentList == null) ? null : findInList(parentList, inboundItem);
1294 if (parentItem != null) {
1295 removeFromList(parentList, parentItem); //exists, just take it off delete list
1297 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1300 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1303 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1306 if (logger.isTraceEnabled()) {
1307 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1308 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1311 if (logger.isTraceEnabled()) {
1312 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1313 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1315 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1316 deleteRelations(childList, ctx, "childList");
1318 if (logger.isTraceEnabled()) {
1319 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1320 + actionList.size() + " new parents and children.");
1322 createRelations(actionList, ctx);
1323 if (logger.isTraceEnabled()) {
1324 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1326 // We return all elements on the inbound list, since we have just worked to make them exist in the system
1327 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1328 return relationsCommonListBody;
1331 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1332 * and sets URI correctly for related items.
1333 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1335 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1336 List<RelationsCommonList.RelationListItem> inboundList,
1337 DocumentModel docModel,
1338 String itemCSID) throws Exception {
1339 String thisURI = this.getUri(docModel);
1340 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1341 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1342 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1343 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1344 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1346 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1347 inboundItem.setObjectCsid(itemCSID);
1348 inboundItemObject.setCsid(itemCSID);
1349 //inboundItemObject.setUri(getUri(docModel));
1352 String objectCsid = inboundItemObject.getCsid();
1353 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1354 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1355 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1356 inboundItemObject.setUri(uri); //CSPACE-4037
1359 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1361 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1362 inboundItem.setSubjectCsid(itemCSID);
1363 inboundItemSubject.setCsid(itemCSID);
1364 //inboundItemSubject.setUri(getUri(docModel));
1367 String subjectCsid = inboundItemSubject.getCsid();
1368 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1369 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1370 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1371 inboundItemSubject.setUri(uri); //CSPACE-4037
1374 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1379 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1380 MultivaluedMap<String, String> queryParams, String childCSID) {
1381 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1382 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1383 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1384 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1386 RelationResource relationResource = new RelationResource();
1387 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1388 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1389 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1390 deleteRelations(parentList, ctx, "parentList-delete");
1393 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1394 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1397 for (RelationsCommonList.RelationListItem item : list) {
1398 RelationResource relationResource = new RelationResource();
1399 if(logger.isTraceEnabled()) {
1400 StringBuilder sb = new StringBuilder();
1401 itemToString(sb, "==== TO DELETE: ", item);
1402 logger.trace(sb.toString());
1404 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1405 if (logger.isDebugEnabled()) {
1406 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1409 } catch (Throwable t) {
1410 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1415 // Note that we must do this after we have completed the Update, so that the repository has the
1416 // info for the item itself. The relations code must call into the repo to get info for each end.
1417 // This could be optimized to pass in the parent docModel, since it will often be one end.
1418 // Nevertheless, we should complete the item save before we do work on the relations, especially
1419 // since a save on Create might fail, and we would not want to create relations for something
1420 // that may not be created...
1421 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate) throws Exception {
1422 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1423 PoxPayloadIn input = ctx.getInput();
1424 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1425 String itemCsid = documentModel.getName();
1427 //Updates relations part
1428 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, forUpdate);
1430 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
1431 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1433 //now we add part for relations list
1434 //ServiceContext ctx = getServiceContext();
1435 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1436 ctx.getOutput().addPart(payloadOutputPart);
1440 * Checks to see if the refName has changed, and if so,
1441 * uses utilities to find all references and update them to use the new refName.
1444 protected void handleRefNameReferencesUpdate() throws Exception {
1445 if (hasRefNameUpdate() == true) {
1446 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1447 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1448 CoreSessionInterface repoSession = this.getRepositorySession();
1450 // Update all the relationship records that referred to the old refName
1451 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1452 oldRefNameOnUpdate, newRefNameOnUpdate);
1456 protected String getRefNameUpdate() {
1457 String result = null;
1459 if (hasRefNameUpdate() == true) {
1460 result = newRefNameOnUpdate;
1461 if (logger.isDebugEnabled() == true) {
1462 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1463 newRefNameOnUpdate, oldRefNameOnUpdate));
1471 * Note: The Vocabulary document handler overrides this method.
1473 protected String getRefPropName() {
1474 return ServiceBindingUtils.AUTH_REF_PROP;