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