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