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.WebApplicationException;
34 import javax.ws.rs.core.MediaType;
35 import javax.ws.rs.core.MultivaluedMap;
36 import javax.ws.rs.core.Response;
37 import javax.ws.rs.core.UriInfo;
38 import javax.xml.bind.JAXBElement;
40 import org.collectionspace.authentication.spi.AuthNContext;
41 import org.collectionspace.services.authorization.AccountPermission;
42 import org.collectionspace.services.jaxb.AbstractCommonList;
43 import org.collectionspace.services.lifecycle.TransitionDef;
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.ResourceBase;
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.AuthorityItemJAXBSchema;
73 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
74 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
75 import org.collectionspace.services.config.service.DocHandlerParams;
76 import org.collectionspace.services.config.service.ListResultField;
77 import org.collectionspace.services.config.service.ObjectPartType;
78 import org.collectionspace.services.config.service.ServiceBindingType;
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;
86 import org.nuxeo.ecm.core.api.DocumentModel;
87 import org.nuxeo.ecm.core.api.DocumentModelList;
88 import org.nuxeo.ecm.core.api.model.PropertyException;
89 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
91 import org.slf4j.Logger;
92 import org.slf4j.LoggerFactory;
95 * RemoteDocumentModelHandler
97 * $LastChangedRevision: $
102 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
103 extends DocumentModelHandler<T, TL> {
106 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
107 private final static String CR = "\r\n";
108 private final static String EMPTYSTR = "";
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 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 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 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) {
200 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
202 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
206 public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
207 super.completeCreate(wrapDoc);
208 if (supportsHierarchy() == true) {
209 handleRelationsPayload(wrapDoc, false);
213 /* NOTE: The authority item doc handler overrides (after calling) this method. It performs refName updates. In this
214 * method we just update any and all relationship records that use refNames that have changed.
216 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
219 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
220 DocumentModel docModel = wrapDoc.getWrappedObject();
221 // We need to return at least those document part(s) and corresponding payloads that were received
222 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
223 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
224 PoxPayloadIn input = ctx.getInput();
226 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
227 for (PayloadInputPart part : inputParts) {
228 String partLabel = part.getLabel();
230 ObjectPartType partMeta = partsMetaMap.get(partLabel);
231 // CSPACE-4030 - generates NPE if the part is missing.
233 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
234 if(unQObjectProperties!=null) {
235 addOutputPart(unQObjectProperties, partLabel, partMeta);
238 } catch (Throwable t){
239 logger.error("Unable to addOutputPart: " + partLabel
240 + " in serviceContextPath: "+this.getServiceContextPath()
241 + " with URI: " + this.getServiceContext().getUriInfo().getPath()
246 if (logger.isWarnEnabled() == true) {
247 logger.warn("MultipartInput part was null for document id = " +
252 // If the resource's service supports hierarchy then we need to perform a little more work
254 if (supportsHierarchy() == true) {
255 handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
256 handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
261 * Adds the output part.
263 * @param unQObjectProperties the un q object properties
264 * @param schema the schema
265 * @param partMeta the part meta
266 * @throws Exception the exception
267 * MediaType.APPLICATION_XML_TYPE
269 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
271 Element doc = DocumentUtils.buildDocument(partMeta, schema,
272 unQObjectProperties);
273 if (logger.isTraceEnabled() == true) {
274 logger.trace(doc.asXML());
276 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
277 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
281 * Extract paging info.
283 * @param commonsList the commons list
285 * @throws Exception the exception
287 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
289 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
291 DocumentFilter docFilter = this.getDocumentFilter();
292 long pageSize = docFilter.getPageSize();
293 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
294 // set the page size and page number
295 commonList.setPageNum(pageNum);
296 commonList.setPageSize(pageSize);
297 DocumentModelList docList = wrapDoc.getWrappedObject();
298 // Set num of items in list. this is useful to our testing framework.
299 commonList.setItemsInPage(docList.size());
300 // set the total result size
301 commonList.setTotalItems(docList.totalSize());
303 return (TL) commonList;
307 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
310 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
313 DocumentModel docModel = wrapDoc.getWrappedObject();
314 String[] schemas = docModel.getDeclaredSchemas();
315 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
316 for (String schema : schemas) {
317 ObjectPartType partMeta = partsMetaMap.get(schema);
318 if (partMeta == null) {
319 continue; // unknown part, ignore
321 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
322 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
323 addExtraCoreValues(docModel, unQObjectProperties);
325 addOutputPart(unQObjectProperties, schema, partMeta);
328 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
330 if (supportsHierarchy() == true) {
331 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
332 if (Tools.isTrue(showSiblings)) {
333 showSiblings(wrapDoc, ctx);
334 return; // actual result is returned on ctx.addOutputPart();
337 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
338 if (Tools.isTrue(showRelations)) {
339 showRelations(wrapDoc, ctx);
340 return; // actual result is returned on ctx.addOutputPart();
343 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
344 if (Tools.isTrue(showAllRelations)) {
345 showAllRelations(wrapDoc, ctx);
346 return; // actual result is returned on ctx.addOutputPart();
350 String currentUser = ctx.getUserId();
351 if (currentUser.equalsIgnoreCase(AuthNContext.ANONYMOUS_USER) == false) {
352 addAccountPermissionsPart();
356 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
358 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
361 private void addAccountPermissionsPart() throws Exception {
362 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
365 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
366 String currentServiceName = ctx.getServiceName();
367 String workflowSubResource = "/";
368 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
369 if (jaxRsContext != null) { // If not null then we're dealing with an authority item
370 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
371 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
373 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
375 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
376 currentServiceName, workflowSubResource);
377 org.collectionspace.services.authorization.ObjectFactory objectFactory =
378 new org.collectionspace.services.authorization.ObjectFactory();
379 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
380 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
381 ctx.addOutputPart(accountPermissionPart);
387 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
390 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
392 //TODO filling extension parts should be dynamic
393 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
394 //not an ideal way of populating objects.
395 DocumentModel docModel = wrapDoc.getWrappedObject();
396 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
397 PoxPayloadIn input = ctx.getInput();
398 if (input.getParts().isEmpty()) {
399 String msg = "No payload found!";
400 logger.error(msg + "Ctx=" + getServiceContext().toString());
401 throw new BadRequestException(msg);
404 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
406 //iterate over parts received and fill those parts
407 List<PayloadInputPart> inputParts = input.getParts();
408 for (PayloadInputPart part : inputParts) {
410 String partLabel = part.getLabel();
411 if (partLabel == null) {
412 String msg = "Part label is missing or empty!";
413 logger.error(msg + "Ctx=" + getServiceContext().toString());
414 throw new BadRequestException(msg);
417 //skip if the part is not in metadata
418 ObjectPartType partMeta = partsMetaMap.get(partLabel);
419 if (partMeta == null) {
422 fillPart(part, docModel, partMeta, action, ctx);
428 * fillPart fills an XML part into given document model
429 * @param part to fill
430 * @param docModel for the given object
431 * @param partMeta metadata for the object to fill
434 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
435 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
437 //check if this is an xml part
438 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
439 Element element = part.getElementBody();
440 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
441 if (action == Action.UPDATE) {
442 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
444 docModel.setProperties(partMeta.getLabel(), objectProps);
449 * Filters out read only properties, so they cannot be set on update.
450 * TODO: add configuration support to do this generally
451 * @param objectProps the properties parsed from the update payload
452 * @param partMeta metadata for the object to fill
454 public void filterReadOnlyPropertiesForPart(
455 Map<String, Object> objectProps, ObjectPartType partMeta) {
456 // Should add in logic to filter most of the core items on update
457 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
458 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
459 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
460 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
461 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
462 // Note that the updatedAt/updatedBy fields are set internally
463 // in DocumentModelHandler.handleCoreValues().
468 * extractPart extracts an XML object from given DocumentModel
470 * @param schema of the object to extract
471 * @param partMeta metadata for the object to extract
474 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
476 return extractPart(docModel, schema, (Map<String, Object>)null);
480 * extractPart extracts an XML object from given DocumentModel
482 * @param schema of the object to extract
483 * @param partMeta metadata for the object to extract
487 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
489 return extractPart(docModel, schema, partMeta, null);
493 * extractPart extracts an XML object from given DocumentModel
495 * @param schema of the object to extract
496 * @param partMeta metadata for the object to extract
499 protected Map<String, Object> extractPart(
500 DocumentModel docModel,
502 Map<String, Object> addToMap)
504 Map<String, Object> result = null;
506 Map<String, Object> objectProps = docModel.getProperties(schema);
507 if (objectProps != null) {
508 //unqualify properties before sending the doc over the wire (to save bandwidh)
509 //FIXME: is there a better way to avoid duplication of a Map/Collection?
510 Map<String, Object> unQObjectProperties =
511 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
512 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
513 for (Entry<String, Object> entry : qualifiedEntries) {
514 String unqProp = getUnQProperty(entry.getKey());
515 unQObjectProperties.put(unqProp, entry.getValue());
517 result = unQObjectProperties;
524 * extractPart extracts an XML object from given DocumentModel
526 * @param schema of the object to extract
527 * @param partMeta metadata for the object to extract
531 protected Map<String, Object> extractPart(
532 DocumentModel docModel, String schema, ObjectPartType partMeta,
533 Map<String, Object> addToMap)
535 Map<String, Object> result = null;
537 result = this.extractPart(docModel, schema, addToMap);
543 public String getStringPropertyFromDoc(
546 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
547 RepositoryInstance repoSession = null;
548 boolean releaseRepoSession = false;
549 String returnValue = null;
552 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
553 repoSession = this.getRepositorySession();
554 if (repoSession == null) {
555 repoSession = repoClient.getRepositorySession();
556 releaseRepoSession = true;
560 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
561 DocumentModel docModel = wrapper.getWrappedObject();
562 returnValue = (String) docModel.getPropertyValue(propertyXPath);
563 } catch (PropertyException pe) {
565 } catch (DocumentException de) {
567 } catch (Exception e) {
568 if (logger.isDebugEnabled()) {
569 logger.debug("Caught exception ", e);
571 throw new DocumentException(e);
573 if (releaseRepoSession && repoSession != null) {
574 repoClient.releaseRepositorySession(repoSession);
577 } catch (Exception e) {
578 if (logger.isDebugEnabled()) {
579 logger.debug("Caught exception ", e);
581 throw new DocumentException(e);
585 if (logger.isWarnEnabled() == true) {
586 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
595 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
598 public AuthorityRefList getAuthorityRefs(
600 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException, Exception {
602 AuthorityRefList authRefList = new AuthorityRefList();
603 AbstractCommonList commonList = (AbstractCommonList) authRefList;
605 DocumentFilter docFilter = this.getDocumentFilter();
606 long pageSize = docFilter.getPageSize();
607 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
608 // set the page size and page number
609 commonList.setPageNum(pageNum);
610 commonList.setPageSize(pageSize);
612 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
615 int iFirstToUse = (int)(pageSize*pageNum);
616 int nFoundInPage = 0;
619 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
620 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
622 boolean releaseRepoSession = false;
623 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
624 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
625 RepositoryInstance repoSession = this.getRepositorySession();
626 if (repoSession == null) {
627 repoSession = repoClient.getRepositorySession(ctx);
628 releaseRepoSession = true;
632 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
633 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
634 // Slightly goofy pagination support - how many refs do we expect from one object?
635 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
636 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
637 if(appendToAuthRefsList(ari, list)) {
646 if (releaseRepoSession == true) {
647 repoClient.releaseRepositorySession(ctx, repoSession);
651 // Set num of items in list. this is useful to our testing framework.
652 commonList.setItemsInPage(nFoundInPage);
653 // set the total result size
654 commonList.setTotalItems(nFoundTotal);
656 } catch (PropertyException pe) {
657 String msg = "Attempted to retrieve value for invalid or missing authority field. "
658 + "Check authority field properties in tenant bindings.";
659 logger.warn(msg, pe);
661 } catch (Exception e) {
662 if (logger.isDebugEnabled()) {
663 logger.debug("Caught exception in getAuthorityRefs", e);
665 Response response = Response.status(
666 Response.Status.INTERNAL_SERVER_ERROR).entity(
667 "Failed to retrieve authority references").type(
668 "text/plain").build();
669 throw new WebApplicationException(response);
675 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
676 List<AuthorityRefList.AuthorityRefItem> list)
678 String fieldName = ari.getQualifiedDisplayName();
680 String refNameValue = (String)ari.getProperty().getValue();
681 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
682 if(item!=null) { // ignore garbage values.
686 } catch(PropertyException pe) {
687 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
692 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
694 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
696 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
697 ilistItem.setRefName(refName);
698 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
699 ilistItem.setItemDisplayName(termInfo.displayName);
700 ilistItem.setSourceField(authRefFieldName);
701 ilistItem.setUri(termInfo.getRelativeUri());
702 } catch (Exception e) {
703 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
710 * Returns the primary value from a list of values.
712 * Assumes that the first value is the primary value.
713 * This assumption may change when and if the primary value
714 * is identified explicitly.
716 * @param values a list of values.
717 * @param propertyName the name of a property through
718 * which the value can be extracted.
719 * @return the primary value.
720 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
721 String primaryValue = "";
722 if (values == null || values.size() == 0) {
725 Object value = values.get(0);
726 if (value instanceof String) {
728 primaryValue = (String) value;
730 // Multivalue group of fields
731 } else if (value instanceof Map) {
733 Map map = (Map) value;
734 if (map.values().size() > 0) {
735 if (map.get(propertyName) != null) {
736 primaryValue = (String) map.get(propertyName);
741 logger.warn("Unexpected type for property " + propertyName
742 + " in multivalue list: not String or Map.");
749 * Gets a simple property from the document.
751 * For completeness, as this duplicates DocumentModel method.
753 * @param docModel The document model to get info from
754 * @param schema The name of the schema (part)
755 * @param propertyName The simple scalar property type
756 * @return property value as String
758 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
759 String xpath = "/"+schema+":"+propName;
761 return (String)docModel.getPropertyValue(xpath);
762 } catch(PropertyException pe) {
763 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
764 +pe.getLocalizedMessage());
765 } catch(ClassCastException cce) {
766 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
767 +cce.getLocalizedMessage());
768 } catch(Exception e) {
769 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
770 +e.getLocalizedMessage());
775 * Gets first of a repeating list of scalar values, as a String, from the document.
777 * @param docModel The document model to get info from
778 * @param schema The name of the schema (part)
779 * @param listName The name of the scalar list property
780 * @return first value in list, as a String, or empty string if the list is empty
782 protected String getFirstRepeatingStringProperty(
783 DocumentModel docModel, String schema, String listName) {
784 String xpath = "/"+schema+":"+listName+"/[0]";
786 return (String)docModel.getPropertyValue(xpath);
787 } catch(PropertyException pe) {
788 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
789 +pe.getLocalizedMessage());
790 } catch(IndexOutOfBoundsException ioobe) {
791 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
792 return ""; // gracefully handle missing elements
793 } catch(ClassCastException cce) {
794 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
795 +cce.getLocalizedMessage());
796 } catch(Exception e) {
797 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
798 +e.getLocalizedMessage());
804 * Gets first of a repeating list of scalar values, as a String, from the document.
806 * @param docModel The document model to get info from
807 * @param schema The name of the schema (part)
808 * @param listName The name of the scalar list property
809 * @return first value in list, as a String, or empty string if the list is empty
811 protected String getStringValueInPrimaryRepeatingComplexProperty(
812 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
813 String result = null;
815 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
817 result = (String)docModel.getPropertyValue(xpath);
818 } catch(PropertyException pe) {
819 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
820 +pe.getLocalizedMessage());
821 } catch(IndexOutOfBoundsException ioobe) {
822 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
823 result = ""; // gracefully handle missing elements
824 } catch(ClassCastException cce) {
825 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
826 +cce.getLocalizedMessage());
827 } catch(Exception e) {
828 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
829 +e.getLocalizedMessage());
836 * Gets XPath value from schema. Note that only "/" and "[n]" are
837 * supported for xpath. Can omit grouping elements for repeating complex types,
838 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
839 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
840 * If there are no entries for a list of scalars or for a list of complex types,
841 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
842 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
843 * that many elements in the list.
844 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
846 * @param docModel The document model to get info from
847 * @param schema The name of the schema (part)
848 * @param xpath The XPath expression (without schema prefix)
849 * @return value the indicated property value as a String
851 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
852 String schema, ListResultField field) {
853 Object result = null;
855 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
860 protected String getStringValue(DocumentModel docModel,
861 String schema, ListResultField field) {
862 String result = null;
864 Object value = getListResultValue(docModel, schema, field);
865 if (value != null && value instanceof String) {
866 String strValue = (String) value;
867 if (strValue.trim().isEmpty() == false) {
875 protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
879 private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
881 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
883 sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
885 sb.append(item.getPredicate());
887 sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
891 private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
892 StringBuilder sb = new StringBuilder();
894 if (list.size() > 0) {
895 sb.append("=========== " + label + " ==========" + CR);
897 for (RelationsCommonList.RelationListItem item : list) {
898 itemToString(sb, "== ", item);
901 return sb.toString();
904 /** @return null on parent not found
906 protected String getParentCSID(String thisCSID) throws Exception {
907 String parentCSID = null;
909 String predicate = RelationshipType.HAS_BROADER.value();
910 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
911 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
912 if (parentList != null) {
913 if (parentList.size() == 0) {
916 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
917 parentCSID = relationListItem.getObjectCsid();
920 } catch (Exception e) {
921 logger.error("Could not find parent for this: " + thisCSID, e);
926 protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
927 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
931 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
932 MultipartServiceContext ctx) throws Exception {
933 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
934 String parentCSID = getParentCSID(thisCSID);
935 if (parentCSID == null) {
936 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
940 String predicate = RelationshipType.HAS_BROADER.value();
941 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
942 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
944 List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
947 RelationsCommonList.RelationListItem item = null;
948 for (RelationsCommonList.RelationListItem sibling : siblingList) {
949 if (thisCSID.equals(sibling.getSubjectCsid())) {
950 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.
953 //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.
954 for (RelationsCommonList.RelationListItem self : toRemoveList) {
955 removeFromList(siblingList, self);
958 long siblingSize = siblingList.size();
959 siblingListOuter.setTotalItems(siblingSize);
960 siblingListOuter.setItemsInPage(siblingSize);
961 if(logger.isTraceEnabled()) {
962 String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
963 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
966 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
967 ctx.addOutputPart(relationsPart);
970 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
971 MultipartServiceContext ctx) throws Exception {
972 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
974 String predicate = RelationshipType.HAS_BROADER.value();
975 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
976 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
978 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
979 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
981 if(logger.isTraceEnabled()) {
982 String dump = dumpLists(thisCSID, parentList, childrenList, null);
983 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
986 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
987 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
988 //Not optimal, but that's the current design spec.
990 for (RelationsCommonList.RelationListItem parent : parentList) {
991 childrenList.add(parent);
994 long childrenSize = childrenList.size();
995 childrenListOuter.setTotalItems(childrenSize);
996 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
998 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
999 ctx.addOutputPart(relationsPart);
1002 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1003 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1005 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
1006 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1008 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
1009 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1011 if(logger.isTraceEnabled()) {
1012 String dump = dumpLists(thisCSID, subjectList, objectList, null);
1013 logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1016 subjectList.addAll(objectList);
1018 //now subjectList actually has records BOTH where thisCSID is subject and object.
1019 long relatedSize = subjectList.size();
1020 subjectListOuter.setTotalItems(relatedSize);
1021 subjectListOuter.setItemsInPage(relatedSize);
1023 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1024 ctx.addOutputPart(relationsPart);
1027 private String dumpLists(String itemCSID,
1028 List<RelationsCommonList.RelationListItem> parentList,
1029 List<RelationsCommonList.RelationListItem> childList,
1030 List<RelationsCommonList.RelationListItem> actionList) {
1031 StringBuilder sb = new StringBuilder();
1032 sb.append("itemCSID: " + itemCSID + CR);
1033 if(parentList!=null) {
1034 sb.append(dumpList(parentList, "parentList"));
1036 if(childList!=null) {
1037 sb.append(dumpList(childList, "childList"));
1039 if(actionList!=null) {
1040 sb.append(dumpList(actionList, "actionList"));
1042 return sb.toString();
1045 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
1046 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1047 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1048 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1049 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1050 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1051 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1053 RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1054 RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1055 return relationsCommonList;
1057 //============================= END TODO refactor ==========================
1059 // this method calls the RelationResource to have it create the relations and persist them.
1060 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1061 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1062 for (RelationsCommonList.RelationListItem item : inboundList) {
1063 RelationsCommon rc = new RelationsCommon();
1064 //rc.setCsid(item.getCsid());
1065 //todo: assignTo(item, rc);
1066 RelationsDocListItem itemSubject = item.getSubject();
1067 RelationsDocListItem itemObject = item.getObject();
1069 // Set at least one of CSID and refName for Subject and Object
1070 // Either value might be null for for each of Subject and Object
1071 String subjectCsid = itemSubject.getCsid();
1072 rc.setSubjectCsid(subjectCsid);
1074 String objCsid = itemObject.getCsid();
1075 rc.setObjectCsid(objCsid);
1077 rc.setSubjectRefName(itemSubject.getRefName());
1078 rc.setObjectRefName(itemObject.getRefName());
1080 rc.setRelationshipType(item.getPredicate());
1081 rc.setRelationshipMetaType(item.getRelationshipMetaType());
1082 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
1083 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
1085 // This is superfluous, since it will be fetched by the Relations Create logic.
1086 rc.setSubjectDocumentType(itemSubject.getDocumentType());
1087 rc.setObjectDocumentType(itemObject.getDocumentType());
1089 // This is superfluous, since it will be fetched by the Relations Create logic.
1090 rc.setSubjectUri(itemSubject.getUri());
1091 rc.setObjectUri(itemObject.getUri());
1092 // May not have the info here. Only really require CSID or refName.
1093 // Rest is handled in the Relation create mechanism
1094 //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1096 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1097 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1098 payloadOut.addPart(outputPart);
1099 RelationResource relationResource = new RelationResource();
1100 Response res = relationResource.create(ctx, ctx.getResourceMap(),
1101 ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
1105 // Note that item2 may be sparse (only refName, no CSID for subject or object)
1106 // But item1 must not be sparse
1107 private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1108 if (item1 == null || item2 == null) {
1111 RelationsDocListItem subj1 = item1.getSubject();
1112 RelationsDocListItem subj2 = item2.getSubject();
1113 RelationsDocListItem obj1 = item1.getObject();
1114 RelationsDocListItem obj2 = item2.getObject();
1116 String subj1Csid = subj1.getCsid();
1117 String subj2Csid = subj2.getCsid();
1118 String subj1RefName = subj1.getRefName();
1119 String subj2RefName = subj2.getRefName();
1121 String obj1Csid = obj1.getCsid();
1122 String obj2Csid = obj2.getCsid();
1123 String obj1RefName = obj1.getRefName();
1124 String obj2RefName = obj2.getRefName();
1126 String item1Metatype = item1.getRelationshipMetaType();
1127 item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1129 String item2Metatype = item2.getRelationshipMetaType();
1130 item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1132 boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null) && subj1RefName.equals(subj2RefName)))
1133 && (obj1Csid.equals(obj2Csid) || ((obj2Csid==null) && obj1RefName.equals(obj2RefName)))
1134 // predicate is proper, but still allow relationshipType
1135 && (item1.getPredicate().equals(item2.getPredicate())
1136 || ((item2.getPredicate()==null) && item1.getRelationshipType().equals(item2.getRelationshipType())))
1137 // Allow missing docTypes, so long as they do not conflict
1138 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1139 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1140 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1144 // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1145 // But the list items must not be sparse
1146 private RelationsCommonList.RelationListItem findInList(
1147 List<RelationsCommonList.RelationListItem> list,
1148 RelationsCommonList.RelationListItem item) {
1149 RelationsCommonList.RelationListItem foundItem = null;
1150 for (RelationsCommonList.RelationListItem listItem : list) {
1151 if (itemsEqual(listItem, item)) { //equals must be defined, else
1152 foundItem = listItem;
1159 /** updateRelations strategy:
1162 go through inboundList, remove anything from childList that matches from childList
1163 go through inboundList, remove anything from parentList that matches from parentList
1164 go through parentList, delete all remaining
1165 go through childList, delete all remaining
1166 go through actionList, add all remaining.
1167 check for duplicate children
1168 check for more than one parent.
1170 inboundList parentList childList actionList
1171 ---------------- --------------- ---------------- ----------------
1172 child-a parent-c child-a child-b
1173 child-b parent-d child-c
1178 private RelationsCommonList updateRelations(
1179 String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1181 if (logger.isTraceEnabled()) {
1182 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1184 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
1186 return null; //nothing to do--they didn't send a list of relations.
1188 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1189 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1190 List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1191 List<RelationsCommonList.RelationListItem> childList = null;
1192 List<RelationsCommonList.RelationListItem> parentList = null;
1193 DocumentModel docModel = wrapDoc.getWrappedObject();
1194 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1195 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1197 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1198 //Do magic replacement of ${itemCSID} and fix URI's.
1199 fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1201 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1202 UriInfo uriInfo = ctx.getUriInfo();
1203 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1206 //Run getList() once as sent to get childListOuter:
1207 String predicate = RelationshipType.HAS_BROADER.value();
1208 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1209 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1210 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1211 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1212 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1214 RelationResource relationResource = new RelationResource();
1215 RelationsCommonList childListOuter = relationResource.getList(ctx); // Knows all query params because they are in the context.
1217 //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1218 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1219 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1220 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1221 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1224 childList = childListOuter.getRelationListItem();
1225 parentList = parentListOuter.getRelationListItem();
1227 if (parentList.size() > 1) {
1228 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1231 if (logger.isTraceEnabled()) {
1232 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1236 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1237 // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1238 // and so the CSID for those may be null
1239 if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1240 // Look for parents and children
1241 if(itemCSID.equals(inboundItem.getObject().getCsid())
1242 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1243 //then this is an item that says we have a child. That child is inboundItem
1244 RelationsCommonList.RelationListItem childItem =
1245 (childList == null) ? null : findInList(childList, inboundItem);
1246 if (childItem != null) {
1247 if (logger.isTraceEnabled()) {
1248 StringBuilder sb = new StringBuilder();
1249 itemToString(sb, "== Child: ", childItem);
1250 logger.trace("Found inboundChild in current child list: " + sb.toString());
1252 removeFromList(childList, childItem); //exists, just take it off delete list
1254 if (logger.isTraceEnabled()) {
1255 StringBuilder sb = new StringBuilder();
1256 itemToString(sb, "== Child: ", inboundItem);
1257 logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1259 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
1260 String newChildCsid = inboundItem.getSubject().getCsid();
1261 if(newChildCsid == null) {
1262 String newChildRefName = inboundItem.getSubject().getRefName();
1263 if(newChildRefName==null) {
1264 throw new RuntimeException("Child with no CSID or refName!");
1266 if (logger.isTraceEnabled()) {
1267 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1269 DocumentModel newChildDocModel =
1270 ResourceBase.getDocModelForRefName(this.getRepositorySession(),
1271 newChildRefName, getServiceContext().getResourceMap());
1272 newChildCsid = getCsid(newChildDocModel);
1274 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1277 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1278 || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1279 //then this is an item that says we have a parent. inboundItem is that parent.
1280 RelationsCommonList.RelationListItem parentItem =
1281 (parentList == null) ? null : findInList(parentList, inboundItem);
1282 if (parentItem != null) {
1283 removeFromList(parentList, parentItem); //exists, just take it off delete list
1285 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
1288 logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1291 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1294 if (logger.isTraceEnabled()) {
1295 String dump = dumpLists(itemCSID, parentList, childList, actionList);
1296 logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1299 if (logger.isTraceEnabled()) {
1300 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1301 + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1303 deleteRelations(parentList, ctx, "parentList"); //todo: there are items appearing on both lists....april 20.
1304 deleteRelations(childList, ctx, "childList");
1306 if (logger.isTraceEnabled()) {
1307 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1308 + actionList.size() + " new parents and children.");
1310 createRelations(actionList, ctx);
1311 if (logger.isTraceEnabled()) {
1312 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1314 //We return all elements on the inbound list, since we have just worked to make them exist in the system
1315 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1316 return relationsCommonListBody;
1319 /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1320 * and sets URI correctly for related items.
1321 * Operates directly on the items in the list. Does not change the list ordering, does not add or remove any items.
1323 protected void fixupInboundListItems(ServiceContext ctx,
1324 List<RelationsCommonList.RelationListItem> inboundList,
1325 DocumentModel docModel,
1326 String itemCSID) throws Exception {
1327 String thisURI = this.getUri(docModel);
1328 // WARNING: the two code blocks below are almost identical and seem to ask to be put in a generic method.
1329 // beware of the little diffs in inboundItem.setObjectCsid(itemCSID); and inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1330 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1331 RelationsDocListItem inboundItemObject = inboundItem.getObject();
1332 RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1334 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1335 inboundItem.setObjectCsid(itemCSID);
1336 inboundItemObject.setCsid(itemCSID);
1337 //inboundItemObject.setUri(getUri(docModel));
1340 String objectCsid = inboundItemObject.getCsid();
1341 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid); //null if not found.
1342 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1343 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1344 inboundItemObject.setUri(uri); //CSPACE-4037
1347 //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri()); //CSPACE-4042
1349 if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1350 inboundItem.setSubjectCsid(itemCSID);
1351 inboundItemSubject.setCsid(itemCSID);
1352 //inboundItemSubject.setUri(getUri(docModel));
1355 String subjectCsid = inboundItemSubject.getCsid();
1356 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid); //null if not found.
1357 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1358 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1359 inboundItemSubject.setUri(uri); //CSPACE-4037
1362 //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri()); //CSPACE-4042
1367 private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1368 MultivaluedMap<String, String> queryParams, String childCSID) {
1369 logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1370 queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1371 queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1372 queryParams.putSingle(IRelationsManager.OBJECT_QP, null); //null means ANY
1374 RelationResource relationResource = new RelationResource();
1375 RelationsCommonList parentListOuter = relationResource.getList(ctx);
1376 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1377 //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1378 deleteRelations(parentList, ctx, "parentList-delete");
1381 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1382 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1385 for (RelationsCommonList.RelationListItem item : list) {
1386 RelationResource relationResource = new RelationResource();
1387 if(logger.isTraceEnabled()) {
1388 StringBuilder sb = new StringBuilder();
1389 itemToString(sb, "==== TO DELETE: ", item);
1390 logger.trace(sb.toString());
1392 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1393 if (logger.isDebugEnabled()) {
1394 logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1397 } catch (Throwable t) {
1398 String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1403 // Note that we must do this after we have completed the Update, so that the repository has the
1404 // info for the item itself. The relations code must call into the repo to get info for each end.
1405 // This could be optimized to pass in the parent docModel, since it will often be one end.
1406 // Nevertheless, we should complete the item save before we do work on the relations, especially
1407 // since a save on Create might fail, and we would not want to create relations for something
1408 // that may not be created...
1409 private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1410 ServiceContext ctx = getServiceContext();
1411 PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1412 DocumentModel documentModel = (wrapDoc.getWrappedObject());
1413 String itemCsid = documentModel.getName();
1415 //Updates relations part
1416 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1418 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
1419 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1421 //now we add part for relations list
1422 //ServiceContext ctx = getServiceContext();
1423 //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1424 ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1428 * Checks to see if the refName has changed, and if so,
1429 * uses utilities to find all references and update them to use the new refName.
1432 protected void handleRefNameReferencesUpdate() throws Exception {
1433 if (hasRefNameUpdate() == true) {
1434 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1435 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1436 RepositoryInstance repoSession = this.getRepositorySession();
1438 // Update all the relationship records that referred to the old refName
1439 RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1440 oldRefNameOnUpdate, newRefNameOnUpdate);
1444 protected String getRefNameUpdate() {
1445 String result = null;
1447 if (hasRefNameUpdate() == true) {
1448 result = newRefNameOnUpdate;
1449 if (logger.isDebugEnabled() == true) {
1450 logger.debug(String.format("There was a refName update. New: %s Old: %s" ,
1451 newRefNameOnUpdate, oldRefNameOnUpdate));
1459 * Note: The Vocabulary document handler overrides this method.
1461 protected String getRefPropName() {
1462 return ServiceBindingUtils.AUTH_REF_PROP;