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