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.CollectionSpaceClient;
44 import org.collectionspace.services.client.PayloadInputPart;
45 import org.collectionspace.services.client.PayloadOutputPart;
46 import org.collectionspace.services.client.PoxPayloadIn;
47 import org.collectionspace.services.client.PoxPayloadOut;
48 import org.collectionspace.services.client.Profiler;
49 import org.collectionspace.services.client.RelationClient;
50 import org.collectionspace.services.client.workflow.WorkflowClient;
51 import org.collectionspace.services.common.CSWebApplicationException;
52 import org.collectionspace.services.common.NuxeoBasedResource;
53 import org.collectionspace.services.common.authorityref.AuthorityRefList;
54 import org.collectionspace.services.common.config.ServiceConfigUtils;
55 import org.collectionspace.services.common.context.JaxRsContext;
56 import org.collectionspace.services.common.context.MultipartServiceContext;
57 import org.collectionspace.services.common.context.ServiceBindingUtils;
58 import org.collectionspace.services.common.context.ServiceContext;
59 import org.collectionspace.services.common.document.BadRequestException;
60 import org.collectionspace.services.common.document.DocumentException;
61 import org.collectionspace.services.common.document.DocumentUtils;
62 import org.collectionspace.services.common.document.DocumentWrapper;
63 import org.collectionspace.services.common.document.DocumentFilter;
64 import org.collectionspace.services.client.IRelationsManager;
65 import org.collectionspace.services.common.relation.RelationResource;
66 import org.collectionspace.services.common.repository.RepositoryClient;
67 import org.collectionspace.services.common.security.SecurityUtils;
68 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
69 import org.collectionspace.services.common.api.CommonAPI;
70 import org.collectionspace.services.common.api.RefNameUtils;
71 import org.collectionspace.services.common.api.Tools;
72 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
73 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
74 import org.collectionspace.services.config.service.DocHandlerParams;
75 import org.collectionspace.services.config.service.ListResultField;
76 import org.collectionspace.services.config.service.ObjectPartType;
77 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
78 import org.collectionspace.services.relation.RelationsCommon;
79 import org.collectionspace.services.relation.RelationsCommonList;
80 import org.collectionspace.services.relation.RelationsDocListItem;
81 import org.collectionspace.services.relation.RelationshipType;
82 import org.dom4j.Element;
83 import org.nuxeo.ecm.core.api.DocumentModel;
84 import org.nuxeo.ecm.core.api.DocumentModelList;
85 import org.nuxeo.ecm.core.api.model.PropertyException;
86 import org.slf4j.Logger;
87 import org.slf4j.LoggerFactory;
90 * RemoteDocumentModelHandler
92 * $LastChangedRevision: $
97 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
98 extends DocumentModelHandler<T, TL> {
101 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
102 private final static String CR = "\r\n";
103 private final static String EMPTYSTR = "";
106 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
109 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
110 if (ctx instanceof MultipartServiceContext) {
111 super.setServiceContext(ctx);
113 throw new IllegalArgumentException("setServiceContext requires instance of "
114 + MultipartServiceContext.class.getName());
119 protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
120 return getRefnameDisplayName(docWrapper.getWrappedObject());
123 private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
124 String result = null;
125 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
127 DocHandlerParams.Params params = null;
129 params = ServiceConfigUtils.getDocHandlerParams(ctx);
130 ListResultField field = params.getRefnameDisplayNameField();
132 String schema = field.getSchema();
133 if (schema == null || schema.trim().isEmpty()) {
134 schema = ctx.getCommonPartLabel();
137 result = getStringValue(docModel, schema, field);
138 } catch (Exception e) {
139 if (logger.isWarnEnabled()) {
140 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
148 public boolean supportsHierarchy() {
149 boolean result = false;
151 DocHandlerParams.Params params = null;
153 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
154 params = ServiceConfigUtils.getDocHandlerParams(ctx);
155 Boolean bool = params.isSupportsHierarchy();
157 result = bool.booleanValue();
159 } catch (DocumentException e) {
160 // TODO Auto-generated catch block
161 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
162 if (logger.isWarnEnabled() == true) {
171 public boolean supportsVersioning() {
172 boolean result = false;
174 DocHandlerParams.Params params = null;
176 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
177 params = ServiceConfigUtils.getDocHandlerParams(ctx);
178 Boolean bool = params.isSupportsVersioning();
180 result = bool.booleanValue();
182 } catch (DocumentException e) {
183 // TODO Auto-generated catch block
184 String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
185 if (logger.isWarnEnabled() == true) {
194 public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
196 // Do nothing by default, but children can override if they want. The real workflow transition happens in the WorkflowDocumentModelHandler class
200 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
201 super.completeCreate(wrapDoc);
202 if (supportsHierarchy() == true) {
203 handleRelationsPayload(wrapDoc, false);
207 /* NOTE: The authority item doc handler overrides (after calling) this method. It performs refName updates. In this
208 * method we just update any and all relationship records that use refNames that have changed.
210 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
213 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
214 DocumentModel docModel = wrapDoc.getWrappedObject();
215 // We need to return at least those document part(s) and corresponding payloads that were received
216 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
217 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
218 PoxPayloadIn input = ctx.getInput();
220 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
221 for (PayloadInputPart part : inputParts) {
222 String partLabel = part.getLabel();
224 ObjectPartType partMeta = partsMetaMap.get(partLabel);
225 // CSPACE-4030 - generates NPE if the part is missing.
227 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
228 if(unQObjectProperties!=null) {
229 addOutputPart(unQObjectProperties, partLabel, partMeta);
232 } catch (Throwable t){
233 logger.error("Unable to addOutputPart: " + partLabel
234 + " in serviceContextPath: "+this.getServiceContextPath()
235 + " with URI: " + this.getServiceContext().getUriInfo().getPath()
240 if (logger.isWarnEnabled() == true) {
241 logger.warn("MultipartInput part was null for document id = " +
246 // If the resource's service supports hierarchy then we need to perform a little more work
248 if (supportsHierarchy() == true) {
249 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
250 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
255 * Adds the output part.
257 * @param unQObjectProperties the un q object properties
258 * @param schema the schema
259 * @param partMeta the part meta
260 * @throws Exception the exception
261 * MediaType.APPLICATION_XML_TYPE
263 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
265 Element doc = DocumentUtils.buildDocument(partMeta, schema,
266 unQObjectProperties);
267 if (logger.isTraceEnabled() == true) {
268 logger.trace(doc.asXML());
270 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
271 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
275 * Extract paging info.
277 * @param commonsList the commons list
279 * @throws Exception the exception
281 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
283 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
285 DocumentFilter docFilter = this.getDocumentFilter();
286 long pageSize = docFilter.getPageSize();
287 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
288 // set the page size and page number
289 commonList.setPageNum(pageNum);
290 commonList.setPageSize(pageSize);
291 DocumentModelList docList = wrapDoc.getWrappedObject();
292 // Set num of items in list. this is useful to our testing framework.
293 commonList.setItemsInPage(docList.size());
294 // set the total result size
295 commonList.setTotalItems(docList.totalSize());
297 return (TL) commonList;
301 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
304 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
307 DocumentModel docModel = wrapDoc.getWrappedObject();
308 String[] schemas = docModel.getDeclaredSchemas();
309 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
310 for (String schema : schemas) {
311 ObjectPartType partMeta = partsMetaMap.get(schema);
312 if (partMeta == null) {
313 continue; // unknown part, ignore
315 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
316 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
317 addExtraCoreValues(docModel, unQObjectProperties);
319 addOutputPart(unQObjectProperties, schema, partMeta);
322 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
324 if (supportsHierarchy() == true) {
325 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
326 if (Tools.isTrue(showSiblings)) {
327 showSiblings(wrapDoc, ctx);
328 return; // actual result is returned on ctx.addOutputPart();
331 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
332 if (Tools.isTrue(showRelations)) {
333 showRelations(wrapDoc, ctx);
334 return; // actual result is returned on ctx.addOutputPart();
337 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
338 if (Tools.isTrue(showAllRelations)) {
339 showAllRelations(wrapDoc, ctx);
340 return; // actual result is returned on ctx.addOutputPart();
344 String currentUser = ctx.getUserId();
345 if (currentUser.equalsIgnoreCase(AuthNContext.ANONYMOUS_USER) == false) {
346 addAccountPermissionsPart();
350 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
352 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
355 private void addAccountPermissionsPart() throws Exception {
356 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
359 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
360 String currentServiceName = ctx.getServiceName();
361 String workflowSubResource = "/";
362 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
363 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
364 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
365 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
367 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
369 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
370 currentServiceName, workflowSubResource);
371 org.collectionspace.services.authorization.ObjectFactory objectFactory =
372 new org.collectionspace.services.authorization.ObjectFactory();
373 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
374 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
375 ctx.addOutputPart(accountPermissionPart);
381 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
384 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
386 //TODO filling extension parts should be dynamic
387 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
388 //not an ideal way of populating objects.
389 DocumentModel docModel = wrapDoc.getWrappedObject();
390 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
391 PoxPayloadIn input = ctx.getInput();
392 if (input == null || input.getParts().isEmpty()) {
393 String msg = String.format("No payload found for '%s' action.", action);
394 logger.error(msg + "Ctx=" + getServiceContext().toString());
395 throw new BadRequestException(msg);
398 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
400 //iterate over parts received and fill those parts
401 List<PayloadInputPart> inputParts = input.getParts();
402 for (PayloadInputPart part : inputParts) {
404 String partLabel = part.getLabel();
405 if (partLabel == null) {
406 String msg = "Part label is missing or empty!";
407 logger.error(msg + "Ctx=" + getServiceContext().toString());
408 throw new BadRequestException(msg);
411 //skip if the part is not in metadata
412 ObjectPartType partMeta = partsMetaMap.get(partLabel);
413 if (partMeta == null) {
416 fillPart(part, docModel, partMeta, action, ctx);
421 * fillPart fills an XML part into given document model
422 * @param part to fill
423 * @param docModel for the given object
424 * @param partMeta metadata for the object to fill
427 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
428 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
430 //check if this is an xml part
431 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
432 Element element = part.getElementBody();
433 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
434 if (action == Action.UPDATE) {
435 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
437 docModel.setProperties(partMeta.getLabel(), objectProps);
442 * Filters out read only properties, so they cannot be set on update.
443 * TODO: add configuration support to do this generally
444 * @param objectProps the properties parsed from the update payload
445 * @param partMeta metadata for the object to fill
447 public void filterReadOnlyPropertiesForPart(
448 Map<String, Object> objectProps, ObjectPartType partMeta) {
449 // Should add in logic to filter most of the core items on update
450 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
451 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
452 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
453 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
454 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
455 // Note that the updatedAt/updatedBy fields are set internally
456 // in DocumentModelHandler.handleCoreValues().
461 * extractPart extracts an XML object from given DocumentModel
463 * @param schema of the object to extract
464 * @param partMeta metadata for the object to extract
467 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
469 return extractPart(docModel, schema, (Map<String, Object>)null);
473 * extractPart extracts an XML object from given DocumentModel
475 * @param schema of the object to extract
476 * @param partMeta metadata for the object to extract
480 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
482 return extractPart(docModel, schema, partMeta, null);
486 * extractPart extracts an XML object from given DocumentModel
488 * @param schema of the object to extract
489 * @param partMeta metadata for the object to extract
492 protected Map<String, Object> extractPart(
493 DocumentModel docModel,
495 Map<String, Object> addToMap)
497 Map<String, Object> result = null;
499 Map<String, Object> objectProps = docModel.getProperties(schema);
500 if (objectProps != null) {
501 //unqualify properties before sending the doc over the wire (to save bandwidh)
502 //FIXME: is there a better way to avoid duplication of a Map/Collection?
503 Map<String, Object> unQObjectProperties =
504 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
505 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
506 for (Entry<String, Object> entry : qualifiedEntries) {
507 String unqProp = getUnQProperty(entry.getKey());
508 unQObjectProperties.put(unqProp, entry.getValue());
510 result = unQObjectProperties;
517 * extractPart extracts an XML object from given DocumentModel
519 * @param schema of the object to extract
520 * @param partMeta metadata for the object to extract
524 protected Map<String, Object> extractPart(
525 DocumentModel docModel, String schema, ObjectPartType partMeta,
526 Map<String, Object> addToMap)
528 Map<String, Object> result = null;
530 result = this.extractPart(docModel, schema, addToMap);
536 public String getStringPropertyFromDoc(
539 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
540 RepositoryInstance repoSession = null;
541 boolean releaseRepoSession = false;
542 String returnValue = null;
545 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
546 repoSession = this.getRepositorySession();
547 if (repoSession == null) {
548 repoSession = repoClient.getRepositorySession();
549 releaseRepoSession = true;
553 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
554 DocumentModel docModel = wrapper.getWrappedObject();
555 returnValue = (String) docModel.getPropertyValue(propertyXPath);
556 } catch (PropertyException pe) {
558 } catch (DocumentException de) {
560 } catch (Exception e) {
561 if (logger.isDebugEnabled()) {
562 logger.debug("Caught exception ", e);
564 throw new DocumentException(e);
566 if (releaseRepoSession && repoSession != null) {
567 repoClient.releaseRepositorySession(repoSession);
570 } catch (Exception e) {
571 if (logger.isDebugEnabled()) {
572 logger.debug("Caught exception ", e);
574 throw new DocumentException(e);
578 if (logger.isWarnEnabled() == true) {
579 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
588 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
591 public AuthorityRefList getAuthorityRefs(
593 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException, Exception {
595 AuthorityRefList authRefList = new AuthorityRefList();
596 AbstractCommonList commonList = (AbstractCommonList) authRefList;
598 DocumentFilter docFilter = this.getDocumentFilter();
599 long pageSize = docFilter.getPageSize();
600 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
601 // set the page size and page number
602 commonList.setPageNum(pageNum);
603 commonList.setPageSize(pageSize);
605 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
608 int iFirstToUse = (int)(pageSize*pageNum);
609 int nFoundInPage = 0;
612 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
613 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
615 boolean releaseRepoSession = false;
616 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
617 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
618 CoreSessionInterface repoSession = this.getRepositorySession();
619 if (repoSession == null) {
620 repoSession = repoClient.getRepositorySession(ctx);
621 releaseRepoSession = true;
625 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
626 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
627 // Slightly goofy pagination support - how many refs do we expect from one object?
628 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
629 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
630 if(appendToAuthRefsList(ari, list)) {
639 if (releaseRepoSession == true) {
640 repoClient.releaseRepositorySession(ctx, repoSession);
644 // Set num of items in list. this is useful to our testing framework.
645 commonList.setItemsInPage(nFoundInPage);
646 // set the total result size
647 commonList.setTotalItems(nFoundTotal);
649 } catch (PropertyException pe) {
650 String msg = "Attempted to retrieve value for invalid or missing authority field. "
651 + "Check authority field properties in tenant bindings.";
652 logger.warn(msg, pe);
654 } catch (Exception e) {
655 if (logger.isDebugEnabled()) {
656 logger.debug("Caught exception in getAuthorityRefs", e);
658 Response response = Response.status(
659 Response.Status.INTERNAL_SERVER_ERROR).entity(
660 "Failed to retrieve authority references").type(
661 "text/plain").build();
662 throw new CSWebApplicationException(e, response);
668 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
669 List<AuthorityRefList.AuthorityRefItem> list)
671 String fieldName = ari.getQualifiedDisplayName();
673 String refNameValue = (String)ari.getProperty().getValue();
674 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
675 if(item!=null) { // ignore garbage values.
679 } catch(PropertyException pe) {
680 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
685 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
687 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
689 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
690 ilistItem.setRefName(refName);
691 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
692 ilistItem.setItemDisplayName(termInfo.displayName);
693 ilistItem.setSourceField(authRefFieldName);
694 ilistItem.setUri(termInfo.getRelativeUri());
695 } catch (Exception e) {
696 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
703 * Returns the primary value from a list of values.
705 * Assumes that the first value is the primary value.
706 * This assumption may change when and if the primary value
707 * is identified explicitly.
709 * @param values a list of values.
710 * @param propertyName the name of a property through
711 * which the value can be extracted.
712 * @return the primary value.
713 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
714 String primaryValue = "";
715 if (values == null || values.size() == 0) {
718 Object value = values.get(0);
719 if (value instanceof String) {
721 primaryValue = (String) value;
723 // Multivalue group of fields
724 } else if (value instanceof Map) {
726 Map map = (Map) value;
727 if (map.values().size() > 0) {
728 if (map.get(propertyName) != null) {
729 primaryValue = (String) map.get(propertyName);
734 logger.warn("Unexpected type for property " + propertyName
735 + " in multivalue list: not String or Map.");
742 * Gets a simple property from the document.
744 * For completeness, as this duplicates DocumentModel method.
746 * @param docModel The document model to get info from
747 * @param schema The name of the schema (part)
748 * @param propertyName The simple scalar property type
749 * @return property value as String
751 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
752 String xpath = "/"+schema+":"+propName;
754 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
755 } catch(PropertyException pe) {
756 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
757 +pe.getLocalizedMessage());
758 } catch(ClassCastException cce) {
759 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
760 +cce.getLocalizedMessage());
761 } catch(Exception e) {
762 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
763 +e.getLocalizedMessage());
768 * Gets first of a repeating list of scalar values, as a String, from the document.
770 * @param docModel The document model to get info from
771 * @param schema The name of the schema (part)
772 * @param listName The name of the scalar list property
773 * @return first value in list, as a String, or empty string if the list is empty
775 protected String getFirstRepeatingStringProperty(DocumentModel docModel,
776 String schema, String listName) {
777 String xpath = "/" + schema + ":" + listName + "/[0]";
779 return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
780 } catch (PropertyException pe) {
781 throw new RuntimeException("Problem retrieving property {" + xpath
782 + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
783 } catch (IndexOutOfBoundsException ioobe) {
784 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
785 return ""; // gracefully handle missing elements
786 } catch (ClassCastException cce) {
787 throw new RuntimeException("Problem retrieving property {" + xpath
788 + "} as String. Not a repeating String property?"
789 + cce.getLocalizedMessage());
790 } catch (Exception e) {
791 throw new RuntimeException("Unknown problem retrieving property {"
792 + xpath + "}." + e.getLocalizedMessage());
797 * Gets first of a repeating list of scalar values, as a String, from the document.
799 * @param docModel The document model to get info from
800 * @param schema The name of the schema (part)
801 * @param listName The name of the scalar list property
802 * @return first value in list, as a String, or empty string if the list is empty
804 protected String getStringValueInPrimaryRepeatingComplexProperty(
805 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
806 String result = null;
808 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
810 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
811 } catch(PropertyException pe) {
812 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
813 +pe.getLocalizedMessage());
814 } catch(IndexOutOfBoundsException ioobe) {
815 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
816 result = ""; // gracefully handle missing elements
817 } catch(ClassCastException cce) {
818 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
819 +cce.getLocalizedMessage());
820 } catch(NullPointerException npe) {
821 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
822 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
824 } catch(Exception e) {
825 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
826 +e.getLocalizedMessage());
833 * Gets XPath value from schema. Note that only "/" and "[n]" are
834 * supported for xpath. Can omit grouping elements for repeating complex types,
835 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
836 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
837 * If there are no entries for a list of scalars or for a list of complex types,
838 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
839 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
840 * that many elements in the list.
841 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
843 * @param docModel The document model to get info from
844 * @param schema The name of the schema (part)
845 * @param xpath The XPath expression (without schema prefix)
846 * @return value the indicated property value as a String
847 * @throws DocumentException
849 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
850 String schema, ListResultField field) throws DocumentException {
851 Object result = null;
853 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
858 protected String getStringValue(DocumentModel docModel,
859 String schema, ListResultField field) throws DocumentException {
860 String result = null;
862 Object value = getListResultValue(docModel, schema, field);
863 if (value != null && value instanceof String) {
864 String strValue = (String) value;
865 if (strValue.trim().isEmpty() == false) {
873 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
877 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
879 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
881 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
883 sb.append(item.getPredicate());
885 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
889 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
890 StringBuilder sb = new StringBuilder();
892 if (list.size() > 0) {
893 sb.append("=========== " + label + " ==========" + CR);
895 for (RelationsCommonList.RelationListItem item : list) {
896 itemToString(sb, "== ", item);
899 return sb.toString();
902 /** @return null on parent not found
904 protected String getParentCSID(String thisCSID) throws Exception {
905 String parentCSID = null;
907 String predicate = RelationshipType.HAS_BROADER.value();
908 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
909 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
910 if (parentList != null) {
911 if (parentList.size() == 0) {
914 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
915 parentCSID = relationListItem.getObjectCsid();
918 } catch (Exception e) {
919 logger.error("Could not find parent for this: " + thisCSID, e);
924 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
925 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
929 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
930 MultipartServiceContext ctx) throws Exception {
931 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
932 String parentCSID = getParentCSID(thisCSID);
933 if (parentCSID == null) {
934 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
938 String predicate = RelationshipType.HAS_BROADER.value();
939 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
940 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
942 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
945 RelationsCommonList.RelationListItem item = null;
946 for (RelationsCommonList.RelationListItem sibling : siblingList) {
947 if (thisCSID.equals(sibling.getSubjectCsid())) {
948 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.
951 //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.
952 for (RelationsCommonList.RelationListItem self : toRemoveList) {
953 removeFromList(siblingList, self);
956 long siblingSize = siblingList.size();
957 siblingListOuter.setTotalItems(siblingSize);
958 siblingListOuter.setItemsInPage(siblingSize);
959 if(logger.isTraceEnabled()) {
960 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
961 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
964 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
965 ctx.addOutputPart(relationsPart);
968 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
969 MultipartServiceContext ctx) throws Exception {
970 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
972 String predicate = RelationshipType.HAS_BROADER.value();
973 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
974 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
976 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
977 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
979 if(logger.isTraceEnabled()) {
980 String dump = dumpLists(thisCSID, parentList, childrenList, null);
981 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
984 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
985 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
986 //Not optimal, but that's the current design spec.
988 for (RelationsCommonList.RelationListItem parent : parentList) {
989 childrenList.add(parent);
992 long childrenSize = childrenList.size();
993 childrenListOuter.setTotalItems(childrenSize);
994 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
996 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
997 ctx.addOutputPart(relationsPart);
1000 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1001 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1003 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1004 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1006 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1007 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1009 if(logger.isTraceEnabled()) {
1010 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1011 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1014 subjectList.addAll(objectList);
1016 //now subjectList actually has records BOTH where thisCSID is subject and object.
1017 long relatedSize = subjectList.size();
1018 subjectListOuter.setTotalItems(relatedSize);
1019 subjectListOuter.setItemsInPage(relatedSize);
1021 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1022 ctx.addOutputPart(relationsPart);
1025 private String dumpLists(String itemCSID,
1026 List<RelationsCommonList.RelationListItem> parentList,
1027 List<RelationsCommonList.RelationListItem> childList,
1028 List<RelationsCommonList.RelationListItem> actionList) {
1029 StringBuilder sb = new StringBuilder();
1030 sb.append("itemCSID: " + itemCSID + CR);
1031 if(parentList!=null) {
1032 sb.append(dumpList(parentList, "parentList"));
1034 if(childList!=null) {
1035 sb.append(dumpList(childList, "childList"));
1037 if(actionList!=null) {
1038 sb.append(dumpList(actionList, "actionList"));
1040 return sb.toString();
1043 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1044 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1045 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1046 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1047 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1048 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1049 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1051 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1052 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1053 return relationsCommonList;
1055 //============================= END TODO refactor ==========================
1057 // this method calls the RelationResource to have it create the relations and persist them.
1058 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1059 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1060 for (RelationsCommonList.RelationListItem item : inboundList) {
1061 RelationsCommon rc = new RelationsCommon();
1062 //rc.setCsid(item.getCsid());
1063 //todo: assignTo(item, rc);
1064 RelationsDocListItem itemSubject = item.getSubject();
1065 RelationsDocListItem itemObject = item.getObject();
1067 // Set at least one of CSID and refName for Subject and Object
1068 // Either value might be null for for each of Subject and Object
1069 String subjectCsid = itemSubject.getCsid();
1070 rc.setSubjectCsid(subjectCsid);
1072 String objCsid = itemObject.getCsid();
1073 rc.setObjectCsid(objCsid);
1075 rc.setSubjectRefName(itemSubject.getRefName());
1076 rc.setObjectRefName(itemObject.getRefName());
1078 rc.setRelationshipType(item.getPredicate());
1079 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1080 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1081 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1083 // This is superfluous, since it will be fetched by the Relations Create logic.
1084 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1085 rc.setObjectDocumentType(itemObject.getDocumentType());
1087 // This is superfluous, since it will be fetched by the Relations Create logic.
1088 rc.setSubjectUri(itemSubject.getUri());
1089 rc.setObjectUri(itemObject.getUri());
1090 // May not have the info here. Only really require CSID or refName.
1091 // Rest is handled in the Relation create mechanism
1092 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1094 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1095 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1096 payloadOut.addPart(outputPart);
1097 RelationResource relationResource = new RelationResource();
1098 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1099 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1103 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1104 // But item1 must not be sparse
1105 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1106 if (item1 == null || item2 == null) {
1109 RelationsDocListItem subj1 = item1.getSubject();
1110 RelationsDocListItem subj2 = item2.getSubject();
1111 RelationsDocListItem obj1 = item1.getObject();
1112 RelationsDocListItem obj2 = item2.getObject();
1114 String subj1Csid = subj1.getCsid();
1115 String subj2Csid = subj2.getCsid();
1116 String subj1RefName = subj1.getRefName();
1117 String subj2RefName = subj2.getRefName();
1119 String obj1Csid = obj1.getCsid();
1120 String obj2Csid = obj2.getCsid();
1121 String obj1RefName = obj1.getRefName();
1122 String obj2RefName = obj2.getRefName();
1124 String item1Metatype = item1.getRelationshipMetaType();
1125 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1127 String item2Metatype = item2.getRelationshipMetaType();
1128 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1130 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1131 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1132 // predicate is proper, but still allow relationshipType
1133 && (item1.getPredicate().equals(item2.getPredicate())
1134 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1135 // Allow missing docTypes, so long as they do not conflict
1136 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1137 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1138 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1142 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1143 // But the list items must not be sparse
1144 private RelationsCommonList.RelationListItem findInList(
1145 List<RelationsCommonList.RelationListItem> list,
1146 RelationsCommonList.RelationListItem item) {
1147 RelationsCommonList.RelationListItem foundItem = null;
1148 for (RelationsCommonList.RelationListItem listItem : list) {
1149 if (itemsEqual(listItem, item)) { //equals must be defined, else
1150 foundItem = listItem;
1157 /** updateRelations strategy:
1160 go through inboundList, remove anything from childList that matches from childList
1161 go through inboundList, remove anything from parentList that matches from parentList
1162 go through parentList, delete all remaining
1163 go through childList, delete all remaining
1164 go through actionList, add all remaining.
1165 check for duplicate children
1166 check for more than one parent.
1168 inboundList parentList childList actionList
1169 ---------------- --------------- ---------------- ----------------
1170 child-a parent-c child-a child-b
1171 child-b parent-d child-c
1176 private RelationsCommonList updateRelations(
1177 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1179 if (logger.isTraceEnabled()) {
1180 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1182 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1184 return null; //nothing to do--they didn't send a list of relations.
1186 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1187 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1188 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1189 List<RelationsCommonList.RelationListItem> childList = null;
1190 List<RelationsCommonList.RelationListItem> parentList = null;
1191 DocumentModel docModel = wrapDoc.getWrappedObject();
1192 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1193 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1195 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1196 //Do magic replacement of ${itemCSID} and fix URI's.
1197 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1199 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1200 UriInfo uriInfo = ctx.getUriInfo();
1201 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1204 //Run getList() once as sent to get childListOuter:
1205 String predicate = RelationshipType.HAS_BROADER.value();
1206 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1207 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1208 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1209 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1210 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1212 RelationResource relationResource = new RelationResource();
1213 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1215 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1216 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1217 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1218 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1219 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1222 childList = childListOuter.getRelationListItem();
1223 parentList = parentListOuter.getRelationListItem();
1225 if (parentList.size() > 1) {
1226 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1229 if (logger.isTraceEnabled()) {
1230 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1233 // For debugging purpose only.
1234 if (logger.isDebugEnabled()) {
1235 String debugMsg = "AuthItemDocHndler.updateRelations for: " + itemCSID + " with an fUpdate value of FALSE.";
1236 logger.debug(debugMsg);
1237 throw new Exception(debugMsg);
1241 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1242 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1243 // and so the CSID for those may be null
1244 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1245 // Look for parents and children
1246 if(itemCSID.equals(inboundItem.getObject().getCsid())
1247 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1248 //then this is an item that says we have a child. That child is inboundItem
1249 RelationsCommonList.RelationListItem childItem =
1250 (childList == null) ? null : findInList(childList, inboundItem);
1251 if (childItem != null) {
1252 if (logger.isTraceEnabled()) {
1253 StringBuilder sb = new StringBuilder();
1254 itemToString(sb, "== Child: ", childItem);
1255 logger.trace("Found inboundChild in current child list: " + sb.toString());
1257 removeFromList(childList, childItem); //exists, just take it off delete list
1259 if (logger.isTraceEnabled()) {
1260 StringBuilder sb = new StringBuilder();
1261 itemToString(sb, "== Child: ", inboundItem);
1262 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1264 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1265 String newChildCsid = inboundItem.getSubject().getCsid();
1266 if(newChildCsid == null) {
1267 String newChildRefName = inboundItem.getSubject().getRefName();
1268 if(newChildRefName==null) {
1269 throw new RuntimeException("Child with no CSID or refName!");
1271 if (logger.isTraceEnabled()) {
1272 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1274 DocumentModel newChildDocModel =
1275 NuxeoBasedResource.getDocModelForRefName(this.getRepositorySession(),
1276 newChildRefName, getServiceContext().getResourceMap());
1277 newChildCsid = getCsid(newChildDocModel);
1279 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1282 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1283 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1284 //then this is an item that says we have a parent. inboundItem is that parent.
1285 RelationsCommonList.RelationListItem parentItem =
1286 (parentList == null) ? null : findInList(parentList, inboundItem);
1287 if (parentItem != null) {
1288 removeFromList(parentList, parentItem); //exists, just take it off delete list
1290 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1293 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1296 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1299 if (logger.isTraceEnabled()) {
1300 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1301 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1304 if (logger.isTraceEnabled()) {
1305 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1306 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1308 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1309 deleteRelations(childList, ctx, "childList");
1311 if (logger.isTraceEnabled()) {
1312 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1313 + actionList.size() + " new parents and children.");
1315 createRelations(actionList, ctx);
1316 if (logger.isTraceEnabled()) {
1317 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1319 // We return all elements on the inbound list, since we have just worked to make them exist in the system
1320 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1321 return relationsCommonListBody;
1324 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1325 * and sets URI correctly for related items.
1326 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1328 protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1329 List<RelationsCommonList.RelationListItem> inboundList,
1330 DocumentModel docModel,
1331 String itemCSID) throws Exception {
1332 String thisURI = this.getUri(docModel);
1333 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1334 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1335 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1336 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1337 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1339 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1340 inboundItem.setObjectCsid(itemCSID);
1341 inboundItemObject.setCsid(itemCSID);
1342 //inboundItemObject.setUri(getUri(docModel));
1345 String objectCsid = inboundItemObject.getCsid();
1346 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1347 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1348 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1349 inboundItemObject.setUri(uri); //CSPACE-4037
1352 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1354 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1355 inboundItem.setSubjectCsid(itemCSID);
1356 inboundItemSubject.setCsid(itemCSID);
1357 //inboundItemSubject.setUri(getUri(docModel));
1360 String subjectCsid = inboundItemSubject.getCsid();
1361 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1362 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1363 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1364 inboundItemSubject.setUri(uri); //CSPACE-4037
1367 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1372 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1373 MultivaluedMap<String, String> queryParams, String childCSID) {
1374 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1375 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1376 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1377 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1379 RelationResource relationResource = new RelationResource();
1380 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1381 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1382 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1383 deleteRelations(parentList, ctx, "parentList-delete");
1386 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1387 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1390 for (RelationsCommonList.RelationListItem item : list) {
1391 RelationResource relationResource = new RelationResource();
1392 if(logger.isTraceEnabled()) {
1393 StringBuilder sb = new StringBuilder();
1394 itemToString(sb, "==== TO DELETE: ", item);
1395 logger.trace(sb.toString());
1397 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1398 if (logger.isDebugEnabled()) {
1399 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1402 } catch (Throwable t) {
1403 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1408 // Note that we must do this after we have completed the Update, so that the repository has the
1409 // info for the item itself. The relations code must call into the repo to get info for each end.
1410 // This could be optimized to pass in the parent docModel, since it will often be one end.
1411 // Nevertheless, we should complete the item save before we do work on the relations, especially
1412 // since a save on Create might fail, and we would not want to create relations for something
1413 // that may not be created...
1414 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1415 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1416 PoxPayloadIn input = ctx.getInput();
1417 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1418 String itemCsid = documentModel.getName();
1420 //Updates relations part
1421 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1423 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
1424 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1426 //now we add part for relations list
1427 //ServiceContext ctx = getServiceContext();
1428 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1429 ctx.getOutput().addPart(payloadOutputPart);
1433 * Checks to see if the refName has changed, and if so,
1434 * uses utilities to find all references and update them to use the new refName.
1437 protected void handleRefNameReferencesUpdate() throws Exception {
1438 if (hasRefNameUpdate() == true) {
1439 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1440 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1441 CoreSessionInterface repoSession = this.getRepositorySession();
1443 // Update all the relationship records that referred to the old refName
1444 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1445 oldRefNameOnUpdate, newRefNameOnUpdate);
1449 protected String getRefNameUpdate() {
1450 String result = null;
1452 if (hasRefNameUpdate() == true) {
1453 result = newRefNameOnUpdate;
1454 if (logger.isDebugEnabled() == true) {
1455 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1456 newRefNameOnUpdate, oldRefNameOnUpdate));
1464 * Note: The Vocabulary document handler overrides this method.
1466 protected String getRefPropName() {
1467 return ServiceBindingUtils.AUTH_REF_PROP;