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