]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
a82daf2ecd16299b0f3eb542cc8d65b5a2fcda5a
[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.common.vocabulary.nuxeo;
25
26 import org.collectionspace.services.client.AuthorityClient;
27 import org.collectionspace.services.client.PayloadInputPart;
28 import org.collectionspace.services.client.PayloadOutputPart;
29 import org.collectionspace.services.client.PoxPayloadIn;
30 import org.collectionspace.services.client.PoxPayloadOut;
31 import org.collectionspace.services.client.RelationClient;
32 import org.collectionspace.services.common.ResourceBase;
33 import org.collectionspace.services.common.ServiceMessages;
34 import org.collectionspace.services.common.api.CommonAPI;
35 import org.collectionspace.services.common.api.RefName;
36 import org.collectionspace.services.common.api.Tools;
37 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
38 import org.collectionspace.services.common.context.MultipartServiceContext;
39 import org.collectionspace.services.common.context.ServiceBindingUtils;
40 import org.collectionspace.services.common.context.ServiceContext;
41 import org.collectionspace.services.common.document.DocumentException;
42 import org.collectionspace.services.common.document.DocumentFilter;
43 import org.collectionspace.services.common.document.DocumentWrapper;
44 import org.collectionspace.services.common.document.DocumentWrapperImpl;
45 import org.collectionspace.services.common.relation.IRelationsManager;
46 import org.collectionspace.services.common.repository.RepositoryClient;
47 import org.collectionspace.services.common.repository.RepositoryClientFactory;
48 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
49 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
50 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
51 import org.collectionspace.services.config.service.ListResultField;
52 import org.collectionspace.services.config.service.ObjectPartType;
53 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
54 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
55 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
56 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
57 import org.collectionspace.services.relation.RelationResource;
58 import org.collectionspace.services.relation.RelationsCommon;
59 import org.collectionspace.services.relation.RelationsCommonList;
60 import org.collectionspace.services.relation.RelationsDocListItem;
61 import org.collectionspace.services.relation.RelationshipType;
62 import org.collectionspace.services.vocabulary.VocabularyItemJAXBSchema;
63 import org.nuxeo.ecm.core.api.ClientException;
64 import org.nuxeo.ecm.core.api.DocumentModel;
65 import org.nuxeo.ecm.core.api.model.PropertyException;
66 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
67 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
68 import org.nuxeo.runtime.transaction.TransactionHelper;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
71
72 import javax.ws.rs.PathParam;
73 import javax.ws.rs.WebApplicationException;
74 import javax.ws.rs.core.Context;
75 import javax.ws.rs.core.MultivaluedMap;
76 import javax.ws.rs.core.Response;
77 import javax.ws.rs.core.UriInfo;
78 import java.util.ArrayList;
79 import java.util.List;
80 import java.util.Map;
81 import java.util.regex.Matcher;
82 import java.util.regex.Pattern;
83 import org.collectionspace.services.nuxeo.client.java.DocumentModelHandler;
84
85 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
86 /**
87  * AuthorityItemDocumentModelHandler
88  *
89  * $LastChangedRevision: $
90  * $LastChangedDate: $
91  */
92 public abstract class AuthorityItemDocumentModelHandler<AICommon>
93         extends DocHandlerBase<AICommon> {
94
95     private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
96     private String authorityItemCommonSchemaName;
97     private String authorityItemTermGroupXPathBase;
98     /**
99      * inVocabulary is the parent Authority for this context
100      */
101     protected String inAuthority = null;
102     protected String authorityRefNameBase = null;
103     // Used to determine when the displayName changes as part of the update.
104     protected String oldDisplayNameOnUpdate = null;
105     protected String oldRefNameOnUpdate = null;
106     protected String newRefNameOnUpdate = null;
107
108     public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
109         this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
110     }
111
112     public void setInAuthority(String inAuthority) {
113         this.inAuthority = inAuthority;
114     }
115
116     /** Subclasses may override this to customize the URI segment. */
117     public String getAuthorityServicePath() {
118         return getServiceContext().getServiceName().toLowerCase();    // Laramie20110510 CSPACE-3932
119     }
120
121     @Override
122     public String getUri(DocumentModel docModel) {
123         // Laramie20110510 CSPACE-3932
124         String authorityServicePath = getAuthorityServicePath();
125         if(inAuthority==null) { // Only happens on queries to wildcarded authorities
126                 try {
127                         inAuthority = (String) docModel.getProperty(authorityItemCommonSchemaName,
128                         AuthorityItemJAXBSchema.IN_AUTHORITY);
129                 } catch (ClientException pe) {
130                         throw new RuntimeException("Could not get parent specifier for item!");
131                 }
132         }
133         return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
134     }
135
136     protected String getAuthorityRefNameBase() {
137         return this.authorityRefNameBase;
138     }
139
140     public void setAuthorityRefNameBase(String value) {
141         this.authorityRefNameBase = value;
142     }
143
144     /*
145      * Note: the Vocabulary service's VocabularyItemDocumentModelHandler class overrides this method.
146      */
147     protected ListResultField getListResultsDisplayNameField() {
148         ListResultField result = new ListResultField();
149         // Per CSPACE-5132, the name of this element remains 'displayName'
150         // for backwards compatibility, although its value is obtained
151         // from the termDisplayName field.
152         //
153         // In CSPACE-5134, these list results will change substantially
154         // to return display names for both the preferred term and for
155         // each non-preferred term (if any).
156         result.setElement(AuthorityItemJAXBSchema.DISPLAY_NAME);
157         result.setXpath(NuxeoUtils.getPrimaryXPathPropertyName(
158                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_DISPLAY_NAME));
159         
160         return result;
161     }
162     
163     /*
164      * Note: the Vocabulary service's VocabularyItemDocumentModelHandler class overrides this method.
165      */    
166     protected ListResultField getListResultsTermStatusField() {
167         ListResultField result = new ListResultField();
168         
169         result.setElement(AuthorityItemJAXBSchema.TERM_STATUS);
170         result.setXpath(NuxeoUtils.getPrimaryXPathPropertyName(
171                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_STATUS));
172
173         return result;
174     }    
175     
176     @Override
177     public List<ListResultField> getListItemsArray() throws DocumentException {
178         List<ListResultField> list = super.getListItemsArray();
179         int nFields = list.size();
180         // Ensure that each item in a list of Authority items includes
181         // a set of common fields, so we do not depend upon configuration
182         // for general logic.
183         boolean hasDisplayName = false;
184         boolean hasShortId = false;
185         boolean hasRefName = false;
186         boolean hasTermStatus = false;
187         for (int i = 0; i < nFields; i++) {
188             ListResultField field = list.get(i);
189             String elName = field.getElement();
190             if (AuthorityItemJAXBSchema.TERM_DISPLAY_NAME.equals(elName) || VocabularyItemJAXBSchema.DISPLAY_NAME.equals(elName)) {
191                 hasDisplayName = true;
192             } else if (AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName)) {
193                 hasShortId = true;
194             } else if (AuthorityItemJAXBSchema.REF_NAME.equals(elName)) {
195                 hasRefName = true;
196             } else if (AuthorityItemJAXBSchema.TERM_STATUS.equals(elName)) {
197                 hasTermStatus = true;
198             }
199         }
200         ListResultField field;
201         if (!hasDisplayName) {
202                 field = getListResultsDisplayNameField();
203             list.add(field);  //Note: We're updating the "global" service and tenant bindings instance here -the list instance here is a reference to the tenant bindings instance in the singleton ServiceMain.
204         }
205         if (!hasShortId) {
206             field = new ListResultField();
207             field.setElement(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
208             field.setXpath(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
209             list.add(field);
210         }
211         if (!hasRefName) {
212             field = new ListResultField();
213             field.setElement(AuthorityItemJAXBSchema.REF_NAME);
214             field.setXpath(AuthorityItemJAXBSchema.REF_NAME);
215             list.add(field);
216         }
217         if (!hasTermStatus) {
218             field = getListResultsTermStatusField();
219             list.add(field);
220         }
221         
222         return list;
223     }
224
225
226     /* (non-Javadoc)
227      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
228      */
229     @Override
230     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
231         // first fill all the parts of the document
232         super.handleCreate(wrapDoc);
233         // Ensure we have required fields set properly
234         handleInAuthority(wrapDoc.getWrappedObject());
235         
236         // FIXME: This call to synthesize a shortIdentifier from the termDisplayName
237         // of the preferred term may have been commented out, in the course of
238         // adding support for preferred / non-preferred terms, in CSPACE-4813
239         // and linked issues. Revisit this to determine whether we want to
240         // re-enable it.
241         //
242         // CSPACE-3178:
243         // handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
244         // refName includes displayName, so we force a correct value here.
245         updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
246     }
247
248     /*
249      * Note that the Vocabulary service's document-model for items overrides this method.
250      */
251         protected String getPrimaryDisplayName(DocumentModel docModel, String schema,
252                         String complexPropertyName, String fieldName) {
253                 String result = null;
254
255                 result = getStringValueInPrimaryRepeatingComplexProperty(docModel, schema, complexPropertyName, fieldName);
256                 
257                 return result;
258         }
259     
260     /* (non-Javadoc)
261      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
262      */
263     @Override
264     public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
265         // First, get a copy of the old displayName
266         // oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
267         //        AuthorityItemJAXBSchema.DISPLAY_NAME);
268         oldDisplayNameOnUpdate = getPrimaryDisplayName(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName,
269                 getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
270         oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
271                 AuthorityItemJAXBSchema.REF_NAME);
272         super.handleUpdate(wrapDoc);
273
274         // Now, check the new display and handle the refname update.
275         String newDisplayName = (String) getPrimaryDisplayName(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName,
276                 authorityItemTermGroupXPathBase,
277                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
278         if (newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
279             // Need to update the refName, and then fix all references.
280             newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
281         } else {
282             // Mark as not needing attention in completeUpdate phase.
283             newRefNameOnUpdate = null;
284             oldRefNameOnUpdate = null;
285         }
286     }
287
288     /**
289      * Handle display name.
290      *
291      * @param docModel the doc model
292      * @throws Exception the exception
293      */
294 //    protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
295 //        // Do nothing by default.
296 //    }
297
298     /**
299      * Handle refName updates for changes to display name.
300      * Assumes refName is already correct. Just ensures it is right.
301      *
302      * @param docModel the doc model
303      * @param newDisplayName the new display name
304      * @throws Exception the exception
305      */
306     protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
307             String newDisplayName) throws Exception {
308         RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
309         if (authItem == null) {
310             String err = "Authority Item has illegal refName: " + oldRefNameOnUpdate;
311             logger.debug(err);
312             throw new IllegalArgumentException(err);
313         }
314         authItem.displayName = newDisplayName;
315         String updatedRefName = authItem.toString();
316         docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
317         return updatedRefName;
318     }
319     
320     /*
321      * Note: The Vocabulary document handler overrides this method.
322      */
323     protected String getRefPropName() {
324         return ServiceBindingUtils.AUTH_REF_PROP;
325     }
326
327     /**
328      * Checks to see if the refName has changed, and if so, 
329      * uses utilities to find all references and update them.
330      * @throws Exception 
331      */
332     protected void handleItemRefNameReferenceUpdate() throws Exception {
333         if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
334             // We have work to do.
335             if (logger.isDebugEnabled()) {
336                 String eol = System.getProperty("line.separator");
337                 logger.debug("Need to find and update references to Item." + eol
338                         + "   Old refName" + oldRefNameOnUpdate + eol
339                         + "   New refName" + newRefNameOnUpdate);
340             }
341             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
342             RepositoryClient repoClient = getRepositoryClient(ctx);
343             String refNameProp = getRefPropName();
344
345             int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
346                     oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
347             if (logger.isDebugEnabled()) {
348                 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
349             }
350         }
351     }
352
353     /**
354      * If no short identifier was provided in the input payload, generate a
355      * short identifier from the preferred term display name or term name.
356      */
357         private void handleDisplayNameAsShortIdentifier(DocumentModel docModel,
358                         String schemaName) throws Exception {
359                 String shortIdentifier = (String) docModel.getProperty(schemaName,
360                                 AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
361
362                 String termDisplayName = getPrimaryDisplayName(
363                                 docModel, authorityItemCommonSchemaName,
364                                 getItemTermInfoGroupXPathBase(),
365                                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
366
367                 String termName = getPrimaryDisplayName(
368                                 docModel, authorityItemCommonSchemaName,
369                                 getItemTermInfoGroupXPathBase(),
370                                 AuthorityItemJAXBSchema.TERM_NAME);
371
372                 if (Tools.isEmpty(shortIdentifier)) {
373                         String generatedShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(termDisplayName,
374                                                         termName);
375                         docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER,
376                                         generatedShortIdentifier);
377                 }
378         }
379
380     /**
381      * Generate a refName for the authority item from the short identifier
382      * and display name.
383      * 
384      * All refNames for authority items are generated.  If a client supplies
385      * a refName, it will be overwritten during create (per this method) 
386      * or discarded during update (per filterReadOnlyPropertiesForPart).
387      * 
388      * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
389      * 
390      */
391     protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
392             String schemaName,
393             String authorityRefBaseName) throws Exception {
394         DocumentModel docModel = wrapDoc.getWrappedObject();
395         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
396         String displayName = getPrimaryDisplayName(docModel, authorityItemCommonSchemaName,
397                     getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
398         
399         if (Tools.isEmpty(authorityRefBaseName)) {
400             throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
401         }
402         
403         RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
404         String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
405         docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
406     }
407
408     /**
409      * Check the logic around the parent pointer. Note that we only need do this on
410      * create, since we have logic to make this read-only on update. 
411      * 
412      * @param docModel
413      * 
414      * @throws Exception the exception
415      */
416     private void handleInAuthority(DocumentModel docModel) throws Exception {
417         if(inAuthority==null) { // Only happens on queries to wildcarded authorities
418                 throw new IllegalStateException("Trying to Create an object with no inAuthority value!");
419         }
420         docModel.setProperty(authorityItemCommonSchemaName,
421                 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
422     }
423     
424     
425     public AuthorityRefDocList getReferencingObjects(
426                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
427                 List<String> serviceTypes,
428                 String propertyName,
429             String itemcsid) throws Exception {
430         AuthorityRefDocList authRefDocList = null;
431         RepositoryInstance repoSession = null;
432         boolean releaseRepoSession = false;
433         
434         try { 
435                 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
436                 repoSession = this.getRepositorySession();
437                 if (repoSession == null) {
438                         repoSession = repoClient.getRepositorySession();
439                         releaseRepoSession = true;
440                 }
441             DocumentFilter myFilter = getDocumentFilter();
442
443                 try {
444                         DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, itemcsid);
445                         DocumentModel docModel = wrapper.getWrappedObject();
446                         String refName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
447                 authRefDocList = RefNameServiceUtils.getAuthorityRefDocs(
448                                 repoSession, ctx, repoClient,
449                         serviceTypes,
450                         refName,
451                         propertyName,
452                         myFilter.getPageSize(), myFilter.getStartPage(), true /*computeTotal*/);
453                 } catch (PropertyException pe) {
454                         throw pe;
455                 } catch (DocumentException de) {
456                         throw de;
457                 } catch (Exception e) {
458                         if (logger.isDebugEnabled()) {
459                                 logger.debug("Caught exception ", e);
460                         }
461                         throw new DocumentException(e);
462                 } finally {
463                         if (releaseRepoSession && repoSession != null) {
464                                 repoClient.releaseRepositorySession(repoSession);
465                         }
466                 }
467         } catch (Exception e) {
468                 if (logger.isDebugEnabled()) {
469                         logger.debug("Caught exception ", e);
470                 }
471                 throw new DocumentException(e);
472         }               
473         return authRefDocList;
474     }
475
476
477
478
479     /* (non-Javadoc)
480      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
481      */
482     @Override
483     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
484             throws Exception {
485         Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
486
487         // Add the CSID to the common part, since they may have fetched via the shortId.
488         if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
489             String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
490             unQObjectProperties.put("csid", csid);
491         }
492
493         return unQObjectProperties;
494     }
495
496     /**
497      * Filters out selected values supplied in an update request.
498      * 
499      * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
500      * that the link to the item's parent remains untouched.
501      * 
502      * @param objectProps the properties filtered out from the update payload
503      * @param partMeta metadata for the object to fill
504      */
505     @Override
506     public void filterReadOnlyPropertiesForPart(
507             Map<String, Object> objectProps, ObjectPartType partMeta) {
508         super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
509         String commonPartLabel = getServiceContext().getCommonPartLabel();
510         if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
511             objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
512             objectProps.remove(AuthorityItemJAXBSchema.CSID);
513             objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
514             objectProps.remove(AuthorityItemJAXBSchema.REF_NAME);
515         }
516     }
517
518     @Override
519     public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
520         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
521         super.extractAllParts(wrapDoc);
522
523         String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
524         if (Tools.isTrue(showSiblings)) {
525             showSiblings(wrapDoc, ctx);
526             return;   // actual result is returned on ctx.addOutputPart();
527         }
528
529         String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
530         if (Tools.isTrue(showRelations)) {
531             showRelations(wrapDoc, ctx);
532             return;   // actual result is returned on ctx.addOutputPart();
533         }
534
535         String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
536         if (Tools.isTrue(showAllRelations)) {
537             showAllRelations(wrapDoc, ctx);
538             return;   // actual result is returned on ctx.addOutputPart();
539         }
540     }
541
542     /** @return null on parent not found
543      */
544     protected String getParentCSID(String thisCSID) throws Exception {
545         String parentCSID = null;
546         try {
547             String predicate = RelationshipType.HAS_BROADER.value();
548             RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
549             List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
550             if (parentList != null) {
551                 if (parentList.size() == 0) {
552                     return null;
553                 }
554                 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
555                 parentCSID = relationListItem.getObjectCsid();
556             }
557             return parentCSID;
558         } catch (Exception e) {
559             logger.error("Could not find parent for this: " + thisCSID, e);
560             return null;
561         }
562     }
563
564     public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
565             MultipartServiceContext ctx) throws Exception {
566         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
567
568         String predicate = RelationshipType.HAS_BROADER.value();
569         RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
570         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
571
572         RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
573         List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
574
575         if(logger.isTraceEnabled()) {
576             String dump = dumpLists(thisCSID, parentList, childrenList, null);
577             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
578         }
579         
580         //Assume that there are more children than parents.  Will be true for parent/child, but maybe not for other relations.
581         //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
582         //Not optimal, but that's the current design spec.
583         long added = 0;
584         for (RelationsCommonList.RelationListItem parent : parentList) {
585             childrenList.add(parent);
586             added++;
587         }
588         long childrenSize = childrenList.size();
589         childrenListOuter.setTotalItems(childrenSize);
590         childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
591
592         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
593         ctx.addOutputPart(relationsPart);
594     }
595
596     public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
597             MultipartServiceContext ctx) throws Exception {
598         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
599         String parentCSID = getParentCSID(thisCSID);
600         if (parentCSID == null) {
601             logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
602             return;
603         }
604
605         String predicate = RelationshipType.HAS_BROADER.value();
606         RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
607         List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
608
609         List<RelationsCommonList.RelationListItem> toRemoveList = newList();
610
611
612         RelationsCommonList.RelationListItem item = null;
613         for (RelationsCommonList.RelationListItem sibling : siblingList) {
614             if (thisCSID.equals(sibling.getSubjectCsid())) {
615                 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.
616             }
617         }
618         //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.
619         for (RelationsCommonList.RelationListItem self : toRemoveList) {
620             removeFromList(siblingList, self);
621         }
622
623         long siblingSize = siblingList.size();
624         siblingListOuter.setTotalItems(siblingSize);
625         siblingListOuter.setItemsInPage(siblingSize);
626         if(logger.isTraceEnabled()) {
627             String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
628             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
629         }
630
631         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
632         ctx.addOutputPart(relationsPart);
633     }
634
635     public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
636         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
637
638         RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null);   //  nulls are wildcards:  predicate=*, and object=*
639         List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
640
641         RelationsCommonList objectListOuter = getRelations(null, thisCSID, null);   //  nulls are wildcards:  subject=*, and predicate=*
642         List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
643
644         if(logger.isTraceEnabled()) {
645             String dump = dumpLists(thisCSID, subjectList, objectList, null);
646             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
647         }
648         //  MERGE LISTS:
649         subjectList.addAll(objectList);
650
651         //now subjectList actually has records BOTH where thisCSID is subject and object.
652         long relatedSize = subjectList.size();
653         subjectListOuter.setTotalItems(relatedSize);
654         subjectListOuter.setItemsInPage(relatedSize);
655
656         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
657         ctx.addOutputPart(relationsPart);
658     }
659
660     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
661         super.fillAllParts(wrapDoc, action);
662         /*
663         ServiceContext ctx = getServiceContext();
664         PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
665         DocumentModel documentModel = (wrapDoc.getWrappedObject());
666         String itemCsid = documentModel.getName();
667         
668         //UPDATE and CREATE will call.   Updates relations part
669         RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
670         
671         PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
672         ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
673          */
674     }
675
676     public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
677         super.completeCreate(wrapDoc);
678         handleRelationsPayload(wrapDoc, false);
679     }
680
681     public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
682         super.completeUpdate(wrapDoc);
683         handleRelationsPayload(wrapDoc, true);
684         handleItemRefNameReferenceUpdate();
685     }
686
687     // Note that we must do this after we have completed the Update, so that the repository has the
688     // info for the item itself. The relations code must call into the repo to get info for each end.
689     // This could be optimized to pass in the parent docModel, since it will often be one end.
690     // Nevertheless, we should complete the item save before we do work on the relations, especially
691     // since a save on Create might fail, and we would not want to create relations for something
692     // that may not be created...
693     private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
694         ServiceContext ctx = getServiceContext();
695         PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
696         DocumentModel documentModel = (wrapDoc.getWrappedObject());
697         String itemCsid = documentModel.getName();
698
699         //Updates relations part
700         RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
701
702         PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
703         ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
704
705         //now we add part for relations list
706         //ServiceContext ctx = getServiceContext();
707         //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
708         ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
709     }
710
711     /**  updateRelations strategy:
712     
713     go through inboundList, remove anything from childList that matches  from childList
714     go through inboundList, remove anything from parentList that matches  from parentList
715     go through parentList, delete all remaining
716     go through childList, delete all remaining
717     go through actionList, add all remaining.
718     check for duplicate children
719     check for more than one parent.
720     
721     inboundList                           parentList                      childList          actionList
722     ----------------                          ---------------                  ----------------       ----------------
723     child-a                                   parent-c                        child-a             child-b
724     child-b                                   parent-d                        child-c
725     parent-a
726      */
727     private RelationsCommonList updateRelations(
728             String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
729             throws Exception {
730         if (logger.isTraceEnabled()) {
731             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
732         }
733         PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME);        //input.getPart("relations_common");
734         if (part == null) {
735             return null;  //nothing to do--they didn't send a list of relations.
736         }
737         RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
738         List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
739         List<RelationsCommonList.RelationListItem> actionList = newList();
740         List<RelationsCommonList.RelationListItem> childList = null;
741         List<RelationsCommonList.RelationListItem> parentList = null;
742         DocumentModel docModel = wrapDoc.getWrappedObject();
743                 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
744
745         ServiceContext ctx = getServiceContext();
746         //Do magic replacement of ${itemCSID} and fix URI's.
747         fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
748
749         String HAS_BROADER = RelationshipType.HAS_BROADER.value();
750         UriInfo uriInfo = ctx.getUriInfo();
751         MultivaluedMap queryParams = uriInfo.getQueryParameters();
752
753         if (fUpdate) {
754             //Run getList() once as sent to get childListOuter:
755             String predicate = RelationshipType.HAS_BROADER.value();
756             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
757             queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
758             queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
759             queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
760             queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
761             RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo());    //magically knows all query params because they are in the context.
762
763             //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
764             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
765             queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
766             queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
767             RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
768
769
770             childList = childListOuter.getRelationListItem();
771             parentList = parentListOuter.getRelationListItem();
772
773             if (parentList.size() > 1) {
774                 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
775             }
776
777             if (logger.isTraceEnabled()) {
778                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
779             }
780         }
781
782
783         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
784             // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
785             // and so the CSID for those may be null
786             if(inboundItem.getPredicate().equals(HAS_BROADER)) {
787                 // Look for parents and children
788                 if(itemCSID.equals(inboundItem.getObject().getCsid())
789                                 || itemRefName.equals(inboundItem.getObject().getRefName())) {
790                         //then this is an item that says we have a child.  That child is inboundItem
791                         RelationsCommonList.RelationListItem childItem =
792                                         (childList == null) ? null : findInList(childList, inboundItem);
793                         if (childItem != null) {
794                         if (logger.isTraceEnabled()) {
795                                 StringBuilder sb = new StringBuilder();
796                                 itemToString(sb, "== Child: ", childItem);
797                             logger.trace("Found inboundChild in current child list: " + sb.toString());
798                         }
799                                 removeFromList(childList, childItem);    //exists, just take it off delete list
800                         } else {
801                         if (logger.isTraceEnabled()) {
802                                 StringBuilder sb = new StringBuilder();
803                                 itemToString(sb, "== Child: ", inboundItem);
804                             logger.trace("inboundChild not in current child list, will add: " + sb.toString());
805                         }
806                                 actionList.add(inboundItem);   //doesn't exist as a child, but is a child.  Add to additions list
807                                 String newChildCsid = inboundItem.getSubject().getCsid();
808                                 if(newChildCsid == null) {
809                                         String newChildRefName = inboundItem.getSubject().getRefName();
810                                         if(newChildRefName==null) {
811                                                 throw new RuntimeException("Child with no CSID or refName!");
812                                         }
813                             if (logger.isTraceEnabled()) {
814                                 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
815                             }
816                                 DocumentModel newChildDocModel = 
817                                         ResourceBase.getDocModelForRefName(this.getRepositorySession(), 
818                                                         newChildRefName, getServiceContext().getResourceMap());
819                                 newChildCsid = getCsid(newChildDocModel);
820                                 }
821                                 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
822                         }
823
824                 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
825                                         || itemRefName.equals(inboundItem.getSubject().getRefName())) {
826                         //then this is an item that says we have a parent.  inboundItem is that parent.
827                         RelationsCommonList.RelationListItem parentItem =
828                                         (parentList == null) ? null : findInList(parentList, inboundItem);
829                         if (parentItem != null) {
830                                 removeFromList(parentList, parentItem);    //exists, just take it off delete list
831                         } else {
832                                 actionList.add(inboundItem);   //doesn't exist as a parent, but is a parent. Add to additions list
833                         }
834                 } else {
835                     logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
836                 }
837             } else {
838                 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
839             }
840         }
841         if (logger.isTraceEnabled()) {
842             String dump = dumpLists(itemCSID, parentList, childList, actionList);
843             logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
844         }
845         if (fUpdate) {
846             if (logger.isTraceEnabled()) {
847                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
848                         + parentList.size() + " existing parents and " + childList.size() + " existing children.");
849             }
850             deleteRelations(parentList, ctx, "parentList");               //todo: there are items appearing on both lists....april 20.
851             deleteRelations(childList, ctx, "childList");
852         }
853         if (logger.isTraceEnabled()) {
854             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
855                     + actionList.size() + " new parents and children.");
856         }
857         createRelations(actionList, ctx);
858         if (logger.isTraceEnabled()) {
859             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
860         }
861         //We return all elements on the inbound list, since we have just worked to make them exist in the system
862         // and be non-redundant, etc.  That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
863         return relationsCommonListBody;
864     }
865
866     private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
867         logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
868         queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
869         queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
870         queryParams.putSingle(IRelationsManager.OBJECT_QP, null);  //null means ANY
871         RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
872         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
873         //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
874         deleteRelations(parentList, ctx, "parentList-delete");
875     }
876
877     
878     private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
879         sb.append(prefix);
880                 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
881         sb.append(": ["); 
882         sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
883         sb.append("]--");
884         sb.append(item.getPredicate());
885         sb.append("-->["); 
886         sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
887         sb.append("]");
888     }
889     
890     private String dumpLists(String itemCSID,
891             List<RelationsCommonList.RelationListItem> parentList,
892             List<RelationsCommonList.RelationListItem> childList,
893             List<RelationsCommonList.RelationListItem> actionList) {
894         StringBuilder sb = new StringBuilder();
895         sb.append("itemCSID: " + itemCSID + CR);
896         if(parentList!=null) {
897                 sb.append(dumpList(parentList, "parentList"));
898         }
899         if(childList!=null) {
900                 sb.append(dumpList(childList, "childList"));
901         }
902         if(actionList!=null) {
903                 sb.append(dumpList(actionList, "actionList"));
904         }
905         return sb.toString();
906     }
907     private final static String CR = "\r\n";
908     private final static String T = " ";
909
910     private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
911         StringBuilder sb = new StringBuilder();
912         String s;
913         if (list.size() > 0) {
914             sb.append("=========== " + label + " ==========" + CR);
915         }
916         for (RelationsCommonList.RelationListItem item : list) {
917                 itemToString(sb, "==  ", item);
918                 sb.append(CR);
919         }
920         return sb.toString();
921     }
922
923     /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
924      *   and sets URI correctly for related items.
925      *   Operates directly on the items in the list.  Does not change the list ordering, does not add or remove any items.
926      */
927     protected void fixupInboundListItems(ServiceContext ctx,
928             List<RelationsCommonList.RelationListItem> inboundList,
929             DocumentModel docModel,
930             String itemCSID) throws Exception {
931         String thisURI = this.getUri(docModel);
932         // WARNING:  the two code blocks below are almost identical  and seem to ask to be put in a generic method.
933         //                    beware of the little diffs in  inboundItem.setObjectCsid(itemCSID); and   inboundItem.setSubjectCsid(itemCSID); in the two blocks.
934         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
935             RelationsDocListItem inboundItemObject = inboundItem.getObject();
936             RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
937
938             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
939                 inboundItem.setObjectCsid(itemCSID);
940                 inboundItemObject.setCsid(itemCSID);
941                 //inboundItemObject.setUri(getUri(docModel));
942             } else {
943                 /*
944                 String objectCsid = inboundItemObject.getCsid();
945                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid);    //null if not found.
946                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
947                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
948                 inboundItemObject.setUri(uri);    //CSPACE-4037
949                  */
950             }
951             //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri());    //CSPACE-4042
952
953             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
954                 inboundItem.setSubjectCsid(itemCSID);
955                 inboundItemSubject.setCsid(itemCSID);
956                 //inboundItemSubject.setUri(getUri(docModel));
957             } else {
958                 /*
959                 String subjectCsid = inboundItemSubject.getCsid();
960                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid);    //null if not found.
961                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
962                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
963                 inboundItemSubject.setUri(uri);    //CSPACE-4037
964                  */
965             }
966             //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri());  //CSPACE-4042
967
968         }
969     }
970
971     // this method calls the RelationResource to have it create the relations and persist them.
972     private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
973         for (RelationsCommonList.RelationListItem item : inboundList) {
974             RelationsCommon rc = new RelationsCommon();
975             //rc.setCsid(item.getCsid());
976             //todo: assignTo(item, rc);
977             RelationsDocListItem itemSubject = item.getSubject();
978             RelationsDocListItem itemObject = item.getObject();
979
980             // Set at least one of CSID and refName for Subject and Object
981             // Either value might be null for for each of Subject and Object 
982             String subjectCsid = itemSubject.getCsid();
983             rc.setSubjectCsid(subjectCsid);
984
985             String objCsid = itemObject.getCsid();
986             rc.setObjectCsid(objCsid);
987
988             rc.setSubjectRefName(itemSubject.getRefName());
989             rc.setObjectRefName(itemObject.getRefName());
990
991             rc.setRelationshipType(item.getPredicate());
992             //RelationshipType  foo = (RelationshipType.valueOf(item.getPredicate())) ;
993             //rc.setPredicate(foo);     //this must be one of the type found in the enum in  services/jaxb/src/main/resources/relations_common.xsd
994
995             // This is superfluous, since it will be fetched by the Relations Create logic.
996             rc.setSubjectDocumentType(itemSubject.getDocumentType());
997             rc.setObjectDocumentType(itemObject.getDocumentType());
998
999             // This is superfluous, since it will be fetched by the Relations Create logic.
1000             rc.setSubjectUri(itemSubject.getUri());
1001             rc.setObjectUri(itemObject.getUri());
1002             // May not have the info here. Only really require CSID or refName. 
1003             // Rest is handled in the Relation create mechanism
1004             //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1005
1006             PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1007             PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1008             payloadOut.addPart(outputPart);
1009             RelationResource relationResource = new RelationResource();
1010             Object res = relationResource.create(ctx.getResourceMap(),
1011                     ctx.getUriInfo(), payloadOut.toXML());    //NOTE ui recycled from above to pass in unknown query params.
1012         }
1013     }
1014
1015     private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
1016         try {
1017             for (RelationsCommonList.RelationListItem item : list) {
1018                 RelationResource relationResource = new RelationResource();
1019                 if(logger.isTraceEnabled()) {
1020                         StringBuilder sb = new StringBuilder();
1021                         itemToString(sb, "==== TO DELETE: ", item);
1022                         logger.trace(sb.toString());
1023                 }
1024                 Object res = relationResource.delete(item.getCsid());
1025             }
1026         } catch (Throwable t) {
1027             String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1028             logger.error(msg);
1029         }
1030     }
1031
1032     private List<RelationsCommonList.RelationListItem> newList() {
1033         List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
1034         return result;
1035     }
1036
1037     protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
1038         List<RelationsCommonList.RelationListItem> result = newList();
1039         for (RelationsCommonList.RelationListItem item : inboundList) {
1040             result.add(item);
1041         }
1042         return result;
1043     }
1044
1045     // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1046     // But the list items must not be sparse
1047     private RelationsCommonList.RelationListItem findInList(
1048                 List<RelationsCommonList.RelationListItem> list, 
1049                 RelationsCommonList.RelationListItem item) {
1050         RelationsCommonList.RelationListItem foundItem = null;
1051         for (RelationsCommonList.RelationListItem listItem : list) {
1052             if (itemsEqual(listItem, item)) {   //equals must be defined, else
1053                 foundItem = listItem;
1054                 break;
1055             }
1056         }
1057         return foundItem;
1058     }
1059
1060     // Note that item2 may be sparse (only refName, no CSID for subject or object)
1061     // But item1 must not be sparse 
1062     private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1063         if (item1 == null || item2 == null) {
1064             return false;
1065         }
1066         RelationsDocListItem subj1 = item1.getSubject();
1067         RelationsDocListItem subj2 = item2.getSubject();
1068         RelationsDocListItem obj1 = item1.getObject();
1069         RelationsDocListItem obj2 = item2.getObject();
1070         String subj1Csid = subj1.getCsid();
1071         String subj2Csid = subj2.getCsid();
1072         String subj1RefName = subj1.getRefName();
1073         String subj2RefName = subj2.getRefName();
1074
1075         String obj1Csid = obj1.getCsid();
1076         String obj2Csid = obj2.getCsid();
1077         String obj1RefName = obj1.getRefName();
1078         String obj2RefName = obj2.getRefName();
1079
1080         boolean isEqual = 
1081                            (subj1Csid.equals(subj2Csid) || ((subj2Csid==null)  && subj1RefName.equals(subj2RefName)))
1082                 && (obj1Csid.equals(obj1Csid)   || ((obj2Csid==null)   && obj1RefName.equals(obj2RefName)))
1083                 // predicate is proper, but still allow relationshipType
1084                 && (item1.getPredicate().equals(item2.getPredicate())
1085                         ||  ((item2.getPredicate()==null)  && item1.getRelationshipType().equals(item2.getRelationshipType())))
1086                 // Allow missing docTypes, so long as they do not conflict
1087                 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1088                 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1089         return isEqual;
1090     }
1091
1092     private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1093         list.remove(item);
1094     }
1095
1096     /* don't even THINK of re-using this method.
1097      * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
1098      */
1099     private String extractInAuthorityCSID(String uri) {
1100         String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
1101         Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
1102         Matcher m = p.matcher(uri);
1103         if (m.find()) {
1104             if (m.groupCount() < 3) {
1105                 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
1106                 return "";
1107             } else {
1108                 //String service = m.group(1);
1109                 String inauth = m.group(2);
1110                 //String theRest = m.group(3);
1111                 return inauth;
1112                 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
1113             }
1114         } else {
1115             logger.warn("REGEX-NOT-MATCHED looking in " + uri);
1116             return "";
1117         }
1118     }
1119
1120     //ensures CSPACE-4042
1121     protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
1122         String authorityCSID = extractInAuthorityCSID(thisURI);
1123         String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
1124         if (Tools.isBlank(authorityCSID)
1125                 || Tools.isBlank(authorityCSIDForInbound)
1126                 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
1127             throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
1128         }
1129     }
1130
1131     //================= TODO: move this to common, refactoring this and  CollectionObjectResource.java
1132     public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1133         ServiceContext ctx = getServiceContext();
1134         MultivaluedMap queryParams = ctx.getQueryParams();
1135         queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1136         queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1137         queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1138
1139         RelationResource relationResource = new RelationResource();
1140         RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
1141         return relationsCommonList;
1142     }
1143     //============================= END TODO refactor ==========================
1144
1145     public String getItemTermInfoGroupXPathBase() {
1146         return authorityItemTermGroupXPathBase;
1147     }
1148         
1149     public void setItemTermInfoGroupXPathBase(String itemTermInfoGroupXPathBase) {
1150         authorityItemTermGroupXPathBase = itemTermInfoGroupXPathBase;
1151     }
1152     
1153     protected String getAuthorityItemCommonSchemaName() {
1154         return authorityItemCommonSchemaName;
1155     }
1156 }