]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
52166f15623006dafe6b35daf5d4c2b07cd2f6d8
[tmp/jakarta-migration.git] /
1 /**
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:
5
6  *  http://www.collectionspace.org
7  *  http://wiki.collectionspace.org
8
9  *  Copyright 2009 University of California at Berkeley
10
11  *  Licensed under the Educational Community License (ECL), Version 2.0.
12  *  You may not use this file except in compliance with this License.
13
14  *  You may obtain a copy of the ECL 2.0 License at
15
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt
17
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.
23  */
24 package org.collectionspace.services.nuxeo.client.java;
25
26 import java.net.HttpURLConnection;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Map.Entry;
34 import java.util.Set;
35
36 import javax.ws.rs.core.MediaType;
37 import javax.ws.rs.core.MultivaluedMap;
38 import javax.ws.rs.core.Response;
39 import javax.ws.rs.core.UriInfo;
40 import javax.xml.bind.JAXBElement;
41
42 import org.collectionspace.authentication.AuthN;
43 import org.collectionspace.services.authorization.AccountPermission;
44 import org.collectionspace.services.jaxb.AbstractCommonList;
45 import org.collectionspace.services.lifecycle.TransitionDef;
46 import org.collectionspace.services.client.AccountClient;
47 import org.collectionspace.services.client.CollectionSpaceClient;
48 import org.collectionspace.services.client.PayloadInputPart;
49 import org.collectionspace.services.client.PayloadOutputPart;
50 import org.collectionspace.services.client.PoxPayloadIn;
51 import org.collectionspace.services.client.PoxPayloadOut;
52 import org.collectionspace.services.client.Profiler;
53 import org.collectionspace.services.client.RelationClient;
54 import org.collectionspace.services.client.workflow.WorkflowClient;
55 import org.collectionspace.services.common.CSWebApplicationException;
56 import org.collectionspace.services.common.NuxeoBasedResource;
57 import org.collectionspace.services.common.ServiceException;
58 import org.collectionspace.services.common.authorityref.AuthorityRefList;
59 import org.collectionspace.services.common.config.ServiceConfigUtils;
60 import org.collectionspace.services.common.context.JaxRsContext;
61 import org.collectionspace.services.common.context.MultipartServiceContext;
62 import org.collectionspace.services.common.context.ServiceBindingUtils;
63 import org.collectionspace.services.common.context.ServiceContext;
64 import org.collectionspace.services.common.document.BadRequestException;
65 import org.collectionspace.services.common.document.DocumentException;
66 import org.collectionspace.services.common.document.DocumentUtils;
67 import org.collectionspace.services.common.document.DocumentWrapper;
68 import org.collectionspace.services.common.document.DocumentFilter;
69 import org.collectionspace.services.client.IRelationsManager;
70 import org.collectionspace.services.common.relation.RelationResource;
71 import org.collectionspace.services.common.repository.RepositoryClient;
72 import org.collectionspace.services.common.security.SecurityUtils;
73 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
74 import org.collectionspace.services.common.api.CommonAPI;
75 import org.collectionspace.services.common.api.RefNameUtils;
76 import org.collectionspace.services.common.api.Tools;
77 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
78 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
79 import org.collectionspace.services.config.service.DocHandlerParams;
80 import org.collectionspace.services.config.service.ListResultField;
81 import org.collectionspace.services.config.service.ObjectPartType;
82 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
83 import org.collectionspace.services.relation.RelationsCommon;
84 import org.collectionspace.services.relation.RelationsCommonList;
85 import org.collectionspace.services.relation.RelationsDocListItem;
86 import org.collectionspace.services.relation.RelationshipType;
87
88 import org.dom4j.Element;
89 import org.nuxeo.ecm.core.api.DocumentModel;
90 import org.nuxeo.ecm.core.api.DocumentModelList;
91 import org.nuxeo.ecm.core.api.impl.DataModelImpl;
92 import org.nuxeo.ecm.core.api.model.DocumentPart;
93 import org.nuxeo.ecm.core.api.model.Property;
94 import org.nuxeo.ecm.core.api.model.PropertyException;
95 import org.nuxeo.ecm.core.api.model.impl.ScalarProperty;
96 import org.slf4j.Logger;
97 import org.slf4j.LoggerFactory;
98
99 /**
100  * RemoteDocumentModelHandler
101  *
102  * $LastChangedRevision: $
103  * $LastChangedDate: $
104  * @param <T> 
105  * @param <TL> 
106  */
107 public abstract class   RemoteDocumentModelHandlerImpl<T, TL>
108         extends DocumentModelHandler<T, TL> {
109
110     /** The logger. */
111     private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
112     private final static String CR = "\r\n";
113     private final static String EMPTYSTR = "";
114         private static final String COLLECTIONSPACE_CORE_SCHEMA = CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA;
115         private static final String ACCOUNT_PERMISSION_COMMON_PART_NAME = AccountClient.SERVICE_COMMON_PART_NAME;
116     
117     /* (non-Javadoc)
118      * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
119      */
120     @Override
121     public void setServiceContext(ServiceContext ctx) {  //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
122         if (ctx instanceof MultipartServiceContext) {
123             super.setServiceContext(ctx);
124         } else {
125             throw new IllegalArgumentException("setServiceContext requires instance of "
126                     + MultipartServiceContext.class.getName());
127         }
128     }
129     
130     @Override
131     protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
132         return getRefnameDisplayName(docWrapper.getWrappedObject());
133     }
134         
135         private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
136                 String result = null;
137                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
138                 
139         DocHandlerParams.Params params = null;
140         try {
141                         params = ServiceConfigUtils.getDocHandlerParams(ctx);
142                         ListResultField field = params.getRefnameDisplayNameField();
143                         
144                         String schema = field.getSchema();
145                         if (schema == null || schema.trim().isEmpty()) {
146                                 schema = ctx.getCommonPartLabel();
147                         }
148                         
149                         result = getStringValue(docModel, schema, field);
150                 } catch (Exception e) {
151                         if (logger.isWarnEnabled()) {
152                                 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
153                         }
154                 }
155                 
156                 return result;
157         }
158         
159     @Override
160     public boolean supportsHierarchy() {
161         boolean result = false;
162         
163         DocHandlerParams.Params params = null;
164         try {
165                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
166                         params = ServiceConfigUtils.getDocHandlerParams(ctx);
167                         Boolean bool = params.isSupportsHierarchy();
168                         if (bool != null) {
169                                 result = bool.booleanValue();
170                         }
171                 } catch (DocumentException e) {
172                         // TODO Auto-generated catch block
173                         String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
174                         if (logger.isWarnEnabled() == true) {
175                                 logger.warn(errMsg);
176                         }
177                 }
178         
179         return result;
180     }
181     
182     @Override
183     public boolean supportsVersioning() {
184         boolean result = false;
185         
186         DocHandlerParams.Params params = null;
187         try {
188                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
189                         params = ServiceConfigUtils.getDocHandlerParams(ctx);
190                         Boolean bool = params.isSupportsVersioning();
191                         if (bool != null) {
192                                 result = bool.booleanValue();
193                         }
194                 } catch (DocumentException e) {
195                         // TODO Auto-generated catch block
196                         String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
197                         if (logger.isWarnEnabled() == true) {
198                                 logger.warn(errMsg);
199                         }
200                 }
201         
202         return result;
203     }
204     
205         @Override
206         public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
207                         throws Exception {
208                 // Do nothing by default, but children can override if they want.  The real workflow transition happens in the WorkflowDocumentModelHandler class
209         }
210         
211     @Override
212     public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
213         super.completeCreate(wrapDoc);
214         if (supportsHierarchy() == true) {
215                 handleRelationsPayload(wrapDoc, false);
216         }
217     }
218         
219     @Override
220     public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
221         DocumentModel docModel = wrapDoc.getWrappedObject();
222         String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
223         
224         if (docModel.getCurrentLifeCycleState().contains(workflowState) == true) {
225                 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
226                     "Cannot UPDATE a resource/record if it is in the workflow state: " + workflowState);
227         } else {
228                 super.handleUpdate(wrapDoc);
229         }
230     }
231     
232     @Override
233     public boolean handleDelete(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
234         DocumentModel docModel = wrapDoc.getWrappedObject();
235         String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
236         
237         if (docModel.getCurrentLifeCycleState().contains(workflowState) == true) {
238                 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
239                     "Cannot DELETE a resource/record if it is in the workflow state: " + workflowState);
240         } else {
241                 return super.handleDelete(wrapDoc);
242         }
243     }    
244     
245     /* NOTE: The authority item doc handler overrides (after calling) this method.  It performs refName updates.  In this
246      * method we just update any and all relationship records that use refNames that have changed.
247      * (non-Javadoc)
248      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
249      */
250     @Override
251     public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
252         DocumentModel docModel = wrapDoc.getWrappedObject();
253         
254         String[] schemas = docModel.getDeclaredSchemas();
255         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
256         for (String schema : schemas) {
257             ObjectPartType partMeta = partsMetaMap.get(schema);
258             if (partMeta == null) {
259                 continue; // unknown part, ignore
260             }
261             Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
262             if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
263                 addExtraCoreValues(docModel, unQObjectProperties);
264             }
265             addOutputPart(unQObjectProperties, schema, partMeta);
266         }
267
268         //
269         //  If the resource's service supports hierarchy then we need to perform a little more work
270         //
271         if (supportsHierarchy() == true) {
272             handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
273             handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
274         }
275     }
276
277     /**
278      * Adds the output part.
279      *
280      * @param unQObjectProperties the un q object properties
281      * @param schema the schema
282      * @param partMeta the part meta
283      * @throws Exception the exception
284      * MediaType.APPLICATION_XML_TYPE
285      */
286     protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
287             throws Exception {
288         Element doc = DocumentUtils.buildDocument(partMeta, schema,
289                 unQObjectProperties);
290         if (logger.isTraceEnabled() == true) {
291             logger.trace(doc.asXML());
292         }
293         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
294         ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
295     }
296     
297     /**
298      * Extract paging info.
299      *
300      * @param commonsList the commons list
301      * @return the tL
302      * @throws Exception the exception
303      */
304     public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
305             throws Exception {
306         AbstractCommonList commonList = (AbstractCommonList) theCommonList;
307
308         DocumentFilter docFilter = this.getDocumentFilter();
309         long pageSize = docFilter.getPageSize();
310         long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
311         // set the page size and page number
312         commonList.setPageNum(pageNum);
313         commonList.setPageSize(pageSize);
314         DocumentModelList docList = wrapDoc.getWrappedObject();
315         // Set num of items in list. this is useful to our testing framework.
316         commonList.setItemsInPage(docList.size());
317         // set the total result size
318         commonList.setTotalItems(docList.totalSize());
319
320         return (TL) commonList;
321     }
322     
323     /**
324      * Extract paging info.
325      *
326      * @param commonsList the commons list
327      * @return the tL
328      * @throws Exception the exception
329      */
330     public TL extractPagingInfo(TL theCommonList, DocumentModelList docList)
331             throws Exception {
332         AbstractCommonList commonList = (AbstractCommonList) theCommonList;
333
334         DocumentFilter docFilter = this.getDocumentFilter();
335         long pageSize = docFilter.getPageSize();
336         long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
337         // set the page size and page number
338         commonList.setPageNum(pageNum);
339         commonList.setPageSize(pageSize);
340         // Set num of items in list. this is useful to our testing framework.
341         commonList.setItemsInPage(docList.size());
342         // set the total result size
343         commonList.setTotalItems(docList.totalSize());
344
345         return (TL) commonList;
346     }    
347
348     /* (non-Javadoc)
349      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
350      */
351     @Override
352     public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
353             throws Exception {
354
355         DocumentModel docModel = wrapDoc.getWrappedObject();
356         String[] schemas = docModel.getDeclaredSchemas();
357         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
358         for (String schema : schemas) {
359             ObjectPartType partMeta = partsMetaMap.get(schema);
360             if (partMeta == null) {
361                 continue; // unknown part, ignore
362             }
363             Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
364             if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
365                 addExtraCoreValues(docModel, unQObjectProperties);
366             }
367             addOutputPart(unQObjectProperties, schema, partMeta);
368         }
369         
370         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
371
372         if (supportsHierarchy() == true) {
373             String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
374             if (Tools.isTrue(showSiblings)) {
375                 showSiblings(wrapDoc, ctx);
376                 return;   // actual result is returned on ctx.addOutputPart();
377             }
378
379             String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
380             if (Tools.isTrue(showRelations)) {
381                 showRelations(wrapDoc, ctx);
382                 return;   // actual result is returned on ctx.addOutputPart();
383             }
384
385             String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
386             if (Tools.isTrue(showAllRelations)) {
387                 showAllRelations(wrapDoc, ctx);
388                 return;   // actual result is returned on ctx.addOutputPart();
389             }
390         }
391         
392         String currentUser = ctx.getUserId();
393         if (currentUser.equalsIgnoreCase(AuthN.ANONYMOUS_USER) == false &&
394                         currentUser.equalsIgnoreCase(AuthN.SPRING_ADMIN_USER) == false) {
395                 addAccountPermissionsPart();
396         }
397     }
398     
399     private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
400                 throws Exception {
401         unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
402     }
403     
404     private void addAccountPermissionsPart() throws Exception {
405         Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
406         profiler.start();
407         
408         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
409         String currentServiceName = ctx.getServiceName();
410         String workflowSubResource = "/";
411         JaxRsContext jaxRsContext = ctx.getJaxRsContext();
412         if (jaxRsContext != null) { // If not null then we're dealing with an authority item
413                 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
414                 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
415         } else {
416                 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
417         }
418         AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
419                         currentServiceName, workflowSubResource);
420         org.collectionspace.services.authorization.ObjectFactory objectFactory =
421                 new org.collectionspace.services.authorization.ObjectFactory();
422         JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
423         PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
424         ctx.addOutputPart(accountPermissionPart);
425         
426         profiler.stop();
427     }
428
429     /* (non-Javadoc)
430      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
431      */
432     @Override
433     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
434
435         //TODO filling extension parts should be dynamic
436         //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
437         //not an ideal way of populating objects.
438         DocumentModel docModel = wrapDoc.getWrappedObject();
439         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
440         PoxPayloadIn input = ctx.getInput();
441         if (input == null || input.getParts().isEmpty()) {
442             String msg = String.format("No payload found for '%s' action.", action);
443             logger.error(msg + "Ctx=" + getServiceContext().toString());
444             throw new BadRequestException(msg);
445         }
446
447         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
448
449         //iterate over parts received and fill those parts
450         boolean werePartsFilled = false;
451         List<PayloadInputPart> inputParts = input.getParts();
452         for (PayloadInputPart part : inputParts) {
453
454             String partLabel = part.getLabel();
455             if (partLabel == null) {
456                 String msg = "Part label is missing or empty!";
457                 logger.error(msg + "Ctx=" + getServiceContext().toString());
458                 throw new BadRequestException(msg);
459             }
460
461             //skip if the part is not in metadata or if it is a system part
462             ObjectPartType partMeta = partsMetaMap.get(partLabel);
463             if (partMeta == null || isSystemPart(partLabel)) {
464                 continue;
465             }
466             fillPart(part, docModel, partMeta, action, ctx);
467             werePartsFilled = true;
468         }
469         
470         if (logger.isTraceEnabled() && werePartsFilled == false) {
471                 String msg = String.format("%s request had no XML payload parts processed in the request.  Could be a payload with only relations-common-list request.", 
472                                 action.toString());
473                 logger.trace(msg);
474         }
475     }
476
477     private boolean isSystemPart(String partLabel) {
478         boolean result = false;
479         
480         if (partLabel != null && (partLabel.equalsIgnoreCase(COLLECTIONSPACE_CORE_SCHEMA) ||
481                         partLabel.equalsIgnoreCase(ACCOUNT_PERMISSION_COMMON_PART_NAME))) {
482                 result = true;
483         }
484         
485                 return result;
486         }
487
488         /**
489      * fillPart fills an XML part into given document model
490      * @param part to fill
491      * @param docModel for the given object
492      * @param partMeta metadata for the object to fill
493      * @throws Exception
494      */
495         protected void fillPart(PayloadInputPart part, DocumentModel docModel, ObjectPartType partMeta, Action action,
496                         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
497                 // check if this is an xml part
498                 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
499                         Element element = part.getElementBody();
500                         Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
501                         if (action == Action.UPDATE) {
502                                 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
503                         }
504                         String schemaName = partMeta.getLabel();
505                         docModel.setProperties(schemaName, objectProps);
506                         Set<String> fieldNameSet = getFieldNamesSet(objectProps);
507                         setFieldsDirty(docModel, schemaName, fieldNameSet);  // Force Nuxeo to update EVERY field by marking them dirty/modified
508                 }
509         }
510         
511         
512         /**
513          * Due to an apparent Nuxeo bug, we need to explicitly mark all fields as dirty for updates
514          * to take place properly.  See https://issues.collectionspace.org/browse/CSPACE-7124
515          * 
516          * We should improve this code by marking dirty/modified only the fields that were part of the UPDATE/PUT request
517          * payload.  The current code will mark any field with a name in the 'fieldNameSet' set.  Since field names do not
518          * have to be unique in document/record, we might end up unnecessarily marking some fields as modified -not ideal but
519          * better than brute-force marking ALL fields as modified.
520          * 
521          * @param docModel
522          * @param schemaName
523          */
524         private void setFieldsDirty(DocumentModel docModel, String schemaName, Set<String> fieldNameSet) {
525         DataModelImpl dataModel = (DataModelImpl) docModel.getDataModel(schemaName);
526         DocumentPart documentPart = dataModel.getDocumentPart();
527         setFieldsDirty(documentPart.getChildren(), fieldNameSet);
528     }
529         
530         /**
531          * Recursively step through all the children and sub-children setting their dirty flags.
532          * See https://issues.collectionspace.org/browse/CSPACE-7124
533          * @param children
534          */
535         private void setFieldsDirty(Collection<Property> children, Set<String> fieldNameSet) {
536                 if (children != null && (children.size() > 0)) {
537                         for (Property prop : children ) {
538                                 String propName = prop.getName();
539                                 logger.debug(propName);
540                     if (prop.isPhantom() == false) {
541                         if (prop.isScalar() == false) {
542                                 setFieldsDirty(prop.getChildren(), fieldNameSet);
543                         } else if (fieldNameSet.contains(propName)) {
544                                 ScalarProperty scalarProp = (ScalarProperty)prop;
545                                 scalarProp.setIsModified();
546                         }
547                     }
548                 }
549                 }
550         }
551         
552         /*
553          * Gets the set of field names used in a map of properties.  We'll use this make our workaround to a
554          * bug (See https://issues.collectionspace.org/browse/CSPACE-7124) more efficient by only marking fields
555          * with names in this set as modified -ie, needing persisting.
556          * 
557          * Since the map of properties can contain other maps of properties as values, there can be duplicate
558          * field names in the 'objectProps' map.  And since we're creating a set, there can be no duplicates in the result.
559          * 
560          */
561         private Set<String> getFieldNamesSet(Map<String, Object> objectProps) {
562                 HashSet<String> result = new HashSet<String>();
563                 
564                 addFieldNamesToSet(result, objectProps);
565                 
566                 return result;
567         }
568         
569         private void addFieldNamesToSet(Set<String> fieldNameSet, List<Object> valueList) {
570                 for (Object value : valueList) {
571                         if (value instanceof List) {
572                                 addFieldNamesToSet(fieldNameSet, (List<Object>)value);
573                         } else if (value instanceof Map) {
574                                 addFieldNamesToSet(fieldNameSet, (Map<String, Object>)value);
575                         } else {
576                                 logger.debug(value.toString());
577                         }
578                 }
579         }
580                 
581         private void addFieldNamesToSet(Set<String> fieldNameSet, Map<String, Object> objectProps) {
582                 for (String key : objectProps.keySet()) {
583                         Object value = objectProps.get(key);
584                         if (value instanceof Map) {
585                                 addFieldNamesToSet(fieldNameSet, (Map<String, Object>)value);
586                         } else if (value instanceof List) {
587                                 addFieldNamesToSet(fieldNameSet, (List)value);
588                         } else {
589                                 fieldNameSet.add(key);
590                         }
591                 }
592         }
593         
594     /**
595      * Filters out read only properties, so they cannot be set on update.
596      * TODO: add configuration support to do this generally
597      * @param objectProps the properties parsed from the update payload
598      * @param partMeta metadata for the object to fill
599      */
600     public void filterReadOnlyPropertiesForPart(
601             Map<String, Object> objectProps, ObjectPartType partMeta) {
602         // Should add in logic to filter most of the core items on update
603         if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
604                 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
605                 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
606                 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
607                 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
608                 // Note that the updatedAt/updatedBy fields are set internally
609                 // in DocumentModelHandler.handleCoreValues().
610         }
611     }
612
613     /**
614      * extractPart extracts an XML object from given DocumentModel
615      * @param docModel
616      * @param schema of the object to extract
617      * @param partMeta metadata for the object to extract
618      * @throws Exception
619      */
620     protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
621             throws Exception {
622         return extractPart(docModel, schema, (Map<String, Object>)null);
623     }
624     
625     /**
626      * extractPart extracts an XML object from given DocumentModel
627      * @param docModel
628      * @param schema of the object to extract
629      * @param partMeta metadata for the object to extract
630      * @throws Exception
631      */
632     @Deprecated
633     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
634             throws Exception {
635         return extractPart(docModel, schema, partMeta, null);
636     }    
637
638     /**
639      * extractPart extracts an XML object from given DocumentModel
640      * @param docModel
641      * @param schema of the object to extract
642      * @param partMeta metadata for the object to extract
643      * @throws Exception
644      */
645     protected Map<String, Object> extractPart(
646             DocumentModel docModel, 
647             String schema,
648             Map<String, Object> addToMap)
649             throws Exception {
650         Map<String, Object> result = null;
651
652         Map<String, Object> objectProps = docModel.getProperties(schema);
653         if (objectProps != null) {
654                 //unqualify properties before sending the doc over the wire (to save bandwidh)
655                 //FIXME: is there a better way to avoid duplication of a Map/Collection?
656                 Map<String, Object> unQObjectProperties =
657                         (addToMap != null) ? addToMap : (new HashMap<String, Object>());
658                 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
659                 for (Entry<String, Object> entry : qualifiedEntries) {
660                     String unqProp = getUnQProperty(entry.getKey());
661                     unQObjectProperties.put(unqProp, entry.getValue());
662                 }
663                 result = unQObjectProperties;
664         }
665
666         return result;
667     }
668     
669     /**
670      * extractPart extracts an XML object from given DocumentModel
671      * @param docModel
672      * @param schema of the object to extract
673      * @param partMeta metadata for the object to extract
674      * @throws Exception
675      */
676     @Deprecated
677     protected Map<String, Object> extractPart(
678             DocumentModel docModel, String schema, ObjectPartType partMeta,
679             Map<String, Object> addToMap)
680             throws Exception {
681         Map<String, Object> result = null;
682
683         result = this.extractPart(docModel, schema, addToMap);
684
685         return result;
686     }
687     
688     /* 
689     public String getStringPropertyFromDoc(
690                 ServiceContext ctx,
691                 String csid,
692                 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
693         RepositoryInstance repoSession = null;
694         boolean releaseRepoSession = false;
695         String returnValue = null;
696
697         try{ 
698                 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
699                 repoSession = this.getRepositorySession();
700                 if (repoSession == null) {
701                         repoSession = repoClient.getRepositorySession();
702                         releaseRepoSession = true;
703                 }
704
705                 try {
706                         DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
707                         DocumentModel docModel = wrapper.getWrappedObject();
708                         returnValue = (String) docModel.getPropertyValue(propertyXPath);
709                 } catch (PropertyException pe) {
710                         throw pe;
711                 } catch (DocumentException de) {
712                         throw de;
713                 } catch (Exception e) {
714                         if (logger.isDebugEnabled()) {
715                                 logger.debug("Caught exception ", e);
716                         }
717                         throw new DocumentException(e);
718                 } finally {
719                         if (releaseRepoSession && repoSession != null) {
720                                 repoClient.releaseRepositorySession(repoSession);
721                         }
722                 }
723         } catch (Exception e) {
724                 if (logger.isDebugEnabled()) {
725                         logger.debug("Caught exception ", e);
726                 }
727                 throw new DocumentException(e);
728         }               
729
730
731         if (logger.isWarnEnabled() == true) {
732                 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
733         }
734         return returnValue;
735     }
736      */
737
738     
739
740     /* (non-Javadoc)
741      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
742      */
743     @Override
744     public AuthorityRefList getAuthorityRefs(
745             String csid,
746             List<AuthRefConfigInfo> authRefConfigInfoList) throws PropertyException, Exception {
747
748         AuthorityRefList authRefList = new AuthorityRefList();
749         AbstractCommonList commonList = (AbstractCommonList) authRefList;
750         
751         DocumentFilter docFilter = this.getDocumentFilter();
752         long pageSize = docFilter.getPageSize();
753         long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
754         // set the page size and page number
755         commonList.setPageNum(pageNum);
756         commonList.setPageSize(pageSize);
757         
758         List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
759
760         try {
761                 int iFirstToUse = (int)(pageSize*pageNum);
762                 int nFoundInPage = 0;
763                 int nFoundTotal = 0;
764                 
765                 ArrayList<RefNameServiceUtils.AuthRefInfo> foundReferences 
766                         = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
767                 
768                 boolean releaseRepoSession = false;
769                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
770                 NuxeoRepositoryClientImpl repoClient = (NuxeoRepositoryClientImpl)this.getRepositoryClient(ctx);
771                 CoreSessionInterface repoSession = this.getRepositorySession();
772                 if (repoSession == null) {
773                         repoSession = repoClient.getRepositorySession(ctx);
774                         releaseRepoSession = true;
775                         this.setRepositorySession(repoSession); // we (the doc handler) should keep track of this repository session in case we need it
776                 }
777                 
778                 try {
779                         DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
780                         RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefConfigInfoList, null, foundReferences);
781                         // Slightly goofy pagination support - how many refs do we expect from one object?
782                         for(RefNameServiceUtils.AuthRefInfo ari:foundReferences) {
783                                 if ((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
784                                         if(appendToAuthRefsList(ari, list)) {
785                                                 nFoundInPage++;
786                                         nFoundTotal++;
787                                         }
788                                 } else {
789                                         nFoundTotal++;
790                                 }
791                         }
792                 } finally {
793                         if (releaseRepoSession == true) {
794                                 repoClient.releaseRepositorySession(ctx, repoSession);
795                         }
796                 }
797                 
798             // Set num of items in list. this is useful to our testing framework.
799             commonList.setItemsInPage(nFoundInPage);
800             // set the total result size
801             commonList.setTotalItems(nFoundTotal);
802             
803         } catch (PropertyException pe) {
804             String msg = "Attempted to retrieve value for invalid or missing authority field. "
805                     + "Check authority field properties in tenant bindings.";
806             logger.warn(msg, pe);
807             throw pe;
808         } catch (Exception e) {
809             if (logger.isDebugEnabled()) {
810                 logger.debug("Caught exception in getAuthorityRefs", e);
811             }
812             Response response = Response.status(
813                     Response.Status.INTERNAL_SERVER_ERROR).entity(
814                     "Failed to retrieve authority references").type(
815                     "text/plain").build();
816             throw new CSWebApplicationException(e, response);
817         }
818
819         return authRefList;
820     }
821
822     private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari, 
823                                                 List<AuthorityRefList.AuthorityRefItem> list)
824             throws Exception {
825         String fieldName = ari.getQualifiedDisplayName();
826         try {
827                         String refNameValue = (String)ari.getProperty().getValue();
828                         AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
829                         if(item!=null) {        // ignore garbage values.
830                                 list.add(item);
831                                 return true;
832                         }
833         } catch(PropertyException pe) {
834                 String msg = "PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage();
835                 if (logger.isDebugEnabled()) {
836                         logger.debug(msg, pe);
837                 } else {
838                         logger.error(msg);
839                 }
840         }
841         return false;
842     }
843
844     /**
845      * Fill in all the values to be returned in the authrefs payload for this item.
846      * 
847      * @param authRefFieldName
848      * @param refName
849      * @return
850      */
851     private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
852         //
853         // Find the CSID for the authority item
854         //
855         String csid = null;
856         try {
857                         DocumentModel docModel = NuxeoUtils.getDocModelForRefName(getServiceContext(), refName, getServiceContext().getResourceMap());
858                         csid = NuxeoUtils.getCsid(docModel);
859                 } catch (Exception e1) {
860                         String msg = String.format("Could not find CSID for authority reference with refname = %s.", refName);
861                         if (logger.isDebugEnabled()) {
862                                 logger.debug(msg, e1);
863                         } else {
864                                 logger.error(msg);
865                         }
866                 }
867         
868         AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
869         try {
870             RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
871             if (Tools.isEmpty(csid) == false) {
872                 ilistItem.setCsid(csid);
873             }
874             ilistItem.setRefName(refName);
875             ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
876             ilistItem.setItemDisplayName(termInfo.displayName);
877             ilistItem.setSourceField(authRefFieldName);
878             ilistItem.setUri(termInfo.getRelativeUri());
879         } catch (Exception e) {
880                 ilistItem = null;
881                 String msg = String.format("Trouble parsing refName from value: %s in field: %s. Error message: %s.",
882                                 refName, authRefFieldName, e.getLocalizedMessage());
883                 if (logger.isDebugEnabled()) {
884                         logger.debug(msg, e);
885                 } else {
886                         logger.error(msg);
887                 }
888         }
889         
890         return ilistItem;
891     }
892
893     /**
894      * Returns the primary value from a list of values.
895      *
896      * Assumes that the first value is the primary value.
897      * This assumption may change when and if the primary value
898      * is identified explicitly.
899      *
900      * @param values a list of values.
901      * @param propertyName the name of a property through
902      *     which the value can be extracted.
903      * @return the primary value.
904     protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
905         String primaryValue = "";
906         if (values == null || values.size() == 0) {
907             return primaryValue;
908         }
909         Object value = values.get(0);
910         if (value instanceof String) {
911             if (value != null) {
912                 primaryValue = (String) value;
913             }
914        // Multivalue group of fields
915        } else if (value instanceof Map) {
916             if (value != null) {
917                 Map map = (Map) value;
918                 if (map.values().size() > 0) {
919                     if (map.get(propertyName) != null) {
920                       primaryValue = (String) map.get(propertyName);
921                     }
922                 }
923             }
924        } else {
925             logger.warn("Unexpected type for property " + propertyName
926                     + " in multivalue list: not String or Map.");
927        }
928        return primaryValue;
929     }
930      */
931
932     /**
933      * Gets a simple property from the document.
934      *
935      * For completeness, as this duplicates DocumentModel method. 
936      *
937      * @param docModel The document model to get info from
938      * @param schema The name of the schema (part)
939      * @param propertyName The simple scalar property type
940      * @return property value as String
941      */
942     protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
943         String xpath = "/"+schema+":"+propName;
944         try {
945                 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
946         } catch(PropertyException pe) {
947                 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
948                                 +pe.getLocalizedMessage());
949         } catch(ClassCastException cce) {
950                 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
951                                 +cce.getLocalizedMessage());
952         } catch(Exception e) {
953                 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
954                                 +e.getLocalizedMessage());
955         }
956     }
957
958     /**
959      * Gets first of a repeating list of scalar values, as a String, from the document.
960      *
961      * @param docModel The document model to get info from
962      * @param schema The name of the schema (part)
963      * @param listName The name of the scalar list property
964      * @return first value in list, as a String, or empty string if the list is empty
965      */
966         protected String getFirstRepeatingStringProperty(DocumentModel docModel,
967                         String schema, String listName) {
968                 String xpath = "/" + schema + ":" + listName + "/[0]";
969                 try {
970                         return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
971                 } catch (PropertyException pe) {
972                         throw new RuntimeException("Problem retrieving property {" + xpath
973                                         + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
974                 } catch (IndexOutOfBoundsException ioobe) {
975                         // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
976                         return ""; // gracefully handle missing elements
977                 } catch (ClassCastException cce) {
978                         throw new RuntimeException("Problem retrieving property {" + xpath
979                                         + "} as String. Not a repeating String property?"
980                                         + cce.getLocalizedMessage());
981                 } catch (Exception e) {
982                         throw new RuntimeException("Unknown problem retrieving property {"
983                                         + xpath + "}." + e.getLocalizedMessage());
984                 }
985         }
986    
987     /**
988      * Gets first of a repeating list of scalar values, as a String, from the document.
989      *
990      * @param docModel The document model to get info from
991      * @param schema The name of the schema (part)
992      * @param listName The name of the scalar list property
993      * @return first value in list, as a String, or empty string if the list is empty
994      */
995     protected String getStringValueInPrimaryRepeatingComplexProperty(
996                 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {          
997         String result = null;
998         
999         String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
1000         try {
1001                 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
1002         } catch(PropertyException pe) {
1003                 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
1004                                 +pe.getLocalizedMessage());
1005         } catch(IndexOutOfBoundsException ioobe) {
1006                 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
1007                 result = "";    // gracefully handle missing elements
1008         } catch(ClassCastException cce) {
1009                 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
1010                                 +cce.getLocalizedMessage());
1011         } catch(NullPointerException npe) {
1012                 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
1013                 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
1014                                 xpath));
1015         } catch(Exception e) {
1016                 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
1017                                 +e.getLocalizedMessage());
1018         }
1019         
1020         return result;
1021     }
1022    
1023     /**
1024      * Gets XPath value from schema. Note that only "/" and "[n]" are
1025      * supported for xpath. Can omit grouping elements for repeating complex types, 
1026      * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
1027      * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
1028      * If there are no entries for a list of scalars or for a list of complex types, 
1029      * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
1030      * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
1031      * that many elements in the list. 
1032      * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
1033      *
1034      * @param docModel The document model to get info from
1035      * @param schema The name of the schema (part)
1036      * @param xpath The XPath expression (without schema prefix)
1037      * @return value the indicated property value as a String
1038      * @throws DocumentException 
1039      */
1040         protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
1041                         String schema, ListResultField field) throws DocumentException {
1042                 Object result = null;
1043
1044                 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
1045                 
1046                 return result;
1047         }
1048         
1049         protected String getStringValue(DocumentModel docModel,
1050                         String schema, ListResultField field) throws DocumentException {
1051                 String result = null;
1052                 
1053                 Object value = getListResultValue(docModel, schema, field);
1054                 if (value != null && value instanceof String) {
1055                         String strValue = (String) value;
1056                         if (strValue.trim().isEmpty() == false) {
1057                                 result = strValue;
1058                         }
1059                 }
1060                 
1061                 return result;
1062         }
1063    
1064     protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1065         list.remove(item);
1066     }
1067         
1068     private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
1069         sb.append(prefix);
1070                 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
1071         sb.append(": ["); 
1072         sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
1073         sb.append("]--");
1074         sb.append(item.getPredicate());
1075         sb.append("-->["); 
1076         sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
1077         sb.append("]");
1078     }
1079     
1080     private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
1081         StringBuilder sb = new StringBuilder();
1082         String s;
1083         if (list.size() > 0) {
1084             sb.append("=========== " + label + " ==========" + CR);
1085         }
1086         for (RelationsCommonList.RelationListItem item : list) {
1087                 itemToString(sb, "==  ", item);
1088                 sb.append(CR);
1089         }
1090         return sb.toString();
1091     }
1092     
1093     /** @return null on parent not found
1094      */
1095     protected String getParentCSID(String thisCSID) throws Exception {
1096         String parentCSID = null;
1097         try {
1098             String predicate = RelationshipType.HAS_BROADER.value();
1099             RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1100             List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1101             if (parentList != null) {
1102                 if (parentList.size() == 0) {
1103                     return null;
1104                 }
1105                 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
1106                 parentCSID = relationListItem.getObjectCsid();
1107             }
1108             return parentCSID;
1109         } catch (Exception e) {
1110             logger.error("Could not find parent for this: " + thisCSID, e);
1111             return null;
1112         }
1113     }
1114     
1115     protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
1116         List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
1117         return result;
1118     }
1119
1120     public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
1121             MultipartServiceContext ctx) throws Exception {
1122         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1123         String parentCSID = getParentCSID(thisCSID);
1124         if (parentCSID == null) {
1125             logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
1126             return;
1127         }
1128
1129         String predicate = RelationshipType.HAS_BROADER.value();
1130         RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
1131         List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
1132
1133         List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
1134
1135
1136         RelationsCommonList.RelationListItem item = null;
1137         for (RelationsCommonList.RelationListItem sibling : siblingList) {
1138             if (thisCSID.equals(sibling.getSubjectCsid())) {
1139                 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.
1140             }
1141         }
1142         //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.
1143         for (RelationsCommonList.RelationListItem self : toRemoveList) {
1144             removeFromList(siblingList, self);
1145         }
1146
1147         long siblingSize = siblingList.size();
1148         siblingListOuter.setTotalItems(siblingSize);
1149         siblingListOuter.setItemsInPage(siblingSize);
1150         if(logger.isTraceEnabled()) {
1151             String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
1152             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1153         }
1154
1155         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
1156         ctx.addOutputPart(relationsPart);
1157     }
1158     
1159     public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
1160             MultipartServiceContext ctx) throws Exception {
1161         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1162
1163         String predicate = RelationshipType.HAS_BROADER.value();
1164         RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1165         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1166
1167         RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
1168         List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
1169
1170         if(logger.isTraceEnabled()) {
1171             String dump = dumpLists(thisCSID, parentList, childrenList, null);
1172             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1173         }
1174         
1175         //Assume that there are more children than parents.  Will be true for parent/child, but maybe not for other relations.
1176         //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
1177         //Not optimal, but that's the current design spec.
1178         long added = 0;
1179         for (RelationsCommonList.RelationListItem parent : parentList) {
1180             childrenList.add(parent);
1181             added++;
1182         }
1183         long childrenSize = childrenList.size();
1184         childrenListOuter.setTotalItems(childrenSize);
1185         childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1186
1187         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1188         ctx.addOutputPart(relationsPart);
1189     }
1190     
1191     public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1192         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1193
1194         RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null);   //  nulls are wildcards:  predicate=*, and object=*
1195         List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1196
1197         RelationsCommonList objectListOuter = getRelations(null, thisCSID, null);   //  nulls are wildcards:  subject=*, and predicate=*
1198         List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1199
1200         if(logger.isTraceEnabled()) {
1201             String dump = dumpLists(thisCSID, subjectList, objectList, null);
1202             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1203         }
1204         //  MERGE LISTS:
1205         subjectList.addAll(objectList);
1206
1207         //now subjectList actually has records BOTH where thisCSID is subject and object.
1208         long relatedSize = subjectList.size();
1209         subjectListOuter.setTotalItems(relatedSize);
1210         subjectListOuter.setItemsInPage(relatedSize);
1211
1212         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1213         ctx.addOutputPart(relationsPart);
1214     }
1215
1216     private String dumpLists(String itemCSID,
1217             List<RelationsCommonList.RelationListItem> parentList,
1218             List<RelationsCommonList.RelationListItem> childList,
1219             List<RelationsCommonList.RelationListItem> actionList) {
1220         StringBuilder sb = new StringBuilder();
1221         sb.append("itemCSID: " + itemCSID + CR);
1222         if(parentList!=null) {
1223                 sb.append(dumpList(parentList, "parentList"));
1224         }
1225         if(childList!=null) {
1226                 sb.append(dumpList(childList, "childList"));
1227         }
1228         if(actionList!=null) {
1229                 sb.append(dumpList(actionList, "actionList"));
1230         }
1231         return sb.toString();
1232     }
1233     
1234     //================= TODO: move this to common, refactoring this and  CollectionObjectResource.java
1235     public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1236         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1237         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1238         queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1239         queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1240         queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1241
1242         RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1243         RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1244         return relationsCommonList;
1245     }
1246     //============================= END TODO refactor ==========================
1247     
1248     // this method calls the RelationResource to have it create the relations and persist them.
1249     private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1250                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1251         for (RelationsCommonList.RelationListItem item : inboundList) {
1252             RelationsCommon rc = new RelationsCommon();
1253             //rc.setCsid(item.getCsid());
1254             //todo: assignTo(item, rc);
1255             RelationsDocListItem itemSubject = item.getSubject();
1256             RelationsDocListItem itemObject = item.getObject();
1257
1258             // Set at least one of CSID and refName for Subject and Object
1259             // Either value might be null for for each of Subject and Object 
1260             String subjectCsid = itemSubject.getCsid();
1261             rc.setSubjectCsid(subjectCsid);
1262
1263             String objCsid = itemObject.getCsid();
1264             rc.setObjectCsid(objCsid);
1265
1266             rc.setSubjectRefName(itemSubject.getRefName());
1267             rc.setObjectRefName(itemObject.getRefName());
1268
1269             rc.setRelationshipType(item.getPredicate());
1270             rc.setRelationshipMetaType(item.getRelationshipMetaType());
1271             //RelationshipType  foo = (RelationshipType.valueOf(item.getPredicate())) ;
1272             //rc.setPredicate(foo);     //this must be one of the type found in the enum in  services/jaxb/src/main/resources/relations_common.xsd
1273
1274             // This is superfluous, since it will be fetched by the Relations Create logic.
1275             rc.setSubjectDocumentType(itemSubject.getDocumentType());
1276             rc.setObjectDocumentType(itemObject.getDocumentType());
1277
1278             // This is superfluous, since it will be fetched by the Relations Create logic.
1279             rc.setSubjectUri(itemSubject.getUri());
1280             rc.setObjectUri(itemObject.getUri());
1281             // May not have the info here. Only really require CSID or refName. 
1282             // Rest is handled in the Relation create mechanism
1283             //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1284
1285             PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1286             PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1287             payloadOut.addPart(outputPart);
1288             RelationResource relationResource = new RelationResource();
1289             Response res = relationResource.create(ctx, ctx.getResourceMap(),
1290                     ctx.getUriInfo(), payloadOut.toXML());    //NOTE ui recycled from above to pass in unknown query params.
1291         }
1292     }
1293     
1294     // Note that item2 may be sparse (only refName, no CSID for subject or object)
1295     // But item1 must not be sparse 
1296     private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1297         if (item1 == null || item2 == null) {
1298             return false;
1299         }
1300         RelationsDocListItem subj1 = item1.getSubject();
1301         RelationsDocListItem subj2 = item2.getSubject();
1302         RelationsDocListItem obj1 = item1.getObject();
1303         RelationsDocListItem obj2 = item2.getObject();
1304         
1305         String subj1Csid = subj1.getCsid();
1306         String subj2Csid = subj2.getCsid();
1307         String subj1RefName = subj1.getRefName();
1308         String subj2RefName = subj2.getRefName();
1309
1310         String obj1Csid = obj1.getCsid();
1311         String obj2Csid = obj2.getCsid();
1312         String obj1RefName = obj1.getRefName();
1313         String obj2RefName = obj2.getRefName();
1314         
1315         String item1Metatype = item1.getRelationshipMetaType();
1316         item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1317         
1318         String item2Metatype = item2.getRelationshipMetaType();
1319         item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1320         
1321         boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null)  && subj1RefName.equals(subj2RefName)))
1322                 && (obj1Csid.equals(obj2Csid)   || ((obj2Csid==null)   && obj1RefName.equals(obj2RefName)))
1323                 // predicate is proper, but still allow relationshipType
1324                 && (item1.getPredicate().equals(item2.getPredicate())
1325                         ||  ((item2.getPredicate()==null)  && item1.getRelationshipType().equals(item2.getRelationshipType())))
1326                 // Allow missing docTypes, so long as they do not conflict
1327                 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1328                 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1329                 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1330         return isEqual;
1331     }
1332     
1333     // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1334     // But the list items must not be sparse
1335     private RelationsCommonList.RelationListItem findInList(
1336                 List<RelationsCommonList.RelationListItem> list, 
1337                 RelationsCommonList.RelationListItem item) {
1338         RelationsCommonList.RelationListItem foundItem = null;
1339         for (RelationsCommonList.RelationListItem listItem : list) {
1340             if (itemsEqual(listItem, item)) {   //equals must be defined, else
1341                 foundItem = listItem;
1342                 break;
1343             }
1344         }
1345         return foundItem;
1346     }
1347     
1348     /**  updateRelations strategy:
1349      *
1350      *
1351     go through inboundList, remove anything from childList that matches  from childList
1352     go through inboundList, remove anything from parentList that matches  from parentList
1353     go through parentList, delete all remaining
1354     go through childList, delete all remaining
1355     go through actionList, add all remaining.
1356     check for duplicate children
1357     check for more than one parent.
1358     
1359     inboundList                           parentList                      childList          actionList
1360     ----------------                          ---------------                  ----------------       ----------------
1361     child-a                                   parent-c                        child-a             child-b
1362     child-b                                   parent-d                        child-c
1363     parent-a
1364      *
1365      *
1366      */
1367     private RelationsCommonList updateRelations(
1368             String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate)
1369             throws Exception {
1370         if (logger.isTraceEnabled()) {
1371             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1372         }
1373         PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME);        //input.getPart("relations_common");
1374         if (part == null) {
1375             return null;  //nothing to do--they didn't send a list of relations.
1376         }
1377         RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1378         List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1379         List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1380         List<RelationsCommonList.RelationListItem> childList = null;
1381         List<RelationsCommonList.RelationListItem> parentList = null;
1382         DocumentModel docModel = wrapDoc.getWrappedObject();
1383                 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1384                         CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1385
1386                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1387         //Do magic replacement of ${itemCSID} and fix URI's.
1388         fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1389
1390         final String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1391         UriInfo uriInfo = ctx.getUriInfo();
1392         MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1393
1394         if (forUpdate) {
1395             //Run getList() once as sent to get childListOuter:
1396             String predicate = RelationshipType.HAS_BROADER.value();
1397             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1398             queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1399             queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1400             queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1401             queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1402             
1403             RelationResource relationResource = new RelationResource();
1404             RelationsCommonList childListOuter = relationResource.getList(ctx);    // Knows all query params because they are in the context.
1405
1406             //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1407             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1408             queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1409             queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1410             RelationsCommonList parentListOuter = relationResource.getList(ctx);
1411
1412
1413             childList = childListOuter.getRelationListItem();
1414             parentList = parentListOuter.getRelationListItem();
1415
1416             if (parentList.size() > 1) {
1417                 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1418             }
1419
1420             if (logger.isTraceEnabled()) {
1421                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1422             }
1423         }
1424
1425         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1426             // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1427             // and so the CSID for those may be null
1428             if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1429                 // Look for parents and children
1430                 if(itemCSID.equals(inboundItem.getObject().getCsid())
1431                                 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1432                         //then this is an item that says we have a child.  That child is inboundItem
1433                         RelationsCommonList.RelationListItem childItem =
1434                                         (childList == null) ? null : findInList(childList, inboundItem);
1435                         if (childItem != null) {
1436                         if (logger.isTraceEnabled()) {
1437                                 StringBuilder sb = new StringBuilder();
1438                                 itemToString(sb, "== Child: ", childItem);
1439                             logger.trace("Found inboundChild in current child list: " + sb.toString());
1440                         }
1441                                 removeFromList(childList, childItem);    //exists, just take it off delete list
1442                         } else {
1443                         if (logger.isTraceEnabled()) {
1444                                 StringBuilder sb = new StringBuilder();
1445                                 itemToString(sb, "== Child: ", inboundItem);
1446                             logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1447                         }
1448                                 actionList.add(inboundItem);   //doesn't exist as a child, but is a child.  Add to additions list
1449                                 String newChildCsid = inboundItem.getSubject().getCsid();
1450                                 if(newChildCsid == null) {
1451                                         String newChildRefName = inboundItem.getSubject().getRefName();
1452                                         if (newChildRefName == null) {
1453                                                 throw new RuntimeException("Child with no CSID or refName!");
1454                                         }
1455                             if (logger.isTraceEnabled()) {
1456                                 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1457                             }
1458                                 DocumentModel newChildDocModel = 
1459                                         NuxeoBasedResource.getDocModelForRefName(getServiceContext(), 
1460                                                         newChildRefName, getServiceContext().getResourceMap());
1461                                 newChildCsid = getCsid(newChildDocModel);
1462                                 }
1463                                 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1464                         }
1465
1466                 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1467                                         || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1468                         //then this is an item that says we have a parent.  inboundItem is that parent.
1469                         RelationsCommonList.RelationListItem parentItem =
1470                                         (parentList == null) ? null : findInList(parentList, inboundItem);
1471                         if (parentItem != null) {
1472                                 removeFromList(parentList, parentItem);    //exists, just take it off delete list
1473                         } else {
1474                                 actionList.add(inboundItem);   //doesn't exist as a parent, but is a parent. Add to additions list
1475                         }
1476                 } else {
1477                     logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1478                 }
1479             } else {
1480                 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1481             }
1482         }
1483         if (logger.isTraceEnabled()) {
1484             String dump = dumpLists(itemCSID, parentList, childList, actionList);
1485             logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1486         }
1487         if (forUpdate) {
1488             if (logger.isTraceEnabled()) {
1489                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1490                         + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1491             }
1492             deleteRelations(parentList, ctx, "parentList");               //todo: there are items appearing on both lists....april 20.
1493             deleteRelations(childList, ctx, "childList");
1494         }
1495         if (logger.isTraceEnabled()) {
1496             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1497                     + actionList.size() + " new parents and children.");
1498         }
1499         createRelations(actionList, ctx);
1500         if (logger.isTraceEnabled()) {
1501             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1502         }
1503         // We return all elements on the inbound list, since we have just worked to make them exist in the system
1504         // and be non-redundant, etc.  That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1505         return relationsCommonListBody;
1506     }
1507     
1508     /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1509      *   and sets URI correctly for related items.
1510      *   Operates directly on the items in the list.  Does not change the list ordering, does not add or remove any items.
1511      */
1512     protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1513             List<RelationsCommonList.RelationListItem> inboundList,
1514             DocumentModel docModel,
1515             String itemCSID) throws Exception {
1516         String thisURI = this.getUri(docModel);
1517         // WARNING:  the two code blocks below are almost identical  and seem to ask to be put in a generic method.
1518         //                    beware of the little diffs in  inboundItem.setObjectCsid(itemCSID); and   inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1519         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1520             RelationsDocListItem inboundItemObject = inboundItem.getObject();
1521             RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1522
1523             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1524                 inboundItem.setObjectCsid(itemCSID);
1525                 inboundItemObject.setCsid(itemCSID);
1526                 //inboundItemObject.setUri(getUri(docModel));
1527             } else {
1528                 /*
1529                 String objectCsid = inboundItemObject.getCsid();
1530                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid);    //null if not found.
1531                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1532                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1533                 inboundItemObject.setUri(uri);    //CSPACE-4037
1534                  */
1535             }
1536             //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri());    //CSPACE-4042
1537
1538             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1539                 inboundItem.setSubjectCsid(itemCSID);
1540                 inboundItemSubject.setCsid(itemCSID);
1541                 //inboundItemSubject.setUri(getUri(docModel));
1542             } else {
1543                 /*
1544                 String subjectCsid = inboundItemSubject.getCsid();
1545                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid);    //null if not found.
1546                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1547                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1548                 inboundItemSubject.setUri(uri);    //CSPACE-4037
1549                  */
1550             }
1551             //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri());  //CSPACE-4042
1552
1553         }
1554     }
1555
1556     private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1557                 MultivaluedMap<String, String> queryParams, String childCSID) {
1558         logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1559         queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1560         queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1561         queryParams.putSingle(IRelationsManager.OBJECT_QP, null);  //null means ANY
1562         
1563         RelationResource relationResource = new RelationResource();
1564         RelationsCommonList parentListOuter = relationResource.getList(ctx);
1565         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1566         //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1567         deleteRelations(parentList, ctx, "parentList-delete");
1568     }
1569
1570     private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1571                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1572                 String listName) {
1573         try {
1574             for (RelationsCommonList.RelationListItem item : list) {
1575                 RelationResource relationResource = new RelationResource();
1576                 if(logger.isTraceEnabled()) {
1577                         StringBuilder sb = new StringBuilder();
1578                         itemToString(sb, "==== TO DELETE: ", item);
1579                         logger.trace(sb.toString());
1580                 }
1581                 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1582                 if (logger.isDebugEnabled()) {
1583                         logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1584                 }
1585             }
1586         } catch (Throwable t) {
1587             String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1588             logger.error(msg);
1589         }
1590     }
1591     
1592     // Note that we must do this after we have completed the Update, so that the repository has the
1593     // info for the item itself. The relations code must call into the repo to get info for each end.
1594     // This could be optimized to pass in the parent docModel, since it will often be one end.
1595     // Nevertheless, we should complete the item save before we do work on the relations, especially
1596     // since a save on Create might fail, and we would not want to create relations for something
1597     // that may not be created...
1598     private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate) throws Exception {
1599         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1600         PoxPayloadIn input = ctx.getInput();
1601         DocumentModel documentModel = (wrapDoc.getWrappedObject());
1602         String itemCsid = documentModel.getName();
1603
1604         //Updates relations part
1605         RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, forUpdate);
1606
1607         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
1608         ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1609
1610         //now we add part for relations list
1611         //ServiceContext ctx = getServiceContext();
1612         //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1613         ctx.getOutput().addPart(payloadOutputPart);
1614     }
1615
1616     /**
1617      * Checks to see if the refName has changed, and if so, 
1618      * uses utilities to find all references and update them to use the new refName.
1619      * @throws Exception 
1620      */
1621     protected void handleRefNameReferencesUpdate() throws Exception {
1622         if (hasRefNameUpdate() == true) {
1623             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1624             RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1625             CoreSessionInterface repoSession = this.getRepositorySession();
1626             
1627             // Update all the relationship records that referred to the old refName
1628             RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1629                     oldRefNameOnUpdate, newRefNameOnUpdate);
1630         }
1631     }
1632     
1633     protected String getRefNameUpdate() {
1634         String result = null;
1635         
1636         if (hasRefNameUpdate() == true) {
1637                 result = newRefNameOnUpdate;
1638                 if (logger.isDebugEnabled() == true) {
1639                         logger.debug(String.format("There was a refName update.  New: %s Old: %s" ,
1640                                         newRefNameOnUpdate, oldRefNameOnUpdate));
1641                 }
1642         }
1643         
1644         return result;
1645     }
1646     
1647     /*
1648      * Note: The Vocabulary document handler overrides this method.
1649      */
1650     protected String getRefPropName() {
1651         return ServiceBindingUtils.AUTH_REF_PROP;
1652     }
1653
1654     
1655     
1656 }