]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
eb6b0158caa76635b20b6674f6c5d5ba841ecb08
[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.trace(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(DocumentModel docModel,
678                                               String schema,
679                                               ObjectPartType partMeta,
680                                               Map<String, Object> addToMap)
681             throws Exception {
682         Map<String, Object> result = null;
683
684         result = this.extractPart(docModel, schema, addToMap);
685
686         return result;
687     }
688     
689     /* (non-Javadoc)
690      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
691      */
692     @Override
693     public AuthorityRefList getAuthorityRefs(
694             String csid,
695             List<AuthRefConfigInfo> authRefConfigInfoList) throws PropertyException, Exception {
696
697         AuthorityRefList authRefList = new AuthorityRefList();
698         AbstractCommonList commonList = (AbstractCommonList) authRefList;
699         
700         DocumentFilter docFilter = this.getDocumentFilter();
701         long pageSize = docFilter.getPageSize();
702         long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
703         // set the page size and page number
704         commonList.setPageNum(pageNum);
705         commonList.setPageSize(pageSize);
706         
707         List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
708
709         try {
710                 int iFirstToUse = (int)(pageSize*pageNum);
711                 int nFoundInPage = 0;
712                 int nFoundTotal = 0;
713                 
714                 ArrayList<RefNameServiceUtils.AuthRefInfo> foundReferences 
715                         = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
716                 
717                 boolean releaseRepoSession = false;
718                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
719                 NuxeoRepositoryClientImpl repoClient = (NuxeoRepositoryClientImpl)this.getRepositoryClient(ctx);
720                 CoreSessionInterface repoSession = this.getRepositorySession();
721                 if (repoSession == null) {
722                         repoSession = repoClient.getRepositorySession(ctx);
723                         releaseRepoSession = true;
724                         this.setRepositorySession(repoSession); // we (the doc handler) should keep track of this repository session in case we need it
725                 }
726                 
727                 try {
728                         DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
729                         RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefConfigInfoList, null, foundReferences);
730                         // Slightly goofy pagination support - how many refs do we expect from one object?
731                         for(RefNameServiceUtils.AuthRefInfo ari:foundReferences) {
732                                 if ((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
733                                         if(appendToAuthRefsList(ari, list)) {
734                                                 nFoundInPage++;
735                                         nFoundTotal++;
736                                         }
737                                 } else {
738                                         nFoundTotal++;
739                                 }
740                         }
741                 } finally {
742                         if (releaseRepoSession == true) {
743                                 repoClient.releaseRepositorySession(ctx, repoSession);
744                         }
745                 }
746                 
747             // Set num of items in list. this is useful to our testing framework.
748             commonList.setItemsInPage(nFoundInPage);
749             // set the total result size
750             commonList.setTotalItems(nFoundTotal);
751             
752         } catch (PropertyException pe) {
753             String msg = "Attempted to retrieve value for invalid or missing authority field. "
754                     + "Check authority field properties in tenant bindings.";
755             logger.warn(msg, pe);
756             throw pe;
757         } catch (Exception e) {
758             if (logger.isDebugEnabled()) {
759                 logger.debug("Caught exception in getAuthorityRefs", e);
760             }
761             Response response = Response.status(
762                     Response.Status.INTERNAL_SERVER_ERROR).entity(
763                     "Failed to retrieve authority references").type(
764                     "text/plain").build();
765             throw new CSWebApplicationException(e, response);
766         }
767
768         return authRefList;
769     }
770
771     private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari, 
772                                                 List<AuthorityRefList.AuthorityRefItem> list)
773             throws Exception {
774         String fieldName = ari.getQualifiedDisplayName();
775         try {
776                         String refNameValue = (String)ari.getProperty().getValue();
777                         AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
778                         if(item!=null) {        // ignore garbage values.
779                                 list.add(item);
780                                 return true;
781                         }
782         } catch(PropertyException pe) {
783                 String msg = "PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage();
784                 if (logger.isDebugEnabled()) {
785                         logger.debug(msg, pe);
786                 } else {
787                         logger.error(msg);
788                 }
789         }
790         return false;
791     }
792
793     /**
794      * Fill in all the values to be returned in the authrefs payload for this item.
795      * 
796      * @param authRefFieldName
797      * @param refName
798      * @return
799      */
800     private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
801         //
802         // Find the CSID for the authority item
803         //
804         String csid = null;
805         try {
806                         DocumentModel docModel = NuxeoUtils.getDocModelForRefName(getServiceContext(), refName, getServiceContext().getResourceMap());
807                         csid = NuxeoUtils.getCsid(docModel);
808                 } catch (Exception e1) {
809                         String msg = String.format("Could not find CSID for authority reference with refname = %s.", refName);
810                         if (logger.isDebugEnabled()) {
811                                 logger.debug(msg, e1);
812                         } else {
813                                 logger.error(msg);
814                         }
815                 }
816         
817         AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
818         try {
819             RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
820             if (Tools.isEmpty(csid) == false) {
821                 ilistItem.setCsid(csid);
822             }
823             ilistItem.setRefName(refName);
824             ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
825             ilistItem.setItemDisplayName(termInfo.displayName);
826             ilistItem.setSourceField(authRefFieldName);
827             ilistItem.setUri(termInfo.getRelativeUri());
828         } catch (Exception e) {
829                 ilistItem = null;
830                 String msg = String.format("Trouble parsing refName from value: %s in field: %s. Error message: %s.",
831                                 refName, authRefFieldName, e.getLocalizedMessage());
832                 if (logger.isDebugEnabled()) {
833                         logger.debug(msg, e);
834                 } else {
835                         logger.error(msg);
836                 }
837         }
838         
839         return ilistItem;
840     }
841
842     /**
843      * Returns the primary value from a list of values.
844      *
845      * Assumes that the first value is the primary value.
846      * This assumption may change when and if the primary value
847      * is identified explicitly.
848      *
849      * @param values a list of values.
850      * @param propertyName the name of a property through
851      *     which the value can be extracted.
852      * @return the primary value.
853     protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
854         String primaryValue = "";
855         if (values == null || values.size() == 0) {
856             return primaryValue;
857         }
858         Object value = values.get(0);
859         if (value instanceof String) {
860             if (value != null) {
861                 primaryValue = (String) value;
862             }
863        // Multivalue group of fields
864        } else if (value instanceof Map) {
865             if (value != null) {
866                 Map map = (Map) value;
867                 if (map.values().size() > 0) {
868                     if (map.get(propertyName) != null) {
869                       primaryValue = (String) map.get(propertyName);
870                     }
871                 }
872             }
873        } else {
874             logger.warn("Unexpected type for property " + propertyName
875                     + " in multivalue list: not String or Map.");
876        }
877        return primaryValue;
878     }
879      */
880
881     /**
882      * Gets a simple property from the document.
883      *
884      * For completeness, as this duplicates DocumentModel method. 
885      *
886      * @param docModel The document model to get info from
887      * @param schema The name of the schema (part)
888      * @param propertyName The simple scalar property type
889      * @return property value as String
890      */
891     protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
892         String xpath = "/"+schema+":"+propName;
893         try {
894                 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
895         } catch(PropertyException pe) {
896                 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
897                                 +pe.getLocalizedMessage());
898         } catch(ClassCastException cce) {
899                 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
900                                 +cce.getLocalizedMessage());
901         } catch(Exception e) {
902                 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
903                                 +e.getLocalizedMessage());
904         }
905     }
906
907     /**
908      * Gets first of a repeating list of scalar values, as a String, from the document.
909      *
910      * @param docModel The document model to get info from
911      * @param schema The name of the schema (part)
912      * @param listName The name of the scalar list property
913      * @return first value in list, as a String, or empty string if the list is empty
914      */
915         protected String getFirstRepeatingStringProperty(DocumentModel docModel,
916                         String schema, String listName) {
917                 String xpath = "/" + schema + ":" + listName + "/[0]";
918                 try {
919                         return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
920                 } catch (PropertyException pe) {
921                         throw new RuntimeException("Problem retrieving property {" + xpath
922                                         + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
923                 } catch (IndexOutOfBoundsException ioobe) {
924                         // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
925                         return ""; // gracefully handle missing elements
926                 } catch (ClassCastException cce) {
927                         throw new RuntimeException("Problem retrieving property {" + xpath
928                                         + "} as String. Not a repeating String property?"
929                                         + cce.getLocalizedMessage());
930                 } catch (Exception e) {
931                         throw new RuntimeException("Unknown problem retrieving property {"
932                                         + xpath + "}." + e.getLocalizedMessage());
933                 }
934         }
935    
936     /**
937      * Gets first of a repeating list of scalar values, as a String, from the document.
938      *
939      * @param docModel The document model to get info from
940      * @param schema The name of the schema (part)
941      * @param listName The name of the scalar list property
942      * @return first value in list, as a String, or empty string if the list is empty
943      */
944     protected String getStringValueInPrimaryRepeatingComplexProperty(
945                 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {          
946         String result = null;
947         
948         String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
949         try {
950                 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
951         } catch(PropertyException pe) {
952                 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
953                                 +pe.getLocalizedMessage());
954         } catch(IndexOutOfBoundsException ioobe) {
955                 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
956                 result = "";    // gracefully handle missing elements
957         } catch(ClassCastException cce) {
958                 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
959                                 +cce.getLocalizedMessage());
960         } catch(NullPointerException npe) {
961                 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
962                 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
963                                 xpath));
964         } catch(Exception e) {
965                 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
966                                 +e.getLocalizedMessage());
967         }
968         
969         return result;
970     }
971    
972     /**
973      * Gets XPath value from schema. Note that only "/" and "[n]" are
974      * supported for xpath. Can omit grouping elements for repeating complex types, 
975      * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
976      * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
977      * If there are no entries for a list of scalars or for a list of complex types, 
978      * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
979      * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
980      * that many elements in the list. 
981      * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
982      *
983      * @param docModel The document model to get info from
984      * @param schema The name of the schema (part)
985      * @param xpath The XPath expression (without schema prefix)
986      * @return value the indicated property value as a String
987      * @throws DocumentException 
988      */
989         protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
990                         String schema, ListResultField field) throws DocumentException {
991                 Object result = null;
992
993                 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
994                 
995                 return result;
996         }
997         
998         protected String getStringValue(DocumentModel docModel,
999                         String schema, ListResultField field) throws DocumentException {
1000                 String result = null;
1001                 
1002                 Object value = getListResultValue(docModel, schema, field);
1003                 if (value != null && value instanceof String) {
1004                         String strValue = (String) value;
1005                         if (strValue.trim().isEmpty() == false) {
1006                                 result = strValue;
1007                         }
1008                 }
1009                 
1010                 return result;
1011         }
1012    
1013     protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1014         list.remove(item);
1015     }
1016         
1017     private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
1018         sb.append(prefix);
1019                 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
1020         sb.append(": ["); 
1021         sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
1022         sb.append("]--");
1023         sb.append(item.getPredicate());
1024         sb.append("-->["); 
1025         sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
1026         sb.append("]");
1027     }
1028     
1029     private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
1030         StringBuilder sb = new StringBuilder();
1031         String s;
1032         if (list.size() > 0) {
1033             sb.append("=========== " + label + " ==========" + CR);
1034         }
1035         for (RelationsCommonList.RelationListItem item : list) {
1036                 itemToString(sb, "==  ", item);
1037                 sb.append(CR);
1038         }
1039         return sb.toString();
1040     }
1041     
1042     /** @return null on parent not found
1043      */
1044     protected String getParentCSID(String thisCSID) throws Exception {
1045         String parentCSID = null;
1046         try {
1047             String predicate = RelationshipType.HAS_BROADER.value();
1048             RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1049             List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1050             if (parentList != null) {
1051                 if (parentList.size() == 0) {
1052                     return null;
1053                 }
1054                 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
1055                 parentCSID = relationListItem.getObjectCsid();
1056             }
1057             return parentCSID;
1058         } catch (Exception e) {
1059             logger.error("Could not find parent for this: " + thisCSID, e);
1060             return null;
1061         }
1062     }
1063     
1064     protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
1065         List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
1066         return result;
1067     }
1068
1069     public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
1070             MultipartServiceContext ctx) throws Exception {
1071         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1072         String parentCSID = getParentCSID(thisCSID);
1073         if (parentCSID == null) {
1074             logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
1075             return;
1076         }
1077
1078         String predicate = RelationshipType.HAS_BROADER.value();
1079         RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
1080         List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
1081
1082         List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
1083
1084
1085         RelationsCommonList.RelationListItem item = null;
1086         for (RelationsCommonList.RelationListItem sibling : siblingList) {
1087             if (thisCSID.equals(sibling.getSubjectCsid())) {
1088                 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.
1089             }
1090         }
1091         //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.
1092         for (RelationsCommonList.RelationListItem self : toRemoveList) {
1093             removeFromList(siblingList, self);
1094         }
1095
1096         long siblingSize = siblingList.size();
1097         siblingListOuter.setTotalItems(siblingSize);
1098         siblingListOuter.setItemsInPage(siblingSize);
1099         if(logger.isTraceEnabled()) {
1100             String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
1101             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1102         }
1103
1104         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
1105         ctx.addOutputPart(relationsPart);
1106     }
1107     
1108     public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
1109             MultipartServiceContext ctx) throws Exception {
1110         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1111
1112         String predicate = RelationshipType.HAS_BROADER.value();
1113         RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
1114         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1115
1116         RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
1117         List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
1118
1119         if(logger.isTraceEnabled()) {
1120             String dump = dumpLists(thisCSID, parentList, childrenList, null);
1121             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1122         }
1123         
1124         //Assume that there are more children than parents.  Will be true for parent/child, but maybe not for other relations.
1125         //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
1126         //Not optimal, but that's the current design spec.
1127         long added = 0;
1128         for (RelationsCommonList.RelationListItem parent : parentList) {
1129             childrenList.add(parent);
1130             added++;
1131         }
1132         long childrenSize = childrenList.size();
1133         childrenListOuter.setTotalItems(childrenSize);
1134         childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
1135
1136         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1137         ctx.addOutputPart(relationsPart);
1138     }
1139     
1140     public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1141         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1142
1143         RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null);   //  nulls are wildcards:  predicate=*, and object=*
1144         List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1145
1146         RelationsCommonList objectListOuter = getRelations(null, thisCSID, null);   //  nulls are wildcards:  subject=*, and predicate=*
1147         List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1148
1149         if(logger.isTraceEnabled()) {
1150             String dump = dumpLists(thisCSID, subjectList, objectList, null);
1151             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1152         }
1153         //  MERGE LISTS:
1154         subjectList.addAll(objectList);
1155
1156         //now subjectList actually has records BOTH where thisCSID is subject and object.
1157         long relatedSize = subjectList.size();
1158         subjectListOuter.setTotalItems(relatedSize);
1159         subjectListOuter.setItemsInPage(relatedSize);
1160
1161         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1162         ctx.addOutputPart(relationsPart);
1163     }
1164
1165     private String dumpLists(String itemCSID,
1166             List<RelationsCommonList.RelationListItem> parentList,
1167             List<RelationsCommonList.RelationListItem> childList,
1168             List<RelationsCommonList.RelationListItem> actionList) {
1169         StringBuilder sb = new StringBuilder();
1170         sb.append("itemCSID: " + itemCSID + CR);
1171         if(parentList!=null) {
1172                 sb.append(dumpList(parentList, "parentList"));
1173         }
1174         if(childList!=null) {
1175                 sb.append(dumpList(childList, "childList"));
1176         }
1177         if(actionList!=null) {
1178                 sb.append(dumpList(actionList, "actionList"));
1179         }
1180         return sb.toString();
1181     }
1182     
1183     //================= TODO: move this to common, refactoring this and  CollectionObjectResource.java
1184     public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1185         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1186         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1187         queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1188         queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1189         queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1190
1191         RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1192         RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1193         return relationsCommonList;
1194     }
1195     //============================= END TODO refactor ==========================
1196     
1197     // this method calls the RelationResource to have it create the relations and persist them.
1198     private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1199                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1200         for (RelationsCommonList.RelationListItem item : inboundList) {
1201             RelationsCommon rc = new RelationsCommon();
1202             //rc.setCsid(item.getCsid());
1203             //todo: assignTo(item, rc);
1204             RelationsDocListItem itemSubject = item.getSubject();
1205             RelationsDocListItem itemObject = item.getObject();
1206
1207             // Set at least one of CSID and refName for Subject and Object
1208             // Either value might be null for for each of Subject and Object 
1209             String subjectCsid = itemSubject.getCsid();
1210             rc.setSubjectCsid(subjectCsid);
1211
1212             String objCsid = itemObject.getCsid();
1213             rc.setObjectCsid(objCsid);
1214
1215             rc.setSubjectRefName(itemSubject.getRefName());
1216             rc.setObjectRefName(itemObject.getRefName());
1217
1218             rc.setRelationshipType(item.getPredicate());
1219             rc.setRelationshipMetaType(item.getRelationshipMetaType());
1220             //RelationshipType  foo = (RelationshipType.valueOf(item.getPredicate())) ;
1221             //rc.setPredicate(foo);     //this must be one of the type found in the enum in  services/jaxb/src/main/resources/relations_common.xsd
1222
1223             // This is superfluous, since it will be fetched by the Relations Create logic.
1224             rc.setSubjectDocumentType(itemSubject.getDocumentType());
1225             rc.setObjectDocumentType(itemObject.getDocumentType());
1226
1227             // This is superfluous, since it will be fetched by the Relations Create logic.
1228             rc.setSubjectUri(itemSubject.getUri());
1229             rc.setObjectUri(itemObject.getUri());
1230             // May not have the info here. Only really require CSID or refName. 
1231             // Rest is handled in the Relation create mechanism
1232             //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1233
1234             PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1235             PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1236             payloadOut.addPart(outputPart);
1237             RelationResource relationResource = new RelationResource();
1238             Response res = relationResource.create(ctx, ctx.getResourceMap(),
1239                     ctx.getUriInfo(), payloadOut.toXML());    //NOTE ui recycled from above to pass in unknown query params.
1240         }
1241     }
1242     
1243     // Note that item2 may be sparse (only refName, no CSID for subject or object)
1244     // But item1 must not be sparse 
1245     private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1246         if (item1 == null || item2 == null) {
1247             return false;
1248         }
1249         RelationsDocListItem subj1 = item1.getSubject();
1250         RelationsDocListItem subj2 = item2.getSubject();
1251         RelationsDocListItem obj1 = item1.getObject();
1252         RelationsDocListItem obj2 = item2.getObject();
1253         
1254         String subj1Csid = subj1.getCsid();
1255         String subj2Csid = subj2.getCsid();
1256         String subj1RefName = subj1.getRefName();
1257         String subj2RefName = subj2.getRefName();
1258
1259         String obj1Csid = obj1.getCsid();
1260         String obj2Csid = obj2.getCsid();
1261         String obj1RefName = obj1.getRefName();
1262         String obj2RefName = obj2.getRefName();
1263         
1264         String item1Metatype = item1.getRelationshipMetaType();
1265         item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1266         
1267         String item2Metatype = item2.getRelationshipMetaType();
1268         item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1269         
1270         boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null)  && subj1RefName.equals(subj2RefName)))
1271                 && (obj1Csid.equals(obj2Csid)   || ((obj2Csid==null)   && obj1RefName.equals(obj2RefName)))
1272                 // predicate is proper, but still allow relationshipType
1273                 && (item1.getPredicate().equals(item2.getPredicate())
1274                         ||  ((item2.getPredicate()==null)  && item1.getRelationshipType().equals(item2.getRelationshipType())))
1275                 // Allow missing docTypes, so long as they do not conflict
1276                 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1277                 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1278                 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1279         return isEqual;
1280     }
1281     
1282     // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1283     // But the list items must not be sparse
1284     private RelationsCommonList.RelationListItem findInList(
1285                 List<RelationsCommonList.RelationListItem> list, 
1286                 RelationsCommonList.RelationListItem item) {
1287         RelationsCommonList.RelationListItem foundItem = null;
1288         for (RelationsCommonList.RelationListItem listItem : list) {
1289             if (itemsEqual(listItem, item)) {   //equals must be defined, else
1290                 foundItem = listItem;
1291                 break;
1292             }
1293         }
1294         return foundItem;
1295     }
1296     
1297     /**  updateRelations strategy:
1298      *
1299      *
1300     go through inboundList, remove anything from childList that matches  from childList
1301     go through inboundList, remove anything from parentList that matches  from parentList
1302     go through parentList, delete all remaining
1303     go through childList, delete all remaining
1304     go through actionList, add all remaining.
1305     check for duplicate children
1306     check for more than one parent.
1307     
1308     inboundList                           parentList                      childList          actionList
1309     ----------------                          ---------------                  ----------------       ----------------
1310     child-a                                   parent-c                        child-a             child-b
1311     child-b                                   parent-d                        child-c
1312     parent-a
1313      *
1314      *
1315      */
1316     private RelationsCommonList updateRelations(
1317             String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate)
1318             throws Exception {
1319         if (logger.isTraceEnabled()) {
1320             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1321         }
1322         PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME);        //input.getPart("relations_common");
1323         if (part == null) {
1324             return null;  //nothing to do--they didn't send a list of relations.
1325         }
1326         RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1327         List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1328         List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1329         List<RelationsCommonList.RelationListItem> childList = null;
1330         List<RelationsCommonList.RelationListItem> parentList = null;
1331         DocumentModel docModel = wrapDoc.getWrappedObject();
1332                 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1333                         CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1334
1335                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1336         //Do magic replacement of ${itemCSID} and fix URI's.
1337         fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1338
1339         final String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1340         UriInfo uriInfo = ctx.getUriInfo();
1341         MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1342
1343         if (forUpdate) {
1344             //Run getList() once as sent to get childListOuter:
1345             String predicate = RelationshipType.HAS_BROADER.value();
1346             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1347             queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1348             queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1349             queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1350             queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1351             
1352             RelationResource relationResource = new RelationResource();
1353             RelationsCommonList childListOuter = relationResource.getList(ctx);    // Knows all query params because they are in the context.
1354
1355             //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1356             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1357             queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1358             queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1359             RelationsCommonList parentListOuter = relationResource.getList(ctx);
1360
1361
1362             childList = childListOuter.getRelationListItem();
1363             parentList = parentListOuter.getRelationListItem();
1364
1365             if (parentList.size() > 1) {
1366                 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1367             }
1368
1369             if (logger.isTraceEnabled()) {
1370                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1371             }
1372         }
1373
1374         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1375             // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1376             // and so the CSID for those may be null
1377             if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1378                 // Look for parents and children
1379                 if(itemCSID.equals(inboundItem.getObject().getCsid())
1380                                 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1381                         //then this is an item that says we have a child.  That child is inboundItem
1382                         RelationsCommonList.RelationListItem childItem =
1383                                         (childList == null) ? null : findInList(childList, inboundItem);
1384                         if (childItem != null) {
1385                         if (logger.isTraceEnabled()) {
1386                                 StringBuilder sb = new StringBuilder();
1387                                 itemToString(sb, "== Child: ", childItem);
1388                             logger.trace("Found inboundChild in current child list: " + sb.toString());
1389                         }
1390                                 removeFromList(childList, childItem);    //exists, just take it off delete list
1391                         } else {
1392                         if (logger.isTraceEnabled()) {
1393                                 StringBuilder sb = new StringBuilder();
1394                                 itemToString(sb, "== Child: ", inboundItem);
1395                             logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1396                         }
1397                                 actionList.add(inboundItem);   //doesn't exist as a child, but is a child.  Add to additions list
1398                                 String newChildCsid = inboundItem.getSubject().getCsid();
1399                                 if(newChildCsid == null) {
1400                                         String newChildRefName = inboundItem.getSubject().getRefName();
1401                                         if (newChildRefName == null) {
1402                                                 throw new RuntimeException("Child with no CSID or refName!");
1403                                         }
1404                             if (logger.isTraceEnabled()) {
1405                                 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1406                             }
1407                                 DocumentModel newChildDocModel = 
1408                                         NuxeoBasedResource.getDocModelForRefName(getServiceContext(), 
1409                                                         newChildRefName, getServiceContext().getResourceMap());
1410                                 newChildCsid = getCsid(newChildDocModel);
1411                                 }
1412                                 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1413                         }
1414
1415                 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1416                                         || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1417                         //then this is an item that says we have a parent.  inboundItem is that parent.
1418                         RelationsCommonList.RelationListItem parentItem =
1419                                         (parentList == null) ? null : findInList(parentList, inboundItem);
1420                         if (parentItem != null) {
1421                                 removeFromList(parentList, parentItem);    //exists, just take it off delete list
1422                         } else {
1423                                 actionList.add(inboundItem);   //doesn't exist as a parent, but is a parent. Add to additions list
1424                         }
1425                 } else {
1426                     logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1427                 }
1428             } else {
1429                 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1430             }
1431         }
1432         if (logger.isTraceEnabled()) {
1433             String dump = dumpLists(itemCSID, parentList, childList, actionList);
1434             logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1435         }
1436         if (forUpdate) {
1437             if (logger.isTraceEnabled()) {
1438                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1439                         + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1440             }
1441             deleteRelations(parentList, ctx, "parentList");               //todo: there are items appearing on both lists....april 20.
1442             deleteRelations(childList, ctx, "childList");
1443         }
1444         if (logger.isTraceEnabled()) {
1445             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1446                     + actionList.size() + " new parents and children.");
1447         }
1448         createRelations(actionList, ctx);
1449         if (logger.isTraceEnabled()) {
1450             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1451         }
1452         // We return all elements on the inbound list, since we have just worked to make them exist in the system
1453         // and be non-redundant, etc.  That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1454         return relationsCommonListBody;
1455     }
1456     
1457     /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1458      *   and sets URI correctly for related items.
1459      *   Operates directly on the items in the list.  Does not change the list ordering, does not add or remove any items.
1460      */
1461     protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1462             List<RelationsCommonList.RelationListItem> inboundList,
1463             DocumentModel docModel,
1464             String itemCSID) throws Exception {
1465         String thisURI = this.getUri(docModel);
1466         // WARNING:  the two code blocks below are almost identical  and seem to ask to be put in a generic method.
1467         //                    beware of the little diffs in  inboundItem.setObjectCsid(itemCSID); and   inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1468         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1469             RelationsDocListItem inboundItemObject = inboundItem.getObject();
1470             RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1471
1472             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1473                 inboundItem.setObjectCsid(itemCSID);
1474                 inboundItemObject.setCsid(itemCSID);
1475                 //inboundItemObject.setUri(getUri(docModel));
1476             } else {
1477                 /*
1478                 String objectCsid = inboundItemObject.getCsid();
1479                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid);    //null if not found.
1480                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1481                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1482                 inboundItemObject.setUri(uri);    //CSPACE-4037
1483                  */
1484             }
1485             //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri());    //CSPACE-4042
1486
1487             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1488                 inboundItem.setSubjectCsid(itemCSID);
1489                 inboundItemSubject.setCsid(itemCSID);
1490                 //inboundItemSubject.setUri(getUri(docModel));
1491             } else {
1492                 /*
1493                 String subjectCsid = inboundItemSubject.getCsid();
1494                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid);    //null if not found.
1495                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1496                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1497                 inboundItemSubject.setUri(uri);    //CSPACE-4037
1498                  */
1499             }
1500             //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri());  //CSPACE-4042
1501
1502         }
1503     }
1504
1505     private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1506                 MultivaluedMap<String, String> queryParams, String childCSID) {
1507         logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1508         queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1509         queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1510         queryParams.putSingle(IRelationsManager.OBJECT_QP, null);  //null means ANY
1511         
1512         RelationResource relationResource = new RelationResource();
1513         RelationsCommonList parentListOuter = relationResource.getList(ctx);
1514         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1515         //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1516         deleteRelations(parentList, ctx, "parentList-delete");
1517     }
1518
1519     private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1520                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1521                 String listName) {
1522         try {
1523             for (RelationsCommonList.RelationListItem item : list) {
1524                 RelationResource relationResource = new RelationResource();
1525                 if(logger.isTraceEnabled()) {
1526                         StringBuilder sb = new StringBuilder();
1527                         itemToString(sb, "==== TO DELETE: ", item);
1528                         logger.trace(sb.toString());
1529                 }
1530                 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1531                 if (logger.isDebugEnabled()) {
1532                         logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1533                 }
1534             }
1535         } catch (Throwable t) {
1536             String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1537             logger.error(msg);
1538         }
1539     }
1540     
1541     // Note that we must do this after we have completed the Update, so that the repository has the
1542     // info for the item itself. The relations code must call into the repo to get info for each end.
1543     // This could be optimized to pass in the parent docModel, since it will often be one end.
1544     // Nevertheless, we should complete the item save before we do work on the relations, especially
1545     // since a save on Create might fail, and we would not want to create relations for something
1546     // that may not be created...
1547     private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean forUpdate) throws Exception {
1548             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1549         PoxPayloadIn input = ctx.getInput();
1550         DocumentModel documentModel = (wrapDoc.getWrappedObject());
1551         String itemCsid = documentModel.getName();
1552
1553         //Updates relations part
1554         RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, forUpdate);
1555
1556         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
1557         ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1558
1559         //now we add part for relations list
1560         //ServiceContext ctx = getServiceContext();
1561         //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1562         ctx.getOutput().addPart(payloadOutputPart);
1563     }
1564
1565     /**
1566      * Checks to see if the refName has changed, and if so, 
1567      * uses utilities to find all references and update them to use the new refName.
1568      * @throws Exception 
1569      */
1570     protected void handleRefNameReferencesUpdate() throws Exception {
1571         if (hasRefNameUpdate() == true) {
1572             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1573             RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1574             CoreSessionInterface repoSession = this.getRepositorySession();
1575             
1576             // Update all the relationship records that referred to the old refName
1577             RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1578                     oldRefNameOnUpdate, newRefNameOnUpdate);
1579         }
1580     }
1581     
1582     protected String getRefNameUpdate() {
1583         String result = null;
1584         
1585         if (hasRefNameUpdate() == true) {
1586                 result = newRefNameOnUpdate;
1587                 if (logger.isDebugEnabled() == true) {
1588                         logger.debug(String.format("There was a refName update.  New: %s Old: %s" ,
1589                                         newRefNameOnUpdate, oldRefNameOnUpdate));
1590                 }
1591         }
1592         
1593         return result;
1594     }
1595     
1596     /*
1597      * Note: The Vocabulary document handler overrides this method.
1598      */
1599     protected String getRefPropName() {
1600         return ServiceBindingUtils.AUTH_REF_PROP;
1601     }
1602
1603     
1604     
1605 }