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.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
76 import org.collectionspace.services.config.service.DocHandlerParams;
77 import org.collectionspace.services.config.service.ListResultField;
78 import org.collectionspace.services.config.service.ObjectPartType;
79 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
80 import org.collectionspace.services.relation.RelationsCommon;
81 import org.collectionspace.services.relation.RelationsCommonList;
82 import org.collectionspace.services.relation.RelationsDocListItem;
83 import org.collectionspace.services.relation.RelationshipType;
84 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.DocumentNotFoundException;
88 import org.nuxeo.ecm.core.api.model.PropertyException;
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
93 * RemoteDocumentModelHandler
95 * $LastChangedRevision: $
100 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
101 extends DocumentModelHandler<T, TL> {
104 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
105 private final static String CR = "\r\n";
106 private final static String EMPTYSTR = "";
107 private static final String COLLECTIONSPACE_CORE_SCHEMA = CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA;
108 private static final String ACCOUNT_PERMISSION_COMMON_PART_NAME = AccountClient.SERVICE_COMMON_PART_NAME;
111 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
114 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
115 if (ctx instanceof MultipartServiceContext) {
116 super.setServiceContext(ctx);
118 throw new IllegalArgumentException("setServiceContext requires instance of "
119 + MultipartServiceContext.class.getName());
124 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
125 return getRefnameDisplayName(docWrapper.getWrappedObject());
128 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
129 String result = null;
130 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
132 DocHandlerParams.Params params = null;
134 params = ServiceConfigUtils.getDocHandlerParams(ctx);
135 ListResultField field = params.getRefnameDisplayNameField();
137 String schema = field.getSchema();
138 if (schema == null || schema.trim().isEmpty()) {
139 schema = ctx.getCommonPartLabel();
142 result = getStringValue(docModel, schema, field);
143 } catch (Exception e) {
144 if (logger.isWarnEnabled()) {
145 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
153 public boolean supportsHierarchy() {
154 boolean result = false;
156 DocHandlerParams.Params params = null;
158 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
159 params = ServiceConfigUtils.getDocHandlerParams(ctx);
160 Boolean bool = params.isSupportsHierarchy();
162 result = bool.booleanValue();
164 } catch (DocumentException e) {
165 // TODO Auto-generated catch block
166 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
167 if (logger.isWarnEnabled() == true) {
176 public boolean supportsVersioning() {
177 boolean result = false;
179 DocHandlerParams.Params params = null;
181 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
182 params = ServiceConfigUtils.getDocHandlerParams(ctx);
183 Boolean bool = params.isSupportsVersioning();
185 result = bool.booleanValue();
187 } catch (DocumentException e) {
188 // TODO Auto-generated catch block
189 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
190 if (logger.isWarnEnabled() == true) {
199 public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
201 // Do nothing by default, but children can override if they want. The real workflow transition happens in the WorkflowDocumentModelHandler 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();
221 String[] schemas = docModel.getDeclaredSchemas();
222 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
223 for (String schema : schemas) {
224 ObjectPartType partMeta = partsMetaMap.get(schema);
225 if (partMeta == null) {
226 continue; // unknown part, ignore
228 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
229 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
230 addExtraCoreValues(docModel, unQObjectProperties);
232 addOutputPart(unQObjectProperties, schema, partMeta);
236 // If the resource's service supports hierarchy then we need to perform a little more work
238 if (supportsHierarchy() == true) {
239 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
240 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
245 * Adds the output part.
247 * @param unQObjectProperties the un q object properties
248 * @param schema the schema
249 * @param partMeta the part meta
250 * @throws Exception the exception
251 * MediaType.APPLICATION_XML_TYPE
253 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
255 Element doc = DocumentUtils.buildDocument(partMeta, schema,
256 unQObjectProperties);
257 if (logger.isTraceEnabled() == true) {
258 logger.trace(doc.asXML());
260 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
261 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
265 * Extract paging info.
267 * @param commonsList the commons list
269 * @throws Exception the exception
271 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
273 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
275 DocumentFilter docFilter = this.getDocumentFilter();
276 long pageSize = docFilter.getPageSize();
277 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
278 // set the page size and page number
279 commonList.setPageNum(pageNum);
280 commonList.setPageSize(pageSize);
281 DocumentModelList docList = wrapDoc.getWrappedObject();
282 // Set num of items in list. this is useful to our testing framework.
283 commonList.setItemsInPage(docList.size());
284 // set the total result size
285 commonList.setTotalItems(docList.totalSize());
287 return (TL) commonList;
291 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
294 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
297 DocumentModel docModel = wrapDoc.getWrappedObject();
298 String[] schemas = docModel.getDeclaredSchemas();
299 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
300 for (String schema : schemas) {
301 ObjectPartType partMeta = partsMetaMap.get(schema);
302 if (partMeta == null) {
303 continue; // unknown part, ignore
305 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
306 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
307 addExtraCoreValues(docModel, unQObjectProperties);
309 addOutputPart(unQObjectProperties, schema, partMeta);
312 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
314 if (supportsHierarchy() == true) {
315 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
316 if (Tools.isTrue(showSiblings)) {
317 showSiblings(wrapDoc, ctx);
318 return; // actual result is returned on ctx.addOutputPart();
321 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
322 if (Tools.isTrue(showRelations)) {
323 showRelations(wrapDoc, ctx);
324 return; // actual result is returned on ctx.addOutputPart();
327 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
328 if (Tools.isTrue(showAllRelations)) {
329 showAllRelations(wrapDoc, ctx);
330 return; // actual result is returned on ctx.addOutputPart();
334 String currentUser = ctx.getUserId();
335 if (currentUser.equalsIgnoreCase(AuthNContext.ANONYMOUS_USER) == false) {
336 addAccountPermissionsPart();
340 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
342 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
345 private void addAccountPermissionsPart() throws Exception {
346 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
349 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
350 String currentServiceName = ctx.getServiceName();
351 String workflowSubResource = "/";
352 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
353 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
354 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
355 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
357 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
359 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
360 currentServiceName, workflowSubResource);
361 org.collectionspace.services.authorization.ObjectFactory objectFactory =
362 new org.collectionspace.services.authorization.ObjectFactory();
363 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
364 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
365 ctx.addOutputPart(accountPermissionPart);
371 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
374 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
376 //TODO filling extension parts should be dynamic
377 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
378 //not an ideal way of populating objects.
379 DocumentModel docModel = wrapDoc.getWrappedObject();
380 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
381 PoxPayloadIn input = ctx.getInput();
382 if (input == null || input.getParts().isEmpty()) {
383 String msg = String.format("No payload found for '%s' action.", action);
384 logger.error(msg + "Ctx=" + getServiceContext().toString());
385 throw new BadRequestException(msg);
388 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
390 //iterate over parts received and fill those parts
391 List<PayloadInputPart> inputParts = input.getParts();
392 for (PayloadInputPart part : inputParts) {
394 String partLabel = part.getLabel();
395 if (partLabel == null) {
396 String msg = "Part label is missing or empty!";
397 logger.error(msg + "Ctx=" + getServiceContext().toString());
398 throw new BadRequestException(msg);
401 //skip if the part is not in metadata or if it is a system part
402 ObjectPartType partMeta = partsMetaMap.get(partLabel);
403 if (partMeta == null || isSystemPart(partLabel)) {
406 fillPart(part, docModel, partMeta, action, ctx);
410 private boolean isSystemPart(String partLabel) {
411 boolean result = false;
413 if (partLabel != null && (partLabel.equalsIgnoreCase(COLLECTIONSPACE_CORE_SCHEMA) ||
414 partLabel.equalsIgnoreCase(ACCOUNT_PERMISSION_COMMON_PART_NAME))) {
422 * fillPart fills an XML part into given document model
423 * @param part to fill
424 * @param docModel for the given object
425 * @param partMeta metadata for the object to fill
428 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
429 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
431 //check if this is an xml part
432 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
433 Element element = part.getElementBody();
434 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
435 if (action == Action.UPDATE) {
436 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
438 docModel.setProperties(partMeta.getLabel(), objectProps);
443 * Filters out read only properties, so they cannot be set on update.
444 * TODO: add configuration support to do this generally
445 * @param objectProps the properties parsed from the update payload
446 * @param partMeta metadata for the object to fill
448 public void filterReadOnlyPropertiesForPart(
449 Map<String, Object> objectProps, ObjectPartType partMeta) {
450 // Should add in logic to filter most of the core items on update
451 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
452 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
453 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
454 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
455 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
456 // Note that the updatedAt/updatedBy fields are set internally
457 // in DocumentModelHandler.handleCoreValues().
462 * extractPart extracts an XML object from given DocumentModel
464 * @param schema of the object to extract
465 * @param partMeta metadata for the object to extract
468 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
470 return extractPart(docModel, schema, (Map<String, Object>)null);
474 * extractPart extracts an XML object from given DocumentModel
476 * @param schema of the object to extract
477 * @param partMeta metadata for the object to extract
481 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
483 return extractPart(docModel, schema, partMeta, 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
493 protected Map<String, Object> extractPart(
494 DocumentModel docModel,
496 Map<String, Object> addToMap)
498 Map<String, Object> result = null;
500 Map<String, Object> objectProps = docModel.getProperties(schema);
501 if (objectProps != null) {
502 //unqualify properties before sending the doc over the wire (to save bandwidh)
503 //FIXME: is there a better way to avoid duplication of a Map/Collection?
504 Map<String, Object> unQObjectProperties =
505 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
506 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
507 for (Entry<String, Object> entry : qualifiedEntries) {
508 String unqProp = getUnQProperty(entry.getKey());
509 unQObjectProperties.put(unqProp, entry.getValue());
511 result = unQObjectProperties;
518 * extractPart extracts an XML object from given DocumentModel
520 * @param schema of the object to extract
521 * @param partMeta metadata for the object to extract
525 protected Map<String, Object> extractPart(
526 DocumentModel docModel, String schema, ObjectPartType partMeta,
527 Map<String, Object> addToMap)
529 Map<String, Object> result = null;
531 result = this.extractPart(docModel, schema, addToMap);
537 public String getStringPropertyFromDoc(
540 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
541 RepositoryInstance repoSession = null;
542 boolean releaseRepoSession = false;
543 String returnValue = null;
546 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
547 repoSession = this.getRepositorySession();
548 if (repoSession == null) {
549 repoSession = repoClient.getRepositorySession();
550 releaseRepoSession = true;
554 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
555 DocumentModel docModel = wrapper.getWrappedObject();
556 returnValue = (String) docModel.getPropertyValue(propertyXPath);
557 } catch (PropertyException pe) {
559 } catch (DocumentException de) {
561 } catch (Exception e) {
562 if (logger.isDebugEnabled()) {
563 logger.debug("Caught exception ", e);
565 throw new DocumentException(e);
567 if (releaseRepoSession && repoSession != null) {
568 repoClient.releaseRepositorySession(repoSession);
571 } catch (Exception e) {
572 if (logger.isDebugEnabled()) {
573 logger.debug("Caught exception ", e);
575 throw new DocumentException(e);
579 if (logger.isWarnEnabled() == true) {
580 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
589 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
592 public AuthorityRefList getAuthorityRefs(
594 List<AuthRefConfigInfo> authRefConfigInfoList) throws PropertyException, Exception {
596 AuthorityRefList authRefList = new AuthorityRefList();
597 AbstractCommonList commonList = (AbstractCommonList) authRefList;
599 DocumentFilter docFilter = this.getDocumentFilter();
600 long pageSize = docFilter.getPageSize();
601 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
602 // set the page size and page number
603 commonList.setPageNum(pageNum);
604 commonList.setPageSize(pageSize);
606 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
609 int iFirstToUse = (int)(pageSize*pageNum);
610 int nFoundInPage = 0;
613 ArrayList<RefNameServiceUtils.AuthRefInfo> foundReferences
614 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
616 boolean releaseRepoSession = false;
617 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
618 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
619 CoreSessionInterface repoSession = this.getRepositorySession();
620 if (repoSession == null) {
621 repoSession = repoClient.getRepositorySession(ctx);
622 releaseRepoSession = true;
623 this.setRepositorySession(repoSession); // we (the doc handler) should keep track of this repository session in case we need it
627 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
628 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefConfigInfoList, null, foundReferences);
629 // Slightly goofy pagination support - how many refs do we expect from one object?
630 for(RefNameServiceUtils.AuthRefInfo ari:foundReferences) {
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 CSWebApplicationException(e, 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 String msg = "PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage();
683 if (logger.isDebugEnabled()) {
684 logger.debug(msg, pe);
693 * Fill in all the values to be returned in the authrefs payload for this item.
695 * @param authRefFieldName
699 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
701 // Find the CSID for the authority item
705 DocumentModel docModel = NuxeoUtils.getDocModelForRefName(getServiceContext(), refName, getServiceContext().getResourceMap());
706 csid = NuxeoUtils.getCsid(docModel);
707 } catch (Exception e1) {
708 String msg = String.format("Could not find CSID for authority reference with refname = %s.", refName);
709 if (logger.isDebugEnabled()) {
710 logger.debug(msg, e1);
716 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
718 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
719 if (Tools.isEmpty(csid) == false) {
720 ilistItem.setCsid(csid);
722 ilistItem.setRefName(refName);
723 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
724 ilistItem.setItemDisplayName(termInfo.displayName);
725 ilistItem.setSourceField(authRefFieldName);
726 ilistItem.setUri(termInfo.getRelativeUri());
727 } catch (Exception e) {
729 String msg = String.format("Trouble parsing refName from value: %s in field: %s. Error message: %s.",
730 refName, authRefFieldName, e.getLocalizedMessage());
731 if (logger.isDebugEnabled()) {
732 logger.debug(msg, e);
742 * Returns the primary value from a list of values.
744 * Assumes that the first value is the primary value.
745 * This assumption may change when and if the primary value
746 * is identified explicitly.
748 * @param values a list of values.
749 * @param propertyName the name of a property through
750 * which the value can be extracted.
751 * @return the primary value.
752 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
753 String primaryValue = "";
754 if (values == null || values.size() == 0) {
757 Object value = values.get(0);
758 if (value instanceof String) {
760 primaryValue = (String) value;
762 // Multivalue group of fields
763 } else if (value instanceof Map) {
765 Map map = (Map) value;
766 if (map.values().size() > 0) {
767 if (map.get(propertyName) != null) {
768 primaryValue = (String) map.get(propertyName);
773 logger.warn("Unexpected type for property " + propertyName
774 + " in multivalue list: not String or Map.");
781 * Gets a simple property from the document.
783 * For completeness, as this duplicates DocumentModel method.
785 * @param docModel The document model to get info from
786 * @param schema The name of the schema (part)
787 * @param propertyName The simple scalar property type
788 * @return property value as String
790 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
791 String xpath = "/"+schema+":"+propName;
793 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
794 } catch(PropertyException pe) {
795 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
796 +pe.getLocalizedMessage());
797 } catch(ClassCastException cce) {
798 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
799 +cce.getLocalizedMessage());
800 } catch(Exception e) {
801 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
802 +e.getLocalizedMessage());
807 * Gets first of a repeating list of scalar values, as a String, from the document.
809 * @param docModel The document model to get info from
810 * @param schema The name of the schema (part)
811 * @param listName The name of the scalar list property
812 * @return first value in list, as a String, or empty string if the list is empty
814 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
815 String schema, String listName) {
816 String xpath = "/" + schema + ":" + listName + "/[0]";
818 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
819 } catch (PropertyException pe) {
820 throw new RuntimeException("Problem retrieving property {" + xpath
821 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
822 } catch (IndexOutOfBoundsException ioobe) {
823 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
824 return ""; // gracefully handle missing elements
825 } catch (ClassCastException cce) {
826 throw new RuntimeException("Problem retrieving property {" + xpath
827 + "} as String. Not a repeating String property?"
828 + cce.getLocalizedMessage());
829 } catch (Exception e) {
830 throw new RuntimeException("Unknown problem retrieving property {"
831 + xpath + "}." + e.getLocalizedMessage());
836 * Gets first of a repeating list of scalar values, as a String, from the document.
838 * @param docModel The document model to get info from
839 * @param schema The name of the schema (part)
840 * @param listName The name of the scalar list property
841 * @return first value in list, as a String, or empty string if the list is empty
843 protected String getStringValueInPrimaryRepeatingComplexProperty(
844 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
845 String result = null;
847 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
849 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
850 } catch(PropertyException pe) {
851 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
852 +pe.getLocalizedMessage());
853 } catch(IndexOutOfBoundsException ioobe) {
854 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
855 result = ""; // gracefully handle missing elements
856 } catch(ClassCastException cce) {
857 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
858 +cce.getLocalizedMessage());
859 } catch(NullPointerException npe) {
860 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
861 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
863 } catch(Exception e) {
864 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
865 +e.getLocalizedMessage());
872 * Gets XPath value from schema. Note that only "/" and "[n]" are
873 * supported for xpath. Can omit grouping elements for repeating complex types,
874 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
875 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
876 * If there are no entries for a list of scalars or for a list of complex types,
877 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
878 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
879 * that many elements in the list.
880 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
882 * @param docModel The document model to get info from
883 * @param schema The name of the schema (part)
884 * @param xpath The XPath expression (without schema prefix)
885 * @return value the indicated property value as a String
886 * @throws DocumentException
888 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
889 String schema, ListResultField field) throws DocumentException {
890 Object result = null;
892 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
897 protected String getStringValue(DocumentModel docModel,
898 String schema, ListResultField field) throws DocumentException {
899 String result = null;
901 Object value = getListResultValue(docModel, schema, field);
902 if (value != null && value instanceof String) {
903 String strValue = (String) value;
904 if (strValue.trim().isEmpty() == false) {
912 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
916 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
918 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
920 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
922 sb.append(item.getPredicate());
924 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
928 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
929 StringBuilder sb = new StringBuilder();
931 if (list.size() > 0) {
932 sb.append("=========== " + label + " ==========" + CR);
934 for (RelationsCommonList.RelationListItem item : list) {
935 itemToString(sb, "== ", item);
938 return sb.toString();
941 /** @return null on parent not found
943 protected String getParentCSID(String thisCSID) throws Exception {
944 String parentCSID = null;
946 String predicate = RelationshipType.HAS_BROADER.value();
947 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
948 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
949 if (parentList != null) {
950 if (parentList.size() == 0) {
953 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
954 parentCSID = relationListItem.getObjectCsid();
957 } catch (Exception e) {
958 logger.error("Could not find parent for this: " + thisCSID, e);
963 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
964 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
968 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
969 MultipartServiceContext ctx) throws Exception {
970 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
971 String parentCSID = getParentCSID(thisCSID);
972 if (parentCSID == null) {
973 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
977 String predicate = RelationshipType.HAS_BROADER.value();
978 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
979 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
981 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
984 RelationsCommonList.RelationListItem item = null;
985 for (RelationsCommonList.RelationListItem sibling : siblingList) {
986 if (thisCSID.equals(sibling.getSubjectCsid())) {
987 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.
990 //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.
991 for (RelationsCommonList.RelationListItem self : toRemoveList) {
992 removeFromList(siblingList, self);
995 long siblingSize = siblingList.size();
996 siblingListOuter.setTotalItems(siblingSize);
997 siblingListOuter.setItemsInPage(siblingSize);
998 if(logger.isTraceEnabled()) {
999 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
1000 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1003 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
1004 ctx.addOutputPart(relationsPart);
1007 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
1008 MultipartServiceContext ctx) throws Exception {
1009 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1011 String predicate = RelationshipType.HAS_BROADER.value();
1012 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1013 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1015 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
1016 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
1018 if(logger.isTraceEnabled()) {
1019 String dump = dumpLists(thisCSID, parentList, childrenList, null);
1020 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1023 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
1024 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
1025 //Not optimal, but that's the current design spec.
1027 for (RelationsCommonList.RelationListItem parent : parentList) {
1028 childrenList.add(parent);
1031 long childrenSize = childrenList.size();
1032 childrenListOuter.setTotalItems(childrenSize);
1033 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1035 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1036 ctx.addOutputPart(relationsPart);
1039 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1040 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1042 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1043 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1045 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1046 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1048 if(logger.isTraceEnabled()) {
1049 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1050 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1053 subjectList.addAll(objectList);
1055 //now subjectList actually has records BOTH where thisCSID is subject and object.
1056 long relatedSize = subjectList.size();
1057 subjectListOuter.setTotalItems(relatedSize);
1058 subjectListOuter.setItemsInPage(relatedSize);
1060 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1061 ctx.addOutputPart(relationsPart);
1064 private String dumpLists(String itemCSID,
1065 List<RelationsCommonList.RelationListItem> parentList,
1066 List<RelationsCommonList.RelationListItem> childList,
1067 List<RelationsCommonList.RelationListItem> actionList) {
1068 StringBuilder sb = new StringBuilder();
1069 sb.append("itemCSID: " + itemCSID + CR);
1070 if(parentList!=null) {
1071 sb.append(dumpList(parentList, "parentList"));
1073 if(childList!=null) {
1074 sb.append(dumpList(childList, "childList"));
1076 if(actionList!=null) {
1077 sb.append(dumpList(actionList, "actionList"));
1079 return sb.toString();
1082 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1083 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1084 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1085 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1086 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1087 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1088 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1090 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1091 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1092 return relationsCommonList;
1094 //============================= END TODO refactor ==========================
1096 // this method calls the RelationResource to have it create the relations and persist them.
1097 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1098 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1099 for (RelationsCommonList.RelationListItem item : inboundList) {
1100 RelationsCommon rc = new RelationsCommon();
1101 //rc.setCsid(item.getCsid());
1102 //todo: assignTo(item, rc);
1103 RelationsDocListItem itemSubject = item.getSubject();
1104 RelationsDocListItem itemObject = item.getObject();
1106 // Set at least one of CSID and refName for Subject and Object
1107 // Either value might be null for for each of Subject and Object
1108 String subjectCsid = itemSubject.getCsid();
1109 rc.setSubjectCsid(subjectCsid);
1111 String objCsid = itemObject.getCsid();
1112 rc.setObjectCsid(objCsid);
1114 rc.setSubjectRefName(itemSubject.getRefName());
1115 rc.setObjectRefName(itemObject.getRefName());
1117 rc.setRelationshipType(item.getPredicate());
1118 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1119 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1120 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1122 // This is superfluous, since it will be fetched by the Relations Create logic.
1123 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1124 rc.setObjectDocumentType(itemObject.getDocumentType());
1126 // This is superfluous, since it will be fetched by the Relations Create logic.
1127 rc.setSubjectUri(itemSubject.getUri());
1128 rc.setObjectUri(itemObject.getUri());
1129 // May not have the info here. Only really require CSID or refName.
1130 // Rest is handled in the Relation create mechanism
1131 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1133 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1134 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1135 payloadOut.addPart(outputPart);
1136 RelationResource relationResource = new RelationResource();
1137 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1138 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1142 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1143 // But item1 must not be sparse
1144 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1145 if (item1 == null || item2 == null) {
1148 RelationsDocListItem subj1 = item1.getSubject();
1149 RelationsDocListItem subj2 = item2.getSubject();
1150 RelationsDocListItem obj1 = item1.getObject();
1151 RelationsDocListItem obj2 = item2.getObject();
1153 String subj1Csid = subj1.getCsid();
1154 String subj2Csid = subj2.getCsid();
1155 String subj1RefName = subj1.getRefName();
1156 String subj2RefName = subj2.getRefName();
1158 String obj1Csid = obj1.getCsid();
1159 String obj2Csid = obj2.getCsid();
1160 String obj1RefName = obj1.getRefName();
1161 String obj2RefName = obj2.getRefName();
1163 String item1Metatype = item1.getRelationshipMetaType();
1164 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1166 String item2Metatype = item2.getRelationshipMetaType();
1167 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1169 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1170 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1171 // predicate is proper, but still allow relationshipType
1172 && (item1.getPredicate().equals(item2.getPredicate())
1173 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1174 // Allow missing docTypes, so long as they do not conflict
1175 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1176 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1177 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1181 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1182 // But the list items must not be sparse
1183 private RelationsCommonList.RelationListItem findInList(
1184 List<RelationsCommonList.RelationListItem> list,
1185 RelationsCommonList.RelationListItem item) {
1186 RelationsCommonList.RelationListItem foundItem = null;
1187 for (RelationsCommonList.RelationListItem listItem : list) {
1188 if (itemsEqual(listItem, item)) { //equals must be defined, else
1189 foundItem = listItem;
1196 /** updateRelations strategy:
1199 go through inboundList, remove anything from childList that matches from childList
1200 go through inboundList, remove anything from parentList that matches from parentList
1201 go through parentList, delete all remaining
1202 go through childList, delete all remaining
1203 go through actionList, add all remaining.
1204 check for duplicate children
1205 check for more than one parent.
1207 inboundList parentList childList actionList
1208 ---------------- --------------- ---------------- ----------------
1209 child-a parent-c child-a child-b
1210 child-b parent-d child-c
1215 private RelationsCommonList updateRelations(
1216 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate)
1218 if (logger.isTraceEnabled()) {
1219 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1221 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1223 return null; //nothing to do--they didn't send a list of relations.
1225 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1226 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1227 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1228 List<RelationsCommonList.RelationListItem> childList = null;
1229 List<RelationsCommonList.RelationListItem> parentList = null;
1230 DocumentModel docModel = wrapDoc.getWrappedObject();
1231 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1232 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1234 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1235 //Do magic replacement of ${itemCSID} and fix URI's.
1236 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1238 final String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1239 UriInfo uriInfo = ctx.getUriInfo();
1240 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1243 //Run getList() once as sent to get childListOuter:
1244 String predicate = RelationshipType.HAS_BROADER.value();
1245 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1246 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1247 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1248 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1249 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1251 RelationResource relationResource = new RelationResource();
1252 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1254 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1255 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1256 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1257 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1258 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1261 childList = childListOuter.getRelationListItem();
1262 parentList = parentListOuter.getRelationListItem();
1264 if (parentList.size() > 1) {
1265 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1268 if (logger.isTraceEnabled()) {
1269 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1273 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1274 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1275 // and so the CSID for those may be null
1276 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1277 // Look for parents and children
1278 if(itemCSID.equals(inboundItem.getObject().getCsid())
1279 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1280 //then this is an item that says we have a child. That child is inboundItem
1281 RelationsCommonList.RelationListItem childItem =
1282 (childList == null) ? null : findInList(childList, inboundItem);
1283 if (childItem != null) {
1284 if (logger.isTraceEnabled()) {
1285 StringBuilder sb = new StringBuilder();
1286 itemToString(sb, "== Child: ", childItem);
1287 logger.trace("Found inboundChild in current child list: " + sb.toString());
1289 removeFromList(childList, childItem); //exists, just take it off delete list
1291 if (logger.isTraceEnabled()) {
1292 StringBuilder sb = new StringBuilder();
1293 itemToString(sb, "== Child: ", inboundItem);
1294 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1296 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1297 String newChildCsid = inboundItem.getSubject().getCsid();
1298 if(newChildCsid == null) {
1299 String newChildRefName = inboundItem.getSubject().getRefName();
1300 if (newChildRefName == null) {
1301 throw new RuntimeException("Child with no CSID or refName!");
1303 if (logger.isTraceEnabled()) {
1304 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1306 DocumentModel newChildDocModel =
1307 NuxeoBasedResource.getDocModelForRefName(getServiceContext(),
1308 newChildRefName, getServiceContext().getResourceMap());
1309 newChildCsid = getCsid(newChildDocModel);
1311 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1314 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1315 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1316 //then this is an item that says we have a parent. inboundItem is that parent.
1317 RelationsCommonList.RelationListItem parentItem =
1318 (parentList == null) ? null : findInList(parentList, inboundItem);
1319 if (parentItem != null) {
1320 removeFromList(parentList, parentItem); //exists, just take it off delete list
1322 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1325 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1328 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1331 if (logger.isTraceEnabled()) {
1332 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1333 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1336 if (logger.isTraceEnabled()) {
1337 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1338 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1340 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1341 deleteRelations(childList, ctx, "childList");
1343 if (logger.isTraceEnabled()) {
1344 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1345 + actionList.size() + " new parents and children.");
1347 createRelations(actionList, ctx);
1348 if (logger.isTraceEnabled()) {
1349 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1351 // We return all elements on the inbound list, since we have just worked to make them exist in the system
1352 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1353 return relationsCommonListBody;
1356 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1357 * and sets URI correctly for related items.
1358 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1360 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1361 List<RelationsCommonList.RelationListItem> inboundList,
1362 DocumentModel docModel,
1363 String itemCSID) throws Exception {
1364 String thisURI = this.getUri(docModel);
1365 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1366 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1367 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1368 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1369 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1371 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1372 inboundItem.setObjectCsid(itemCSID);
1373 inboundItemObject.setCsid(itemCSID);
1374 //inboundItemObject.setUri(getUri(docModel));
1377 String objectCsid = inboundItemObject.getCsid();
1378 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1379 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1380 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1381 inboundItemObject.setUri(uri); //CSPACE-4037
1384 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1386 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1387 inboundItem.setSubjectCsid(itemCSID);
1388 inboundItemSubject.setCsid(itemCSID);
1389 //inboundItemSubject.setUri(getUri(docModel));
1392 String subjectCsid = inboundItemSubject.getCsid();
1393 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1394 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1395 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1396 inboundItemSubject.setUri(uri); //CSPACE-4037
1399 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1404 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1405 MultivaluedMap<String, String> queryParams, String childCSID) {
1406 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1407 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1408 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1409 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1411 RelationResource relationResource = new RelationResource();
1412 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1413 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1414 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1415 deleteRelations(parentList, ctx, "parentList-delete");
1418 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1419 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1422 for (RelationsCommonList.RelationListItem item : list) {
1423 RelationResource relationResource = new RelationResource();
1424 if(logger.isTraceEnabled()) {
1425 StringBuilder sb = new StringBuilder();
1426 itemToString(sb, "==== TO DELETE: ", item);
1427 logger.trace(sb.toString());
1429 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1430 if (logger.isDebugEnabled()) {
1431 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1434 } catch (Throwable t) {
1435 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1440 // Note that we must do this after we have completed the Update, so that the repository has the
1441 // info for the item itself. The relations code must call into the repo to get info for each end.
1442 // This could be optimized to pass in the parent docModel, since it will often be one end.
1443 // Nevertheless, we should complete the item save before we do work on the relations, especially
1444 // since a save on Create might fail, and we would not want to create relations for something
1445 // that may not be created...
1446 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate) throws Exception {
1447 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1448 PoxPayloadIn input = ctx.getInput();
1449 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1450 String itemCsid = documentModel.getName();
1452 //Updates relations part
1453 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, forUpdate);
1455 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
1456 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1458 //now we add part for relations list
1459 //ServiceContext ctx = getServiceContext();
1460 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1461 ctx.getOutput().addPart(payloadOutputPart);
1465 * Checks to see if the refName has changed, and if so,
1466 * uses utilities to find all references and update them to use the new refName.
1469 protected void handleRefNameReferencesUpdate() throws Exception {
1470 if (hasRefNameUpdate() == true) {
1471 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1472 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1473 CoreSessionInterface repoSession = this.getRepositorySession();
1475 // Update all the relationship records that referred to the old refName
1476 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1477 oldRefNameOnUpdate, newRefNameOnUpdate);
1481 protected String getRefNameUpdate() {
1482 String result = null;
1484 if (hasRefNameUpdate() == true) {
1485 result = newRefNameOnUpdate;
1486 if (logger.isDebugEnabled() == true) {
1487 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1488 newRefNameOnUpdate, oldRefNameOnUpdate));
1496 * Note: The Vocabulary document handler overrides this method.
1498 protected String getRefPropName() {
1499 return ServiceBindingUtils.AUTH_REF_PROP;