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 boolean werePartsFilled = false;
392 List<PayloadInputPart> inputParts = input.getParts();
393 for (PayloadInputPart part : inputParts) {
395 String partLabel = part.getLabel();
396 if (partLabel == null) {
397 String msg = "Part label is missing or empty!";
398 logger.error(msg + "Ctx=" + getServiceContext().toString());
399 throw new BadRequestException(msg);
402 //skip if the part is not in metadata or if it is a system part
403 ObjectPartType partMeta = partsMetaMap.get(partLabel);
404 if (partMeta == null || isSystemPart(partLabel)) {
407 fillPart(part, docModel, partMeta, action, ctx);
408 werePartsFilled = true;
411 if (werePartsFilled == false) {
412 String msg = String.format("%s request failed because there were no XML payload parts in the request.",
415 throw new BadRequestException(msg);
419 private boolean isSystemPart(String partLabel) {
420 boolean result = false;
422 if (partLabel != null && (partLabel.equalsIgnoreCase(COLLECTIONSPACE_CORE_SCHEMA) ||
423 partLabel.equalsIgnoreCase(ACCOUNT_PERMISSION_COMMON_PART_NAME))) {
431 * fillPart fills an XML part into given document model
432 * @param part to fill
433 * @param docModel for the given object
434 * @param partMeta metadata for the object to fill
437 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
438 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
440 //check if this is an xml part
441 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
442 Element element = part.getElementBody();
443 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
444 if (action == Action.UPDATE) {
445 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
447 docModel.setProperties(partMeta.getLabel(), objectProps);
452 * Filters out read only properties, so they cannot be set on update.
453 * TODO: add configuration support to do this generally
454 * @param objectProps the properties parsed from the update payload
455 * @param partMeta metadata for the object to fill
457 public void filterReadOnlyPropertiesForPart(
458 Map<String, Object> objectProps, ObjectPartType partMeta) {
459 // Should add in logic to filter most of the core items on update
460 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
461 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
462 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
463 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
464 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
465 // Note that the updatedAt/updatedBy fields are set internally
466 // in DocumentModelHandler.handleCoreValues().
471 * extractPart extracts an XML object from given DocumentModel
473 * @param schema of the object to extract
474 * @param partMeta metadata for the object to extract
477 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
479 return extractPart(docModel, schema, (Map<String, Object>)null);
483 * extractPart extracts an XML object from given DocumentModel
485 * @param schema of the object to extract
486 * @param partMeta metadata for the object to extract
490 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
492 return extractPart(docModel, schema, partMeta, null);
496 * extractPart extracts an XML object from given DocumentModel
498 * @param schema of the object to extract
499 * @param partMeta metadata for the object to extract
502 protected Map<String, Object> extractPart(
503 DocumentModel docModel,
505 Map<String, Object> addToMap)
507 Map<String, Object> result = null;
509 Map<String, Object> objectProps = docModel.getProperties(schema);
510 if (objectProps != null) {
511 //unqualify properties before sending the doc over the wire (to save bandwidh)
512 //FIXME: is there a better way to avoid duplication of a Map/Collection?
513 Map<String, Object> unQObjectProperties =
514 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
515 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
516 for (Entry<String, Object> entry : qualifiedEntries) {
517 String unqProp = getUnQProperty(entry.getKey());
518 unQObjectProperties.put(unqProp, entry.getValue());
520 result = unQObjectProperties;
527 * extractPart extracts an XML object from given DocumentModel
529 * @param schema of the object to extract
530 * @param partMeta metadata for the object to extract
534 protected Map<String, Object> extractPart(
535 DocumentModel docModel, String schema, ObjectPartType partMeta,
536 Map<String, Object> addToMap)
538 Map<String, Object> result = null;
540 result = this.extractPart(docModel, schema, addToMap);
546 public String getStringPropertyFromDoc(
549 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
550 RepositoryInstance repoSession = null;
551 boolean releaseRepoSession = false;
552 String returnValue = null;
555 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
556 repoSession = this.getRepositorySession();
557 if (repoSession == null) {
558 repoSession = repoClient.getRepositorySession();
559 releaseRepoSession = true;
563 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
564 DocumentModel docModel = wrapper.getWrappedObject();
565 returnValue = (String) docModel.getPropertyValue(propertyXPath);
566 } catch (PropertyException pe) {
568 } catch (DocumentException de) {
570 } catch (Exception e) {
571 if (logger.isDebugEnabled()) {
572 logger.debug("Caught exception ", e);
574 throw new DocumentException(e);
576 if (releaseRepoSession && repoSession != null) {
577 repoClient.releaseRepositorySession(repoSession);
580 } catch (Exception e) {
581 if (logger.isDebugEnabled()) {
582 logger.debug("Caught exception ", e);
584 throw new DocumentException(e);
588 if (logger.isWarnEnabled() == true) {
589 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
598 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
601 public AuthorityRefList getAuthorityRefs(
603 List<AuthRefConfigInfo> authRefConfigInfoList) throws PropertyException, Exception {
605 AuthorityRefList authRefList = new AuthorityRefList();
606 AbstractCommonList commonList = (AbstractCommonList) authRefList;
608 DocumentFilter docFilter = this.getDocumentFilter();
609 long pageSize = docFilter.getPageSize();
610 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
611 // set the page size and page number
612 commonList.setPageNum(pageNum);
613 commonList.setPageSize(pageSize);
615 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
618 int iFirstToUse = (int)(pageSize*pageNum);
619 int nFoundInPage = 0;
622 ArrayList<RefNameServiceUtils.AuthRefInfo> foundReferences
623 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
625 boolean releaseRepoSession = false;
626 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
627 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
628 CoreSessionInterface repoSession = this.getRepositorySession();
629 if (repoSession == null) {
630 repoSession = repoClient.getRepositorySession(ctx);
631 releaseRepoSession = true;
632 this.setRepositorySession(repoSession); // we (the doc handler) should keep track of this repository session in case we need it
636 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
637 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefConfigInfoList, null, foundReferences);
638 // Slightly goofy pagination support - how many refs do we expect from one object?
639 for(RefNameServiceUtils.AuthRefInfo ari:foundReferences) {
640 if ((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
641 if(appendToAuthRefsList(ari, list)) {
650 if (releaseRepoSession == true) {
651 repoClient.releaseRepositorySession(ctx, repoSession);
655 // Set num of items in list. this is useful to our testing framework.
656 commonList.setItemsInPage(nFoundInPage);
657 // set the total result size
658 commonList.setTotalItems(nFoundTotal);
660 } catch (PropertyException pe) {
661 String msg = "Attempted to retrieve value for invalid or missing authority field. "
662 + "Check authority field properties in tenant bindings.";
663 logger.warn(msg, pe);
665 } catch (Exception e) {
666 if (logger.isDebugEnabled()) {
667 logger.debug("Caught exception in getAuthorityRefs", e);
669 Response response = Response.status(
670 Response.Status.INTERNAL_SERVER_ERROR).entity(
671 "Failed to retrieve authority references").type(
672 "text/plain").build();
673 throw new CSWebApplicationException(e, response);
679 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
680 List<AuthorityRefList.AuthorityRefItem> list)
682 String fieldName = ari.getQualifiedDisplayName();
684 String refNameValue = (String)ari.getProperty().getValue();
685 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
686 if(item!=null) { // ignore garbage values.
690 } catch(PropertyException pe) {
691 String msg = "PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage();
692 if (logger.isDebugEnabled()) {
693 logger.debug(msg, pe);
702 * Fill in all the values to be returned in the authrefs payload for this item.
704 * @param authRefFieldName
708 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
710 // Find the CSID for the authority item
714 DocumentModel docModel = NuxeoUtils.getDocModelForRefName(getServiceContext(), refName, getServiceContext().getResourceMap());
715 csid = NuxeoUtils.getCsid(docModel);
716 } catch (Exception e1) {
717 String msg = String.format("Could not find CSID for authority reference with refname = %s.", refName);
718 if (logger.isDebugEnabled()) {
719 logger.debug(msg, e1);
725 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
727 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
728 if (Tools.isEmpty(csid) == false) {
729 ilistItem.setCsid(csid);
731 ilistItem.setRefName(refName);
732 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
733 ilistItem.setItemDisplayName(termInfo.displayName);
734 ilistItem.setSourceField(authRefFieldName);
735 ilistItem.setUri(termInfo.getRelativeUri());
736 } catch (Exception e) {
738 String msg = String.format("Trouble parsing refName from value: %s in field: %s. Error message: %s.",
739 refName, authRefFieldName, e.getLocalizedMessage());
740 if (logger.isDebugEnabled()) {
741 logger.debug(msg, e);
751 * Returns the primary value from a list of values.
753 * Assumes that the first value is the primary value.
754 * This assumption may change when and if the primary value
755 * is identified explicitly.
757 * @param values a list of values.
758 * @param propertyName the name of a property through
759 * which the value can be extracted.
760 * @return the primary value.
761 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
762 String primaryValue = "";
763 if (values == null || values.size() == 0) {
766 Object value = values.get(0);
767 if (value instanceof String) {
769 primaryValue = (String) value;
771 // Multivalue group of fields
772 } else if (value instanceof Map) {
774 Map map = (Map) value;
775 if (map.values().size() > 0) {
776 if (map.get(propertyName) != null) {
777 primaryValue = (String) map.get(propertyName);
782 logger.warn("Unexpected type for property " + propertyName
783 + " in multivalue list: not String or Map.");
790 * Gets a simple property from the document.
792 * For completeness, as this duplicates DocumentModel method.
794 * @param docModel The document model to get info from
795 * @param schema The name of the schema (part)
796 * @param propertyName The simple scalar property type
797 * @return property value as String
799 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
800 String xpath = "/"+schema+":"+propName;
802 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
803 } catch(PropertyException pe) {
804 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
805 +pe.getLocalizedMessage());
806 } catch(ClassCastException cce) {
807 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
808 +cce.getLocalizedMessage());
809 } catch(Exception e) {
810 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
811 +e.getLocalizedMessage());
816 * Gets first of a repeating list of scalar values, as a String, from the document.
818 * @param docModel The document model to get info from
819 * @param schema The name of the schema (part)
820 * @param listName The name of the scalar list property
821 * @return first value in list, as a String, or empty string if the list is empty
823 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
824 String schema, String listName) {
825 String xpath = "/" + schema + ":" + listName + "/[0]";
827 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
828 } catch (PropertyException pe) {
829 throw new RuntimeException("Problem retrieving property {" + xpath
830 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
831 } catch (IndexOutOfBoundsException ioobe) {
832 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
833 return ""; // gracefully handle missing elements
834 } catch (ClassCastException cce) {
835 throw new RuntimeException("Problem retrieving property {" + xpath
836 + "} as String. Not a repeating String property?"
837 + cce.getLocalizedMessage());
838 } catch (Exception e) {
839 throw new RuntimeException("Unknown problem retrieving property {"
840 + xpath + "}." + e.getLocalizedMessage());
845 * Gets first of a repeating list of scalar values, as a String, from the document.
847 * @param docModel The document model to get info from
848 * @param schema The name of the schema (part)
849 * @param listName The name of the scalar list property
850 * @return first value in list, as a String, or empty string if the list is empty
852 protected String getStringValueInPrimaryRepeatingComplexProperty(
853 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
854 String result = null;
856 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
858 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
859 } catch(PropertyException pe) {
860 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
861 +pe.getLocalizedMessage());
862 } catch(IndexOutOfBoundsException ioobe) {
863 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
864 result = ""; // gracefully handle missing elements
865 } catch(ClassCastException cce) {
866 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
867 +cce.getLocalizedMessage());
868 } catch(NullPointerException npe) {
869 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
870 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
872 } catch(Exception e) {
873 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
874 +e.getLocalizedMessage());
881 * Gets XPath value from schema. Note that only "/" and "[n]" are
882 * supported for xpath. Can omit grouping elements for repeating complex types,
883 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
884 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
885 * If there are no entries for a list of scalars or for a list of complex types,
886 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
887 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
888 * that many elements in the list.
889 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
891 * @param docModel The document model to get info from
892 * @param schema The name of the schema (part)
893 * @param xpath The XPath expression (without schema prefix)
894 * @return value the indicated property value as a String
895 * @throws DocumentException
897 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
898 String schema, ListResultField field) throws DocumentException {
899 Object result = null;
901 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
906 protected String getStringValue(DocumentModel docModel,
907 String schema, ListResultField field) throws DocumentException {
908 String result = null;
910 Object value = getListResultValue(docModel, schema, field);
911 if (value != null && value instanceof String) {
912 String strValue = (String) value;
913 if (strValue.trim().isEmpty() == false) {
921 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
925 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
927 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
929 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
931 sb.append(item.getPredicate());
933 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
937 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
938 StringBuilder sb = new StringBuilder();
940 if (list.size() > 0) {
941 sb.append("=========== " + label + " ==========" + CR);
943 for (RelationsCommonList.RelationListItem item : list) {
944 itemToString(sb, "== ", item);
947 return sb.toString();
950 /** @return null on parent not found
952 protected String getParentCSID(String thisCSID) throws Exception {
953 String parentCSID = null;
955 String predicate = RelationshipType.HAS_BROADER.value();
956 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
957 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
958 if (parentList != null) {
959 if (parentList.size() == 0) {
962 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
963 parentCSID = relationListItem.getObjectCsid();
966 } catch (Exception e) {
967 logger.error("Could not find parent for this: " + thisCSID, e);
972 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
973 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
977 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
978 MultipartServiceContext ctx) throws Exception {
979 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
980 String parentCSID = getParentCSID(thisCSID);
981 if (parentCSID == null) {
982 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
986 String predicate = RelationshipType.HAS_BROADER.value();
987 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
988 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
990 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
993 RelationsCommonList.RelationListItem item = null;
994 for (RelationsCommonList.RelationListItem sibling : siblingList) {
995 if (thisCSID.equals(sibling.getSubjectCsid())) {
996 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.
999 //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.
1000 for (RelationsCommonList.RelationListItem self : toRemoveList) {
1001 removeFromList(siblingList, self);
1004 long siblingSize = siblingList.size();
1005 siblingListOuter.setTotalItems(siblingSize);
1006 siblingListOuter.setItemsInPage(siblingSize);
1007 if(logger.isTraceEnabled()) {
1008 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
1009 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1012 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
1013 ctx.addOutputPart(relationsPart);
1016 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
1017 MultipartServiceContext ctx) throws Exception {
1018 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1020 String predicate = RelationshipType.HAS_BROADER.value();
1021 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1022 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1024 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
1025 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
1027 if(logger.isTraceEnabled()) {
1028 String dump = dumpLists(thisCSID, parentList, childrenList, null);
1029 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1032 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
1033 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
1034 //Not optimal, but that's the current design spec.
1036 for (RelationsCommonList.RelationListItem parent : parentList) {
1037 childrenList.add(parent);
1040 long childrenSize = childrenList.size();
1041 childrenListOuter.setTotalItems(childrenSize);
1042 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1044 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1045 ctx.addOutputPart(relationsPart);
1048 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1049 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1051 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1052 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1054 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1055 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1057 if(logger.isTraceEnabled()) {
1058 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1059 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1062 subjectList.addAll(objectList);
1064 //now subjectList actually has records BOTH where thisCSID is subject and object.
1065 long relatedSize = subjectList.size();
1066 subjectListOuter.setTotalItems(relatedSize);
1067 subjectListOuter.setItemsInPage(relatedSize);
1069 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1070 ctx.addOutputPart(relationsPart);
1073 private String dumpLists(String itemCSID,
1074 List<RelationsCommonList.RelationListItem> parentList,
1075 List<RelationsCommonList.RelationListItem> childList,
1076 List<RelationsCommonList.RelationListItem> actionList) {
1077 StringBuilder sb = new StringBuilder();
1078 sb.append("itemCSID: " + itemCSID + CR);
1079 if(parentList!=null) {
1080 sb.append(dumpList(parentList, "parentList"));
1082 if(childList!=null) {
1083 sb.append(dumpList(childList, "childList"));
1085 if(actionList!=null) {
1086 sb.append(dumpList(actionList, "actionList"));
1088 return sb.toString();
1091 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1092 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1093 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1094 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1095 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1096 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1097 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1099 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1100 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1101 return relationsCommonList;
1103 //============================= END TODO refactor ==========================
1105 // this method calls the RelationResource to have it create the relations and persist them.
1106 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1107 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1108 for (RelationsCommonList.RelationListItem item : inboundList) {
1109 RelationsCommon rc = new RelationsCommon();
1110 //rc.setCsid(item.getCsid());
1111 //todo: assignTo(item, rc);
1112 RelationsDocListItem itemSubject = item.getSubject();
1113 RelationsDocListItem itemObject = item.getObject();
1115 // Set at least one of CSID and refName for Subject and Object
1116 // Either value might be null for for each of Subject and Object
1117 String subjectCsid = itemSubject.getCsid();
1118 rc.setSubjectCsid(subjectCsid);
1120 String objCsid = itemObject.getCsid();
1121 rc.setObjectCsid(objCsid);
1123 rc.setSubjectRefName(itemSubject.getRefName());
1124 rc.setObjectRefName(itemObject.getRefName());
1126 rc.setRelationshipType(item.getPredicate());
1127 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1128 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1129 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1131 // This is superfluous, since it will be fetched by the Relations Create logic.
1132 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1133 rc.setObjectDocumentType(itemObject.getDocumentType());
1135 // This is superfluous, since it will be fetched by the Relations Create logic.
1136 rc.setSubjectUri(itemSubject.getUri());
1137 rc.setObjectUri(itemObject.getUri());
1138 // May not have the info here. Only really require CSID or refName.
1139 // Rest is handled in the Relation create mechanism
1140 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1142 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1143 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1144 payloadOut.addPart(outputPart);
1145 RelationResource relationResource = new RelationResource();
1146 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1147 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1151 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1152 // But item1 must not be sparse
1153 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1154 if (item1 == null || item2 == null) {
1157 RelationsDocListItem subj1 = item1.getSubject();
1158 RelationsDocListItem subj2 = item2.getSubject();
1159 RelationsDocListItem obj1 = item1.getObject();
1160 RelationsDocListItem obj2 = item2.getObject();
1162 String subj1Csid = subj1.getCsid();
1163 String subj2Csid = subj2.getCsid();
1164 String subj1RefName = subj1.getRefName();
1165 String subj2RefName = subj2.getRefName();
1167 String obj1Csid = obj1.getCsid();
1168 String obj2Csid = obj2.getCsid();
1169 String obj1RefName = obj1.getRefName();
1170 String obj2RefName = obj2.getRefName();
1172 String item1Metatype = item1.getRelationshipMetaType();
1173 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1175 String item2Metatype = item2.getRelationshipMetaType();
1176 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1178 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1179 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1180 // predicate is proper, but still allow relationshipType
1181 && (item1.getPredicate().equals(item2.getPredicate())
1182 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1183 // Allow missing docTypes, so long as they do not conflict
1184 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1185 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1186 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1190 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1191 // But the list items must not be sparse
1192 private RelationsCommonList.RelationListItem findInList(
1193 List<RelationsCommonList.RelationListItem> list,
1194 RelationsCommonList.RelationListItem item) {
1195 RelationsCommonList.RelationListItem foundItem = null;
1196 for (RelationsCommonList.RelationListItem listItem : list) {
1197 if (itemsEqual(listItem, item)) { //equals must be defined, else
1198 foundItem = listItem;
1205 /** updateRelations strategy:
1208 go through inboundList, remove anything from childList that matches from childList
1209 go through inboundList, remove anything from parentList that matches from parentList
1210 go through parentList, delete all remaining
1211 go through childList, delete all remaining
1212 go through actionList, add all remaining.
1213 check for duplicate children
1214 check for more than one parent.
1216 inboundList parentList childList actionList
1217 ---------------- --------------- ---------------- ----------------
1218 child-a parent-c child-a child-b
1219 child-b parent-d child-c
1224 private RelationsCommonList updateRelations(
1225 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate)
1227 if (logger.isTraceEnabled()) {
1228 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1230 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1232 return null; //nothing to do--they didn't send a list of relations.
1234 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1235 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1236 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1237 List<RelationsCommonList.RelationListItem> childList = null;
1238 List<RelationsCommonList.RelationListItem> parentList = null;
1239 DocumentModel docModel = wrapDoc.getWrappedObject();
1240 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1241 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1243 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1244 //Do magic replacement of ${itemCSID} and fix URI's.
1245 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1247 final String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1248 UriInfo uriInfo = ctx.getUriInfo();
1249 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1252 //Run getList() once as sent to get childListOuter:
1253 String predicate = RelationshipType.HAS_BROADER.value();
1254 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1255 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1256 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1257 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1258 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1260 RelationResource relationResource = new RelationResource();
1261 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1263 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1264 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1265 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1266 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1267 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1270 childList = childListOuter.getRelationListItem();
1271 parentList = parentListOuter.getRelationListItem();
1273 if (parentList.size() > 1) {
1274 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1277 if (logger.isTraceEnabled()) {
1278 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1282 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1283 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1284 // and so the CSID for those may be null
1285 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1286 // Look for parents and children
1287 if(itemCSID.equals(inboundItem.getObject().getCsid())
1288 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1289 //then this is an item that says we have a child. That child is inboundItem
1290 RelationsCommonList.RelationListItem childItem =
1291 (childList == null) ? null : findInList(childList, inboundItem);
1292 if (childItem != null) {
1293 if (logger.isTraceEnabled()) {
1294 StringBuilder sb = new StringBuilder();
1295 itemToString(sb, "== Child: ", childItem);
1296 logger.trace("Found inboundChild in current child list: " + sb.toString());
1298 removeFromList(childList, childItem); //exists, just take it off delete list
1300 if (logger.isTraceEnabled()) {
1301 StringBuilder sb = new StringBuilder();
1302 itemToString(sb, "== Child: ", inboundItem);
1303 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1305 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1306 String newChildCsid = inboundItem.getSubject().getCsid();
1307 if(newChildCsid == null) {
1308 String newChildRefName = inboundItem.getSubject().getRefName();
1309 if (newChildRefName == null) {
1310 throw new RuntimeException("Child with no CSID or refName!");
1312 if (logger.isTraceEnabled()) {
1313 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1315 DocumentModel newChildDocModel =
1316 NuxeoBasedResource.getDocModelForRefName(getServiceContext(),
1317 newChildRefName, getServiceContext().getResourceMap());
1318 newChildCsid = getCsid(newChildDocModel);
1320 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1323 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1324 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1325 //then this is an item that says we have a parent. inboundItem is that parent.
1326 RelationsCommonList.RelationListItem parentItem =
1327 (parentList == null) ? null : findInList(parentList, inboundItem);
1328 if (parentItem != null) {
1329 removeFromList(parentList, parentItem); //exists, just take it off delete list
1331 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1334 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1337 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1340 if (logger.isTraceEnabled()) {
1341 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1342 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1345 if (logger.isTraceEnabled()) {
1346 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1347 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1349 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1350 deleteRelations(childList, ctx, "childList");
1352 if (logger.isTraceEnabled()) {
1353 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1354 + actionList.size() + " new parents and children.");
1356 createRelations(actionList, ctx);
1357 if (logger.isTraceEnabled()) {
1358 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1360 // We return all elements on the inbound list, since we have just worked to make them exist in the system
1361 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1362 return relationsCommonListBody;
1365 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1366 * and sets URI correctly for related items.
1367 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1369 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1370 List<RelationsCommonList.RelationListItem> inboundList,
1371 DocumentModel docModel,
1372 String itemCSID) throws Exception {
1373 String thisURI = this.getUri(docModel);
1374 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1375 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1376 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1377 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1378 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1380 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1381 inboundItem.setObjectCsid(itemCSID);
1382 inboundItemObject.setCsid(itemCSID);
1383 //inboundItemObject.setUri(getUri(docModel));
1386 String objectCsid = inboundItemObject.getCsid();
1387 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1388 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1389 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1390 inboundItemObject.setUri(uri); //CSPACE-4037
1393 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1395 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1396 inboundItem.setSubjectCsid(itemCSID);
1397 inboundItemSubject.setCsid(itemCSID);
1398 //inboundItemSubject.setUri(getUri(docModel));
1401 String subjectCsid = inboundItemSubject.getCsid();
1402 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1403 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1404 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1405 inboundItemSubject.setUri(uri); //CSPACE-4037
1408 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1413 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1414 MultivaluedMap<String, String> queryParams, String childCSID) {
1415 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1416 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1417 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1418 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1420 RelationResource relationResource = new RelationResource();
1421 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1422 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1423 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1424 deleteRelations(parentList, ctx, "parentList-delete");
1427 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1428 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1431 for (RelationsCommonList.RelationListItem item : list) {
1432 RelationResource relationResource = new RelationResource();
1433 if(logger.isTraceEnabled()) {
1434 StringBuilder sb = new StringBuilder();
1435 itemToString(sb, "==== TO DELETE: ", item);
1436 logger.trace(sb.toString());
1438 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1439 if (logger.isDebugEnabled()) {
1440 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1443 } catch (Throwable t) {
1444 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1449 // Note that we must do this after we have completed the Update, so that the repository has the
1450 // info for the item itself. The relations code must call into the repo to get info for each end.
1451 // This could be optimized to pass in the parent docModel, since it will often be one end.
1452 // Nevertheless, we should complete the item save before we do work on the relations, especially
1453 // since a save on Create might fail, and we would not want to create relations for something
1454 // that may not be created...
1455 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate) throws Exception {
1456 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1457 PoxPayloadIn input = ctx.getInput();
1458 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1459 String itemCsid = documentModel.getName();
1461 //Updates relations part
1462 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, forUpdate);
1464 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
1465 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1467 //now we add part for relations list
1468 //ServiceContext ctx = getServiceContext();
1469 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1470 ctx.getOutput().addPart(payloadOutputPart);
1474 * Checks to see if the refName has changed, and if so,
1475 * uses utilities to find all references and update them to use the new refName.
1478 protected void handleRefNameReferencesUpdate() throws Exception {
1479 if (hasRefNameUpdate() == true) {
1480 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1481 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1482 CoreSessionInterface repoSession = this.getRepositorySession();
1484 // Update all the relationship records that referred to the old refName
1485 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1486 oldRefNameOnUpdate, newRefNameOnUpdate);
1490 protected String getRefNameUpdate() {
1491 String result = null;
1493 if (hasRefNameUpdate() == true) {
1494 result = newRefNameOnUpdate;
1495 if (logger.isDebugEnabled() == true) {
1496 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1497 newRefNameOnUpdate, oldRefNameOnUpdate));
1505 * Note: The Vocabulary document handler overrides this method.
1507 protected String getRefPropName() {
1508 return ServiceBindingUtils.AUTH_REF_PROP;