]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
2412c94aff34ec29979a43799b59cd654e3f3b04
[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) { // If not null then we're dealing with an authority item
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); // REM - "account_permission" should be using a constant and not a literal
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); cow;
1153                 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1154                         CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1155
1156                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1157         //Do magic replacement of ${itemCSID} and fix URI's.
1158         fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1159
1160         String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1161         UriInfo uriInfo = ctx.getUriInfo();
1162         MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1163
1164         if (fUpdate) {
1165             //Run getList() once as sent to get childListOuter:
1166             String predicate = RelationshipType.HAS_BROADER.value();
1167             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1168             queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1169             queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1170             queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1171             queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1172             
1173             RelationResource relationResource = new RelationResource();
1174             RelationsCommonList childListOuter = relationResource.getList(ctx);    // Knows all query params because they are in the context.
1175
1176             //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1177             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1178             queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1179             queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1180             RelationsCommonList parentListOuter = relationResource.getList(ctx);
1181
1182
1183             childList = childListOuter.getRelationListItem();
1184             parentList = parentListOuter.getRelationListItem();
1185
1186             if (parentList.size() > 1) {
1187                 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1188             }
1189
1190             if (logger.isTraceEnabled()) {
1191                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1192             }
1193         }
1194
1195         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1196             // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1197             // and so the CSID for those may be null
1198             if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1199                 // Look for parents and children
1200                 if(itemCSID.equals(inboundItem.getObject().getCsid())
1201                                 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1202                         //then this is an item that says we have a child.  That child is inboundItem
1203                         RelationsCommonList.RelationListItem childItem =
1204                                         (childList == null) ? null : findInList(childList, inboundItem);
1205                         if (childItem != null) {
1206                         if (logger.isTraceEnabled()) {
1207                                 StringBuilder sb = new StringBuilder();
1208                                 itemToString(sb, "== Child: ", childItem);
1209                             logger.trace("Found inboundChild in current child list: " + sb.toString());
1210                         }
1211                                 removeFromList(childList, childItem);    //exists, just take it off delete list
1212                         } else {
1213                         if (logger.isTraceEnabled()) {
1214                                 StringBuilder sb = new StringBuilder();
1215                                 itemToString(sb, "== Child: ", inboundItem);
1216                             logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1217                         }
1218                                 actionList.add(inboundItem);   //doesn't exist as a child, but is a child.  Add to additions list
1219                                 String newChildCsid = inboundItem.getSubject().getCsid();
1220                                 if(newChildCsid == null) {
1221                                         String newChildRefName = inboundItem.getSubject().getRefName();
1222                                         if(newChildRefName==null) {
1223                                                 throw new RuntimeException("Child with no CSID or refName!");
1224                                         }
1225                             if (logger.isTraceEnabled()) {
1226                                 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1227                             }
1228                                 DocumentModel newChildDocModel = 
1229                                         ResourceBase.getDocModelForRefName(this.getRepositorySession(), 
1230                                                         newChildRefName, getServiceContext().getResourceMap());
1231                                 newChildCsid = getCsid(newChildDocModel);
1232                                 }
1233                                 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1234                         }
1235
1236                 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1237                                         || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1238                         //then this is an item that says we have a parent.  inboundItem is that parent.
1239                         RelationsCommonList.RelationListItem parentItem =
1240                                         (parentList == null) ? null : findInList(parentList, inboundItem);
1241                         if (parentItem != null) {
1242                                 removeFromList(parentList, parentItem);    //exists, just take it off delete list
1243                         } else {
1244                                 actionList.add(inboundItem);   //doesn't exist as a parent, but is a parent. Add to additions list
1245                         }
1246                 } else {
1247                     logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1248                 }
1249             } else {
1250                 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1251             }
1252         }
1253         if (logger.isTraceEnabled()) {
1254             String dump = dumpLists(itemCSID, parentList, childList, actionList);
1255             logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1256         }
1257         if (fUpdate) {
1258             if (logger.isTraceEnabled()) {
1259                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1260                         + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1261             }
1262             deleteRelations(parentList, ctx, "parentList");               //todo: there are items appearing on both lists....april 20.
1263             deleteRelations(childList, ctx, "childList");
1264         }
1265         if (logger.isTraceEnabled()) {
1266             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1267                     + actionList.size() + " new parents and children.");
1268         }
1269         createRelations(actionList, ctx);
1270         if (logger.isTraceEnabled()) {
1271             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1272         }
1273         //We return all elements on the inbound list, since we have just worked to make them exist in the system
1274         // and be non-redundant, etc.  That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1275         return relationsCommonListBody;
1276     }
1277     
1278     /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1279      *   and sets URI correctly for related items.
1280      *   Operates directly on the items in the list.  Does not change the list ordering, does not add or remove any items.
1281      */
1282     protected void fixupInboundListItems(ServiceContext ctx,
1283             List<RelationsCommonList.RelationListItem> inboundList,
1284             DocumentModel docModel,
1285             String itemCSID) throws Exception {
1286         String thisURI = this.getUri(docModel);
1287         // WARNING:  the two code blocks below are almost identical  and seem to ask to be put in a generic method.
1288         //                    beware of the little diffs in  inboundItem.setObjectCsid(itemCSID); and   inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1289         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1290             RelationsDocListItem inboundItemObject = inboundItem.getObject();
1291             RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1292
1293             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1294                 inboundItem.setObjectCsid(itemCSID);
1295                 inboundItemObject.setCsid(itemCSID);
1296                 //inboundItemObject.setUri(getUri(docModel));
1297             } else {
1298                 /*
1299                 String objectCsid = inboundItemObject.getCsid();
1300                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid);    //null if not found.
1301                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1302                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1303                 inboundItemObject.setUri(uri);    //CSPACE-4037
1304                  */
1305             }
1306             //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri());    //CSPACE-4042
1307
1308             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1309                 inboundItem.setSubjectCsid(itemCSID);
1310                 inboundItemSubject.setCsid(itemCSID);
1311                 //inboundItemSubject.setUri(getUri(docModel));
1312             } else {
1313                 /*
1314                 String subjectCsid = inboundItemSubject.getCsid();
1315                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid);    //null if not found.
1316                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1317                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1318                 inboundItemSubject.setUri(uri);    //CSPACE-4037
1319                  */
1320             }
1321             //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri());  //CSPACE-4042
1322
1323         }
1324     }
1325
1326     private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1327                 MultivaluedMap<String, String> queryParams, String childCSID) {
1328         logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1329         queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1330         queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1331         queryParams.putSingle(IRelationsManager.OBJECT_QP, null);  //null means ANY
1332         
1333         RelationResource relationResource = new RelationResource();
1334         RelationsCommonList parentListOuter = relationResource.getList(ctx);
1335         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1336         //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1337         deleteRelations(parentList, ctx, "parentList-delete");
1338     }
1339
1340     private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1341                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1342                 String listName) {
1343         try {
1344             for (RelationsCommonList.RelationListItem item : list) {
1345                 RelationResource relationResource = new RelationResource();
1346                 if(logger.isTraceEnabled()) {
1347                         StringBuilder sb = new StringBuilder();
1348                         itemToString(sb, "==== TO DELETE: ", item);
1349                         logger.trace(sb.toString());
1350                 }
1351                 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1352                 if (logger.isDebugEnabled()) {
1353                         logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1354                 }
1355             }
1356         } catch (Throwable t) {
1357             String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1358             logger.error(msg);
1359         }
1360     }
1361     
1362     // Note that we must do this after we have completed the Update, so that the repository has the
1363     // info for the item itself. The relations code must call into the repo to get info for each end.
1364     // This could be optimized to pass in the parent docModel, since it will often be one end.
1365     // Nevertheless, we should complete the item save before we do work on the relations, especially
1366     // since a save on Create might fail, and we would not want to create relations for something
1367     // that may not be created...
1368     private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1369         ServiceContext ctx = getServiceContext();
1370         PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
1371         DocumentModel documentModel = (wrapDoc.getWrappedObject());
1372         String itemCsid = documentModel.getName();
1373
1374         //Updates relations part
1375         RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1376
1377         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
1378         ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1379
1380         //now we add part for relations list
1381         //ServiceContext ctx = getServiceContext();
1382         //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1383         ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
1384     }
1385
1386     /**
1387      * Checks to see if the refName has changed, and if so, 
1388      * uses utilities to find all references and update them.
1389      * @throws Exception 
1390      */
1391     protected void handleItemRefNameReferenceUpdate() throws Exception {
1392         if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
1393             // We have work to do.
1394             if (logger.isDebugEnabled()) {
1395                 String eol = System.getProperty("line.separator");
1396                 logger.debug("Need to find and update references to Item." + eol
1397                         + "   Old refName" + oldRefNameOnUpdate + eol
1398                         + "   New refName" + newRefNameOnUpdate);
1399             }
1400             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1401             RepositoryClient repoClient = getRepositoryClient(ctx);
1402             String refNameProp = getRefPropName();
1403
1404             int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
1405                     oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
1406             if (logger.isDebugEnabled()) {
1407                 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
1408             }
1409         }
1410     }
1411     
1412     /*
1413      * Note: The Vocabulary document handler overrides this method.
1414      */
1415     protected String getRefPropName() {
1416         return ServiceBindingUtils.AUTH_REF_PROP;
1417     }
1418
1419     
1420     
1421 }