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