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