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();
220 // We need to return at least those document part(s) and corresponding payloads that were received
221 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
222 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
223 PoxPayloadIn input = ctx.getInput();
225 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
226 for (PayloadInputPart part : inputParts) {
227 String partLabel = part.getLabel();
229 ObjectPartType partMeta = partsMetaMap.get(partLabel);
230 // CSPACE-4030 - generates NPE if the part is missing.
232 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
233 if(unQObjectProperties!=null) {
234 addOutputPart(unQObjectProperties, partLabel, partMeta);
237 } catch (Throwable t){
238 logger.error("Unable to addOutputPart: " + partLabel
239 + " in serviceContextPath: "+this.getServiceContextPath()
240 + " with URI: " + this.getServiceContext().getUriInfo().getPath()
245 if (logger.isWarnEnabled() == true) {
246 logger.warn("MultipartInput part was null for document id = " +
251 // If the resource's service supports hierarchy then we need to perform a little more work
253 if (supportsHierarchy() == true) {
254 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
255 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
260 * Adds the output part.
262 * @param unQObjectProperties the un q object properties
263 * @param schema the schema
264 * @param partMeta the part meta
265 * @throws Exception the exception
266 * MediaType.APPLICATION_XML_TYPE
268 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
270 Element doc = DocumentUtils.buildDocument(partMeta, schema,
271 unQObjectProperties);
272 if (logger.isTraceEnabled() == true) {
273 logger.trace(doc.asXML());
275 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
276 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
280 * Extract paging info.
282 * @param commonsList the commons list
284 * @throws Exception the exception
286 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
288 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
290 DocumentFilter docFilter = this.getDocumentFilter();
291 long pageSize = docFilter.getPageSize();
292 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
293 // set the page size and page number
294 commonList.setPageNum(pageNum);
295 commonList.setPageSize(pageSize);
296 DocumentModelList docList = wrapDoc.getWrappedObject();
297 // Set num of items in list. this is useful to our testing framework.
298 commonList.setItemsInPage(docList.size());
299 // set the total result size
300 commonList.setTotalItems(docList.totalSize());
302 return (TL) commonList;
306 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
309 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
312 DocumentModel docModel = wrapDoc.getWrappedObject();
313 String[] schemas = docModel.getDeclaredSchemas();
314 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
315 for (String schema : schemas) {
316 ObjectPartType partMeta = partsMetaMap.get(schema);
317 if (partMeta == null) {
318 continue; // unknown part, ignore
320 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
321 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
322 addExtraCoreValues(docModel, unQObjectProperties);
324 addOutputPart(unQObjectProperties, schema, partMeta);
327 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
329 if (supportsHierarchy() == true) {
330 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
331 if (Tools.isTrue(showSiblings)) {
332 showSiblings(wrapDoc, ctx);
333 return; // actual result is returned on ctx.addOutputPart();
336 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
337 if (Tools.isTrue(showRelations)) {
338 showRelations(wrapDoc, ctx);
339 return; // actual result is returned on ctx.addOutputPart();
342 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
343 if (Tools.isTrue(showAllRelations)) {
344 showAllRelations(wrapDoc, ctx);
345 return; // actual result is returned on ctx.addOutputPart();
349 String currentUser = ctx.getUserId();
350 if (currentUser.equalsIgnoreCase(AuthNContext.ANONYMOUS_USER) == false) {
351 addAccountPermissionsPart();
355 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
357 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
360 private void addAccountPermissionsPart() throws Exception {
361 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
364 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
365 String currentServiceName = ctx.getServiceName();
366 String workflowSubResource = "/";
367 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
368 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
369 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
370 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
372 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
374 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
375 currentServiceName, workflowSubResource);
376 org.collectionspace.services.authorization.ObjectFactory objectFactory =
377 new org.collectionspace.services.authorization.ObjectFactory();
378 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
379 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
380 ctx.addOutputPart(accountPermissionPart);
386 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
389 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
391 //TODO filling extension parts should be dynamic
392 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
393 //not an ideal way of populating objects.
394 DocumentModel docModel = wrapDoc.getWrappedObject();
395 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
396 PoxPayloadIn input = ctx.getInput();
397 if (input == null || input.getParts().isEmpty()) {
398 String msg = String.format("No payload found for '%s' action.", action);
399 logger.error(msg + "Ctx=" + getServiceContext().toString());
400 throw new BadRequestException(msg);
403 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
405 //iterate over parts received and fill those parts
406 List<PayloadInputPart> inputParts = input.getParts();
407 for (PayloadInputPart part : inputParts) {
409 String partLabel = part.getLabel();
410 if (partLabel == null) {
411 String msg = "Part label is missing or empty!";
412 logger.error(msg + "Ctx=" + getServiceContext().toString());
413 throw new BadRequestException(msg);
416 //skip if the part is not in metadata or if it is a system part
417 ObjectPartType partMeta = partsMetaMap.get(partLabel);
418 if (partMeta == null || isSystemPart(partLabel)) {
421 fillPart(part, docModel, partMeta, action, ctx);
425 private boolean isSystemPart(String partLabel) {
426 boolean result = false;
428 if (partLabel != null && (partLabel.equalsIgnoreCase(COLLECTIONSPACE_CORE_SCHEMA) ||
429 partLabel.equalsIgnoreCase(ACCOUNT_PERMISSION_COMMON_PART_NAME))) {
437 * fillPart fills an XML part into given document model
438 * @param part to fill
439 * @param docModel for the given object
440 * @param partMeta metadata for the object to fill
443 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
444 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
446 //check if this is an xml part
447 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
448 Element element = part.getElementBody();
449 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
450 if (action == Action.UPDATE) {
451 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
453 docModel.setProperties(partMeta.getLabel(), objectProps);
458 * Filters out read only properties, so they cannot be set on update.
459 * TODO: add configuration support to do this generally
460 * @param objectProps the properties parsed from the update payload
461 * @param partMeta metadata for the object to fill
463 public void filterReadOnlyPropertiesForPart(
464 Map<String, Object> objectProps, ObjectPartType partMeta) {
465 // Should add in logic to filter most of the core items on update
466 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
467 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
468 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
469 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
470 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
471 // Note that the updatedAt/updatedBy fields are set internally
472 // in DocumentModelHandler.handleCoreValues().
477 * extractPart extracts an XML object from given DocumentModel
479 * @param schema of the object to extract
480 * @param partMeta metadata for the object to extract
483 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
485 return extractPart(docModel, schema, (Map<String, Object>)null);
489 * extractPart extracts an XML object from given DocumentModel
491 * @param schema of the object to extract
492 * @param partMeta metadata for the object to extract
496 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
498 return extractPart(docModel, schema, partMeta, null);
502 * extractPart extracts an XML object from given DocumentModel
504 * @param schema of the object to extract
505 * @param partMeta metadata for the object to extract
508 protected Map<String, Object> extractPart(
509 DocumentModel docModel,
511 Map<String, Object> addToMap)
513 Map<String, Object> result = null;
515 Map<String, Object> objectProps = docModel.getProperties(schema);
516 if (objectProps != null) {
517 //unqualify properties before sending the doc over the wire (to save bandwidh)
518 //FIXME: is there a better way to avoid duplication of a Map/Collection?
519 Map<String, Object> unQObjectProperties =
520 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
521 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
522 for (Entry<String, Object> entry : qualifiedEntries) {
523 String unqProp = getUnQProperty(entry.getKey());
524 unQObjectProperties.put(unqProp, entry.getValue());
526 result = unQObjectProperties;
533 * extractPart extracts an XML object from given DocumentModel
535 * @param schema of the object to extract
536 * @param partMeta metadata for the object to extract
540 protected Map<String, Object> extractPart(
541 DocumentModel docModel, String schema, ObjectPartType partMeta,
542 Map<String, Object> addToMap)
544 Map<String, Object> result = null;
546 result = this.extractPart(docModel, schema, addToMap);
552 public String getStringPropertyFromDoc(
555 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
556 RepositoryInstance repoSession = null;
557 boolean releaseRepoSession = false;
558 String returnValue = null;
561 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
562 repoSession = this.getRepositorySession();
563 if (repoSession == null) {
564 repoSession = repoClient.getRepositorySession();
565 releaseRepoSession = true;
569 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
570 DocumentModel docModel = wrapper.getWrappedObject();
571 returnValue = (String) docModel.getPropertyValue(propertyXPath);
572 } catch (PropertyException pe) {
574 } catch (DocumentException de) {
576 } catch (Exception e) {
577 if (logger.isDebugEnabled()) {
578 logger.debug("Caught exception ", e);
580 throw new DocumentException(e);
582 if (releaseRepoSession && repoSession != null) {
583 repoClient.releaseRepositorySession(repoSession);
586 } catch (Exception e) {
587 if (logger.isDebugEnabled()) {
588 logger.debug("Caught exception ", e);
590 throw new DocumentException(e);
594 if (logger.isWarnEnabled() == true) {
595 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
604 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
607 public AuthorityRefList getAuthorityRefs(
609 List<AuthRefConfigInfo> authRefConfigInfoList) throws PropertyException, Exception {
611 AuthorityRefList authRefList = new AuthorityRefList();
612 AbstractCommonList commonList = (AbstractCommonList) authRefList;
614 DocumentFilter docFilter = this.getDocumentFilter();
615 long pageSize = docFilter.getPageSize();
616 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
617 // set the page size and page number
618 commonList.setPageNum(pageNum);
619 commonList.setPageSize(pageSize);
621 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
624 int iFirstToUse = (int)(pageSize*pageNum);
625 int nFoundInPage = 0;
628 ArrayList<RefNameServiceUtils.AuthRefInfo> foundReferences
629 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
631 boolean releaseRepoSession = false;
632 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
633 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
634 CoreSessionInterface repoSession = this.getRepositorySession();
635 if (repoSession == null) {
636 repoSession = repoClient.getRepositorySession(ctx);
637 releaseRepoSession = true;
638 this.setRepositorySession(repoSession); // we (the doc handler) should keep track of this repository session in case we need it
642 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
643 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefConfigInfoList, null, foundReferences);
644 // Slightly goofy pagination support - how many refs do we expect from one object?
645 for(RefNameServiceUtils.AuthRefInfo ari:foundReferences) {
646 if ((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
647 if(appendToAuthRefsList(ari, list)) {
656 if (releaseRepoSession == true) {
657 repoClient.releaseRepositorySession(ctx, repoSession);
661 // Set num of items in list. this is useful to our testing framework.
662 commonList.setItemsInPage(nFoundInPage);
663 // set the total result size
664 commonList.setTotalItems(nFoundTotal);
666 } catch (PropertyException pe) {
667 String msg = "Attempted to retrieve value for invalid or missing authority field. "
668 + "Check authority field properties in tenant bindings.";
669 logger.warn(msg, pe);
671 } catch (Exception e) {
672 if (logger.isDebugEnabled()) {
673 logger.debug("Caught exception in getAuthorityRefs", e);
675 Response response = Response.status(
676 Response.Status.INTERNAL_SERVER_ERROR).entity(
677 "Failed to retrieve authority references").type(
678 "text/plain").build();
679 throw new CSWebApplicationException(e, response);
685 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
686 List<AuthorityRefList.AuthorityRefItem> list)
688 String fieldName = ari.getQualifiedDisplayName();
690 String refNameValue = (String)ari.getProperty().getValue();
691 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
692 if(item!=null) { // ignore garbage values.
696 } catch(PropertyException pe) {
697 String msg = "PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage();
698 if (logger.isDebugEnabled()) {
699 logger.debug(msg, pe);
708 * Fill in all the values to be returned in the authrefs payload for this item.
710 * @param authRefFieldName
714 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
716 // Find the CSID for the authority item
720 DocumentModel docModel = NuxeoUtils.getDocModelForRefName(getServiceContext(), refName, getServiceContext().getResourceMap());
721 csid = NuxeoUtils.getCsid(docModel);
722 } catch (Exception e1) {
723 String msg = String.format("Could not find CSID for authority reference with refname = %s.", refName);
724 if (logger.isDebugEnabled()) {
725 logger.debug(msg, e1);
731 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
733 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
734 if (Tools.isEmpty(csid) == false) {
735 ilistItem.setCsid(csid);
737 ilistItem.setRefName(refName);
738 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
739 ilistItem.setItemDisplayName(termInfo.displayName);
740 ilistItem.setSourceField(authRefFieldName);
741 ilistItem.setUri(termInfo.getRelativeUri());
742 } catch (Exception e) {
744 String msg = String.format("Trouble parsing refName from value: %s in field: %s. Error message: %s.",
745 refName, authRefFieldName, e.getLocalizedMessage());
746 if (logger.isDebugEnabled()) {
747 logger.debug(msg, e);
757 * Returns the primary value from a list of values.
759 * Assumes that the first value is the primary value.
760 * This assumption may change when and if the primary value
761 * is identified explicitly.
763 * @param values a list of values.
764 * @param propertyName the name of a property through
765 * which the value can be extracted.
766 * @return the primary value.
767 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
768 String primaryValue = "";
769 if (values == null || values.size() == 0) {
772 Object value = values.get(0);
773 if (value instanceof String) {
775 primaryValue = (String) value;
777 // Multivalue group of fields
778 } else if (value instanceof Map) {
780 Map map = (Map) value;
781 if (map.values().size() > 0) {
782 if (map.get(propertyName) != null) {
783 primaryValue = (String) map.get(propertyName);
788 logger.warn("Unexpected type for property " + propertyName
789 + " in multivalue list: not String or Map.");
796 * Gets a simple property from the document.
798 * For completeness, as this duplicates DocumentModel method.
800 * @param docModel The document model to get info from
801 * @param schema The name of the schema (part)
802 * @param propertyName The simple scalar property type
803 * @return property value as String
805 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
806 String xpath = "/"+schema+":"+propName;
808 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
809 } catch(PropertyException pe) {
810 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
811 +pe.getLocalizedMessage());
812 } catch(ClassCastException cce) {
813 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
814 +cce.getLocalizedMessage());
815 } catch(Exception e) {
816 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
817 +e.getLocalizedMessage());
822 * Gets first of a repeating list of scalar values, as a String, from the document.
824 * @param docModel The document model to get info from
825 * @param schema The name of the schema (part)
826 * @param listName The name of the scalar list property
827 * @return first value in list, as a String, or empty string if the list is empty
829 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
830 String schema, String listName) {
831 String xpath = "/" + schema + ":" + listName + "/[0]";
833 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
834 } catch (PropertyException pe) {
835 throw new RuntimeException("Problem retrieving property {" + xpath
836 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
837 } catch (IndexOutOfBoundsException ioobe) {
838 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
839 return ""; // gracefully handle missing elements
840 } catch (ClassCastException cce) {
841 throw new RuntimeException("Problem retrieving property {" + xpath
842 + "} as String. Not a repeating String property?"
843 + cce.getLocalizedMessage());
844 } catch (Exception e) {
845 throw new RuntimeException("Unknown problem retrieving property {"
846 + xpath + "}." + e.getLocalizedMessage());
851 * Gets first of a repeating list of scalar values, as a String, from the document.
853 * @param docModel The document model to get info from
854 * @param schema The name of the schema (part)
855 * @param listName The name of the scalar list property
856 * @return first value in list, as a String, or empty string if the list is empty
858 protected String getStringValueInPrimaryRepeatingComplexProperty(
859 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
860 String result = null;
862 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
864 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
865 } catch(PropertyException pe) {
866 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
867 +pe.getLocalizedMessage());
868 } catch(IndexOutOfBoundsException ioobe) {
869 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
870 result = ""; // gracefully handle missing elements
871 } catch(ClassCastException cce) {
872 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
873 +cce.getLocalizedMessage());
874 } catch(NullPointerException npe) {
875 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
876 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
878 } catch(Exception e) {
879 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
880 +e.getLocalizedMessage());
887 * Gets XPath value from schema. Note that only "/" and "[n]" are
888 * supported for xpath. Can omit grouping elements for repeating complex types,
889 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
890 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
891 * If there are no entries for a list of scalars or for a list of complex types,
892 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
893 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
894 * that many elements in the list.
895 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
897 * @param docModel The document model to get info from
898 * @param schema The name of the schema (part)
899 * @param xpath The XPath expression (without schema prefix)
900 * @return value the indicated property value as a String
901 * @throws DocumentException
903 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
904 String schema, ListResultField field) throws DocumentException {
905 Object result = null;
907 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
912 protected String getStringValue(DocumentModel docModel,
913 String schema, ListResultField field) throws DocumentException {
914 String result = null;
916 Object value = getListResultValue(docModel, schema, field);
917 if (value != null && value instanceof String) {
918 String strValue = (String) value;
919 if (strValue.trim().isEmpty() == false) {
927 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
931 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
933 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
935 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
937 sb.append(item.getPredicate());
939 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
943 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
944 StringBuilder sb = new StringBuilder();
946 if (list.size() > 0) {
947 sb.append("=========== " + label + " ==========" + CR);
949 for (RelationsCommonList.RelationListItem item : list) {
950 itemToString(sb, "== ", item);
953 return sb.toString();
956 /** @return null on parent not found
958 protected String getParentCSID(String thisCSID) throws Exception {
959 String parentCSID = null;
961 String predicate = RelationshipType.HAS_BROADER.value();
962 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
963 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
964 if (parentList != null) {
965 if (parentList.size() == 0) {
968 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
969 parentCSID = relationListItem.getObjectCsid();
972 } catch (Exception e) {
973 logger.error("Could not find parent for this: " + thisCSID, e);
978 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
979 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
983 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
984 MultipartServiceContext ctx) throws Exception {
985 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
986 String parentCSID = getParentCSID(thisCSID);
987 if (parentCSID == null) {
988 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
992 String predicate = RelationshipType.HAS_BROADER.value();
993 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
994 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
996 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
999 RelationsCommonList.RelationListItem item = null;
1000 for (RelationsCommonList.RelationListItem sibling : siblingList) {
1001 if (thisCSID.equals(sibling.getSubjectCsid())) {
1002 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.
1005 //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.
1006 for (RelationsCommonList.RelationListItem self : toRemoveList) {
1007 removeFromList(siblingList, self);
1010 long siblingSize = siblingList.size();
1011 siblingListOuter.setTotalItems(siblingSize);
1012 siblingListOuter.setItemsInPage(siblingSize);
1013 if(logger.isTraceEnabled()) {
1014 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
1015 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1018 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
1019 ctx.addOutputPart(relationsPart);
1022 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
1023 MultipartServiceContext ctx) throws Exception {
1024 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1026 String predicate = RelationshipType.HAS_BROADER.value();
1027 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1028 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1030 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
1031 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
1033 if(logger.isTraceEnabled()) {
1034 String dump = dumpLists(thisCSID, parentList, childrenList, null);
1035 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1038 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
1039 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
1040 //Not optimal, but that's the current design spec.
1042 for (RelationsCommonList.RelationListItem parent : parentList) {
1043 childrenList.add(parent);
1046 long childrenSize = childrenList.size();
1047 childrenListOuter.setTotalItems(childrenSize);
1048 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1050 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1051 ctx.addOutputPart(relationsPart);
1054 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1055 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1057 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1058 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1060 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1061 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1063 if(logger.isTraceEnabled()) {
1064 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1065 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1068 subjectList.addAll(objectList);
1070 //now subjectList actually has records BOTH where thisCSID is subject and object.
1071 long relatedSize = subjectList.size();
1072 subjectListOuter.setTotalItems(relatedSize);
1073 subjectListOuter.setItemsInPage(relatedSize);
1075 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1076 ctx.addOutputPart(relationsPart);
1079 private String dumpLists(String itemCSID,
1080 List<RelationsCommonList.RelationListItem> parentList,
1081 List<RelationsCommonList.RelationListItem> childList,
1082 List<RelationsCommonList.RelationListItem> actionList) {
1083 StringBuilder sb = new StringBuilder();
1084 sb.append("itemCSID: " + itemCSID + CR);
1085 if(parentList!=null) {
1086 sb.append(dumpList(parentList, "parentList"));
1088 if(childList!=null) {
1089 sb.append(dumpList(childList, "childList"));
1091 if(actionList!=null) {
1092 sb.append(dumpList(actionList, "actionList"));
1094 return sb.toString();
1097 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1098 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1099 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1100 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1101 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1102 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1103 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1105 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1106 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1107 return relationsCommonList;
1109 //============================= END TODO refactor ==========================
1111 // this method calls the RelationResource to have it create the relations and persist them.
1112 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1113 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1114 for (RelationsCommonList.RelationListItem item : inboundList) {
1115 RelationsCommon rc = new RelationsCommon();
1116 //rc.setCsid(item.getCsid());
1117 //todo: assignTo(item, rc);
1118 RelationsDocListItem itemSubject = item.getSubject();
1119 RelationsDocListItem itemObject = item.getObject();
1121 // Set at least one of CSID and refName for Subject and Object
1122 // Either value might be null for for each of Subject and Object
1123 String subjectCsid = itemSubject.getCsid();
1124 rc.setSubjectCsid(subjectCsid);
1126 String objCsid = itemObject.getCsid();
1127 rc.setObjectCsid(objCsid);
1129 rc.setSubjectRefName(itemSubject.getRefName());
1130 rc.setObjectRefName(itemObject.getRefName());
1132 rc.setRelationshipType(item.getPredicate());
1133 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1134 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1135 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1137 // This is superfluous, since it will be fetched by the Relations Create logic.
1138 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1139 rc.setObjectDocumentType(itemObject.getDocumentType());
1141 // This is superfluous, since it will be fetched by the Relations Create logic.
1142 rc.setSubjectUri(itemSubject.getUri());
1143 rc.setObjectUri(itemObject.getUri());
1144 // May not have the info here. Only really require CSID or refName.
1145 // Rest is handled in the Relation create mechanism
1146 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1148 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1149 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1150 payloadOut.addPart(outputPart);
1151 RelationResource relationResource = new RelationResource();
1152 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1153 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1157 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1158 // But item1 must not be sparse
1159 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1160 if (item1 == null || item2 == null) {
1163 RelationsDocListItem subj1 = item1.getSubject();
1164 RelationsDocListItem subj2 = item2.getSubject();
1165 RelationsDocListItem obj1 = item1.getObject();
1166 RelationsDocListItem obj2 = item2.getObject();
1168 String subj1Csid = subj1.getCsid();
1169 String subj2Csid = subj2.getCsid();
1170 String subj1RefName = subj1.getRefName();
1171 String subj2RefName = subj2.getRefName();
1173 String obj1Csid = obj1.getCsid();
1174 String obj2Csid = obj2.getCsid();
1175 String obj1RefName = obj1.getRefName();
1176 String obj2RefName = obj2.getRefName();
1178 String item1Metatype = item1.getRelationshipMetaType();
1179 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1181 String item2Metatype = item2.getRelationshipMetaType();
1182 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1184 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1185 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1186 // predicate is proper, but still allow relationshipType
1187 && (item1.getPredicate().equals(item2.getPredicate())
1188 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1189 // Allow missing docTypes, so long as they do not conflict
1190 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1191 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1192 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1196 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1197 // But the list items must not be sparse
1198 private RelationsCommonList.RelationListItem findInList(
1199 List<RelationsCommonList.RelationListItem> list,
1200 RelationsCommonList.RelationListItem item) {
1201 RelationsCommonList.RelationListItem foundItem = null;
1202 for (RelationsCommonList.RelationListItem listItem : list) {
1203 if (itemsEqual(listItem, item)) { //equals must be defined, else
1204 foundItem = listItem;
1211 /** updateRelations strategy:
1214 go through inboundList, remove anything from childList that matches from childList
1215 go through inboundList, remove anything from parentList that matches from parentList
1216 go through parentList, delete all remaining
1217 go through childList, delete all remaining
1218 go through actionList, add all remaining.
1219 check for duplicate children
1220 check for more than one parent.
1222 inboundList parentList childList actionList
1223 ---------------- --------------- ---------------- ----------------
1224 child-a parent-c child-a child-b
1225 child-b parent-d child-c
1230 private RelationsCommonList updateRelations(
1231 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate)
1233 if (logger.isTraceEnabled()) {
1234 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1236 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1238 return null; //nothing to do--they didn't send a list of relations.
1240 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1241 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1242 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1243 List<RelationsCommonList.RelationListItem> childList = null;
1244 List<RelationsCommonList.RelationListItem> parentList = null;
1245 DocumentModel docModel = wrapDoc.getWrappedObject();
1246 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1247 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1249 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1250 //Do magic replacement of ${itemCSID} and fix URI's.
1251 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1253 final String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1254 UriInfo uriInfo = ctx.getUriInfo();
1255 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1258 //Run getList() once as sent to get childListOuter:
1259 String predicate = RelationshipType.HAS_BROADER.value();
1260 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1261 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1262 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1263 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1264 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1266 RelationResource relationResource = new RelationResource();
1267 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1269 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1270 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1271 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1272 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1273 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1276 childList = childListOuter.getRelationListItem();
1277 parentList = parentListOuter.getRelationListItem();
1279 if (parentList.size() > 1) {
1280 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1283 if (logger.isTraceEnabled()) {
1284 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1288 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1289 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1290 // and so the CSID for those may be null
1291 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1292 // Look for parents and children
1293 if(itemCSID.equals(inboundItem.getObject().getCsid())
1294 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1295 //then this is an item that says we have a child. That child is inboundItem
1296 RelationsCommonList.RelationListItem childItem =
1297 (childList == null) ? null : findInList(childList, inboundItem);
1298 if (childItem != null) {
1299 if (logger.isTraceEnabled()) {
1300 StringBuilder sb = new StringBuilder();
1301 itemToString(sb, "== Child: ", childItem);
1302 logger.trace("Found inboundChild in current child list: " + sb.toString());
1304 removeFromList(childList, childItem); //exists, just take it off delete list
1306 if (logger.isTraceEnabled()) {
1307 StringBuilder sb = new StringBuilder();
1308 itemToString(sb, "== Child: ", inboundItem);
1309 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1311 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1312 String newChildCsid = inboundItem.getSubject().getCsid();
1313 if(newChildCsid == null) {
1314 String newChildRefName = inboundItem.getSubject().getRefName();
1315 if (newChildRefName == null) {
1316 throw new RuntimeException("Child with no CSID or refName!");
1318 if (logger.isTraceEnabled()) {
1319 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1321 DocumentModel newChildDocModel =
1322 NuxeoBasedResource.getDocModelForRefName(getServiceContext(),
1323 newChildRefName, getServiceContext().getResourceMap());
1324 newChildCsid = getCsid(newChildDocModel);
1326 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1329 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1330 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1331 //then this is an item that says we have a parent. inboundItem is that parent.
1332 RelationsCommonList.RelationListItem parentItem =
1333 (parentList == null) ? null : findInList(parentList, inboundItem);
1334 if (parentItem != null) {
1335 removeFromList(parentList, parentItem); //exists, just take it off delete list
1337 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1340 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1343 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1346 if (logger.isTraceEnabled()) {
1347 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1348 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1351 if (logger.isTraceEnabled()) {
1352 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1353 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1355 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1356 deleteRelations(childList, ctx, "childList");
1358 if (logger.isTraceEnabled()) {
1359 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1360 + actionList.size() + " new parents and children.");
1362 createRelations(actionList, ctx);
1363 if (logger.isTraceEnabled()) {
1364 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1366 // We return all elements on the inbound list, since we have just worked to make them exist in the system
1367 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1368 return relationsCommonListBody;
1371 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1372 * and sets URI correctly for related items.
1373 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1375 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1376 List<RelationsCommonList.RelationListItem> inboundList,
1377 DocumentModel docModel,
1378 String itemCSID) throws Exception {
1379 String thisURI = this.getUri(docModel);
1380 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1381 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1382 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1383 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1384 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1386 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1387 inboundItem.setObjectCsid(itemCSID);
1388 inboundItemObject.setCsid(itemCSID);
1389 //inboundItemObject.setUri(getUri(docModel));
1392 String objectCsid = inboundItemObject.getCsid();
1393 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1394 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1395 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1396 inboundItemObject.setUri(uri); //CSPACE-4037
1399 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1401 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1402 inboundItem.setSubjectCsid(itemCSID);
1403 inboundItemSubject.setCsid(itemCSID);
1404 //inboundItemSubject.setUri(getUri(docModel));
1407 String subjectCsid = inboundItemSubject.getCsid();
1408 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1409 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1410 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1411 inboundItemSubject.setUri(uri); //CSPACE-4037
1414 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1419 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1420 MultivaluedMap<String, String> queryParams, String childCSID) {
1421 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1422 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1423 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1424 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1426 RelationResource relationResource = new RelationResource();
1427 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1428 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1429 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1430 deleteRelations(parentList, ctx, "parentList-delete");
1433 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1434 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1437 for (RelationsCommonList.RelationListItem item : list) {
1438 RelationResource relationResource = new RelationResource();
1439 if(logger.isTraceEnabled()) {
1440 StringBuilder sb = new StringBuilder();
1441 itemToString(sb, "==== TO DELETE: ", item);
1442 logger.trace(sb.toString());
1444 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1445 if (logger.isDebugEnabled()) {
1446 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1449 } catch (Throwable t) {
1450 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1455 // Note that we must do this after we have completed the Update, so that the repository has the
1456 // info for the item itself. The relations code must call into the repo to get info for each end.
1457 // This could be optimized to pass in the parent docModel, since it will often be one end.
1458 // Nevertheless, we should complete the item save before we do work on the relations, especially
1459 // since a save on Create might fail, and we would not want to create relations for something
1460 // that may not be created...
1461 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate) throws Exception {
1462 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1463 PoxPayloadIn input = ctx.getInput();
1464 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1465 String itemCsid = documentModel.getName();
1467 //Updates relations part
1468 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, forUpdate);
1470 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
1471 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1473 //now we add part for relations list
1474 //ServiceContext ctx = getServiceContext();
1475 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1476 ctx.getOutput().addPart(payloadOutputPart);
1480 * Checks to see if the refName has changed, and if so,
1481 * uses utilities to find all references and update them to use the new refName.
1484 protected void handleRefNameReferencesUpdate() throws Exception {
1485 if (hasRefNameUpdate() == true) {
1486 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1487 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1488 CoreSessionInterface repoSession = this.getRepositorySession();
1490 // Update all the relationship records that referred to the old refName
1491 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1492 oldRefNameOnUpdate, newRefNameOnUpdate);
1496 protected String getRefNameUpdate() {
1497 String result = null;
1499 if (hasRefNameUpdate() == true) {
1500 result = newRefNameOnUpdate;
1501 if (logger.isDebugEnabled() == true) {
1502 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1503 newRefNameOnUpdate, oldRefNameOnUpdate));
1511 * Note: The Vocabulary document handler overrides this method.
1513 protected String getRefPropName() {
1514 return ServiceBindingUtils.AUTH_REF_PROP;