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