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