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