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