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