]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
2ea1c6211bf722309d68e024df99a66f5d151f2e
[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;
25
26 import java.util.List;
27 import java.util.Map;
28
29 import javax.ws.rs.Consumes;
30 import javax.ws.rs.DELETE;
31 import javax.ws.rs.GET;
32 import javax.ws.rs.POST;
33 import javax.ws.rs.PUT;
34 import javax.ws.rs.Path;
35 import javax.ws.rs.PathParam;
36 import javax.ws.rs.Produces;
37 import javax.ws.rs.core.Context;
38 import javax.ws.rs.core.MultivaluedMap;
39 import javax.ws.rs.core.Request;
40 import javax.ws.rs.core.Response;
41 import javax.ws.rs.core.UriBuilder;
42 import javax.ws.rs.core.UriInfo;
43
44 import org.collectionspace.services.client.IClientQueryParams;
45 import org.collectionspace.services.client.IQueryManager;
46 import org.collectionspace.services.client.PoxPayloadIn;
47 import org.collectionspace.services.client.PoxPayloadOut;
48 import org.collectionspace.services.client.XmlTools;
49 import org.collectionspace.services.client.workflow.WorkflowClient;
50 import org.collectionspace.services.common.CSWebApplicationException;
51 import org.collectionspace.services.common.NuxeoBasedResource;
52 import org.collectionspace.services.common.ResourceMap;
53 import org.collectionspace.services.common.ServiceMain;
54 import org.collectionspace.services.common.ServiceMessages;
55 import org.collectionspace.services.common.StoredValuesUriTemplate;
56 import org.collectionspace.services.common.UriTemplateFactory;
57 import org.collectionspace.services.common.UriTemplateRegistry;
58 import org.collectionspace.services.common.UriTemplateRegistryKey;
59 import org.collectionspace.services.common.api.RefName;
60 import org.collectionspace.services.common.api.Tools;
61 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
62 import org.collectionspace.services.common.authorityref.AuthorityRefList;
63 import org.collectionspace.services.common.context.JaxRsContext;
64 import org.collectionspace.services.common.context.MultipartServiceContext;
65 import org.collectionspace.services.common.context.RemoteServiceContext;
66 import org.collectionspace.services.common.context.ServiceBindingUtils;
67 import org.collectionspace.services.common.context.ServiceContext;
68 import org.collectionspace.services.common.document.DocumentException;
69 import org.collectionspace.services.common.document.DocumentFilter;
70 import org.collectionspace.services.common.document.DocumentHandler;
71 import org.collectionspace.services.common.document.DocumentNotFoundException;
72 import org.collectionspace.services.common.document.DocumentReferenceException;
73 import org.collectionspace.services.common.document.DocumentWrapper;
74 import org.collectionspace.services.common.document.Hierarchy;
75 import org.collectionspace.services.common.query.QueryManager;
76 import org.collectionspace.services.common.vocabulary.nuxeo.AuthorityDocumentModelHandler;
77 import org.collectionspace.services.common.vocabulary.nuxeo.AuthorityItemDocumentModelHandler;
78 import org.collectionspace.services.common.workflow.service.nuxeo.WorkflowDocumentModelHandler;
79 import org.collectionspace.services.config.ClientType;
80 import org.collectionspace.services.config.service.ServiceBindingType;
81 import org.collectionspace.services.jaxb.AbstractCommonList;
82 import org.collectionspace.services.lifecycle.TransitionDef;
83 import org.collectionspace.services.nuxeo.client.java.DocumentModelHandler;
84 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
85 import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentFilter;
86 import org.collectionspace.services.nuxeo.client.java.RepositoryClientImpl;
87 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
88 import org.collectionspace.services.workflow.WorkflowCommon;
89 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
90 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm;
91 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
92 import org.jboss.resteasy.util.HttpResponseCodes;
93 import org.nuxeo.ecm.core.api.DocumentModel;
94 import org.nuxeo.ecm.core.api.DocumentModelList;
95 import org.slf4j.Logger;
96 import org.slf4j.LoggerFactory;
97
98 /**
99  * The Class AuthorityResource.
100  */
101
102 @Consumes("application/xml")
103 @Produces("application/xml")
104 public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
105         extends NuxeoBasedResource {
106         
107         final static String SEARCH_TYPE_TERMSTATUS = "ts";
108     public final static String hierarchy = "hierarchy";
109
110     protected Class<AuthCommon> authCommonClass;
111     protected Class<?> resourceClass;
112     protected String authorityCommonSchemaName;
113     protected String authorityItemCommonSchemaName;
114     final static ClientType CLIENT_TYPE = ServiceMain.getInstance().getClientType(); //FIXME: REM - 3 Why is this field needed?  I see no references to it.
115         
116     final static String FETCH_SHORT_ID = "_fetch_";
117     public final static String PARENT_WILDCARD = "_ALL_";
118         
119     final Logger logger = LoggerFactory.getLogger(AuthorityResource.class);
120
121     /**
122      * Instantiates a new Authority resource.
123      */
124     public AuthorityResource(Class<AuthCommon> authCommonClass, Class<?> resourceClass,
125             String authorityCommonSchemaName, String authorityItemCommonSchemaName) {
126         this.authCommonClass = authCommonClass;
127         this.resourceClass = resourceClass;
128         this.authorityCommonSchemaName = authorityCommonSchemaName;
129         this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
130     }
131
132     public abstract String getItemServiceName();
133     
134     public abstract String getItemTermInfoGroupXPathBase();
135
136     @Override
137     protected String getVersionString() {
138         return "$LastChangedRevision: 2617 $";
139     }
140
141     @Override
142     public Class<AuthCommon> getCommonPartClass() {
143         return authCommonClass;
144     }
145
146     /**
147      * Creates the item document handler.
148      * 
149      * @param ctx the ctx
150      * @param inAuthority the in vocabulary
151      * 
152      * @return the document handler
153      * 
154      * @throws Exception the exception
155      */
156     protected DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> createItemDocumentHandler(
157             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
158             String inAuthority, String parentShortIdentifier)
159             throws Exception {
160         String authorityRefNameBase;
161         AuthorityItemDocumentModelHandler<?> docHandler;
162
163         if (parentShortIdentifier == null) {
164             authorityRefNameBase = null;
165         } else {
166             ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx = createServiceContext(getServiceName());
167             if (parentShortIdentifier.equals(FETCH_SHORT_ID)) { // We need to fetch this from the repo
168                 if (ctx.getCurrentRepositorySession() != null) {
169                         parentCtx.setCurrentRepositorySession(ctx.getCurrentRepositorySession()); // We need to use the current repo session if one exists
170                 }
171                 // Get from parent document
172                 parentShortIdentifier = getAuthShortIdentifier(parentCtx, inAuthority);
173             }
174             authorityRefNameBase = buildAuthorityRefNameBase(parentCtx, parentShortIdentifier);
175         }
176
177         docHandler = (AuthorityItemDocumentModelHandler<?>) createDocumentHandler(ctx,
178                 ctx.getCommonPartLabel(getItemServiceName()),
179                 authCommonClass);
180         // FIXME - Richard and Aron think the following three lines should
181         // be in the constructor for the AuthorityItemDocumentModelHandler
182         // because all three are required fields.
183         docHandler.setInAuthority(inAuthority);
184         docHandler.setAuthorityRefNameBase(authorityRefNameBase);
185         docHandler.setItemTermInfoGroupXPathBase(getItemTermInfoGroupXPathBase());
186         return docHandler;
187     }
188
189     public String getAuthShortIdentifier(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String authCSID)
190             throws DocumentNotFoundException, DocumentException {
191         String shortIdentifier = null;
192         
193         try {
194             AuthorityDocumentModelHandler<?> handler = (AuthorityDocumentModelHandler<?>) createDocumentHandler(ctx);
195             shortIdentifier = handler.getShortIdentifier(ctx, authCSID, authorityCommonSchemaName);
196         } catch (Exception e) {
197             if (logger.isDebugEnabled()) {
198                 logger.debug("Caught exception ", e);
199             }
200             throw new DocumentException(e);
201         }
202         
203         return shortIdentifier;
204     }
205
206     protected String buildAuthorityRefNameBase(
207             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String shortIdentifier) {
208         RefName.Authority authority = RefName.Authority.buildAuthority(ctx.getTenantName(),
209                 ctx.getServiceName(), 
210                 null,   // Only use shortId form!!!
211                 shortIdentifier, null);
212         return authority.toString();
213     }
214
215     public static class CsidAndShortIdentifier {
216         String CSID;
217         String shortIdentifier;
218     }
219
220         protected String lookupParentCSID(String parentspecifier, String method,
221                         String op, UriInfo uriInfo) throws Exception {
222                 CsidAndShortIdentifier tempResult = lookupParentCSIDAndShortIdentifer(NULL_CONTEXT,
223                                 parentspecifier, method, op, uriInfo);
224                 return tempResult.CSID;
225         }
226         
227         protected String lookupParentCSID(ServiceContext ctx, String parentspecifier, String method,
228                         String op, UriInfo uriInfo) throws Exception {
229                 CsidAndShortIdentifier tempResult = lookupParentCSIDAndShortIdentifer(ctx,
230                                 parentspecifier, method, op, uriInfo);
231                 return tempResult.CSID;
232         }
233
234
235     private CsidAndShortIdentifier lookupParentCSIDAndShortIdentifer(
236                 ServiceContext existingCtx, // Ok to be null
237                 String parentIdentifier,
238                 String method,
239                 String op,
240                 UriInfo uriInfo)
241             throws Exception {
242         CsidAndShortIdentifier result = new CsidAndShortIdentifier();
243         Specifier parentSpec = Specifier.getSpecifier(parentIdentifier, method, op);
244         
245         String parentcsid;
246         String parentShortIdentifier;
247         if (parentSpec.form == SpecifierForm.CSID) {
248             parentShortIdentifier = null;
249             parentcsid = parentSpec.value;
250             // Uncomment when app layer is ready to integrate
251             // Uncommented since refNames are currently only generated if not present - ADR CSPACE-3178
252             parentShortIdentifier = FETCH_SHORT_ID;
253         } else {
254             parentShortIdentifier = parentSpec.value;
255             String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, parentShortIdentifier);
256             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getServiceName(), uriInfo);
257             CoreSessionInterface repoSession = null;
258             if (existingCtx != null) {
259                 repoSession = (CoreSessionInterface) existingCtx.getCurrentRepositorySession();  // We want to use the thread's current repo session
260             }
261             parentcsid = getRepositoryClient(ctx).findDocCSID(repoSession, ctx, whereClause); //FIXME: REM - If the parent has been soft-deleted, should we be looking for the item?
262         }
263         
264         result.CSID = parentcsid;
265         result.shortIdentifier = parentShortIdentifier;
266         
267         return result;
268     }
269
270     public String lookupItemCSID(ServiceContext<PoxPayloadIn, PoxPayloadOut> existingContext, String itemspecifier, String parentcsid, String method, String op)
271             throws Exception {
272         String itemcsid;
273         
274         Specifier itemSpec = Specifier.getSpecifier(itemspecifier, method, op);
275         if (itemSpec.form == SpecifierForm.CSID) {
276             itemcsid = itemSpec.value;
277         } else {
278             String itemWhereClause = RefNameServiceUtils.buildWhereForAuthItemByName(authorityItemCommonSchemaName, itemSpec.value, parentcsid);
279                 MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(getItemServiceName());
280             CoreSessionInterface repoSession = null;
281             if (existingContext != null) {
282                 repoSession = (CoreSessionInterface) existingContext.getCurrentRepositorySession();  // We want to use the thread's current repo session
283             }
284             itemcsid = getRepositoryClient(ctx).findDocCSID(repoSession, ctx, itemWhereClause); //FIXME: REM - Should we be looking for the 'wf_deleted' query param and filtering on it?
285         }
286         
287         return itemcsid;
288     }
289
290     /*
291      * Generally, callers will first call RefName.AuthorityItem.parse with a refName, and then 
292      * use the returned item.inAuthority.resource and a resourceMap to get a service-specific
293      * Resource. They then call this method on that resource.
294      */
295     @Override
296         public DocumentModel getDocModelForAuthorityItem(CoreSessionInterface repoSession, RefName.AuthorityItem item) 
297                         throws Exception, DocumentNotFoundException {
298         if(item == null) {
299                 return null;
300         }
301         String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, item.getParentShortIdentifier());
302         // Ensure we have the right context.
303         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(item.inAuthority.resource);
304         
305         // HACK - this really must be moved to the doc handler, not here. No Nuxeo specific stuff here!
306         RepositoryClientImpl client = (RepositoryClientImpl)getRepositoryClient(ctx);
307         String parentcsid = client.findDocCSID(repoSession, ctx, whereClause);
308
309         String itemWhereClause = RefNameServiceUtils.buildWhereForAuthItemByName(authorityItemCommonSchemaName, item.getShortIdentifier(), parentcsid);
310         ctx = createServiceContext(getItemServiceName());
311         DocumentWrapper<DocumentModel> docWrapper = client.findDoc(repoSession, ctx, itemWhereClause);
312         DocumentModel docModel = docWrapper.getWrappedObject();
313         return docModel;
314     }
315
316
317     @POST
318     public Response createAuthority(String xmlPayload) {
319         //
320         // Requests to create new authorities come in on new threads. Unfortunately, we need to synchronize those threads on this block because, as of 8/27/2015, we can't seem to get Nuxeo
321         // transaction code to deal with a database level UNIQUE constraint violations on the 'shortidentifier' column of the vocabularies_common table.
322         // Therefore, to prevent having multiple authorities with the same shortid, we need to synchronize
323         // the code that creates new authorities.  The authority document model handler will first check for authorities with the same short id before
324         // trying to create a new authority.
325         //
326         synchronized(AuthorityResource.class) {
327                 try {
328                     PoxPayloadIn input = new PoxPayloadIn(xmlPayload);
329                     ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(input);
330                     DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
331                     
332                     String csid = getRepositoryClient(ctx).create(ctx, handler);
333                     UriBuilder path = UriBuilder.fromResource(resourceClass);
334                     path.path("" + csid);
335                     Response response = Response.created(path.build()).build();
336                     return response;
337                 } catch (Exception e) {
338                     throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
339                 }
340         }
341     }
342
343     protected boolean supportsReplicating(String tenantId, String serviceName) {
344         boolean result = false;
345         
346         ServiceBindingType sb = getTenantBindingsReader().getServiceBinding(tenantId, getServiceName());
347         result = sb.isSupportsReplicating();
348         
349         return result;
350     }
351
352     /**
353      * Synchronizes the authority and its items/terms with a Shared Authority Server.
354      * 
355      * @param specifier either a CSID or one of the urn forms
356      * 
357      * @return the authority
358      */
359     @POST
360     @Path("{csid}/sync")
361     public byte[] synchronize(
362             @Context Request request,
363             @Context UriInfo ui,
364             @PathParam("csid") String identifier) {
365         byte[] result;
366         boolean neededSync = false;
367         PoxPayloadOut payloadOut = null;
368         Specifier specifier;
369         
370         //
371         // Prevent multiple SAS synchronizations from occurring simultaneously by synchronizing this method.
372         //
373         synchronized(AuthorityResource.class) {        
374                 try {
375                     ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(ui);
376                     /*
377                      * Make sure this authority service supports synchronization
378                      */
379                     if (supportsReplicating(ctx.getTenantId(), ctx.getServiceName()) == false) {
380                         throw new DocumentException(Response.Status.FORBIDDEN.getStatusCode());
381                     }
382                     AuthorityDocumentModelHandler handler = (AuthorityDocumentModelHandler)createDocumentHandler(ctx);
383                     specifier = Specifier.getSpecifier(identifier, "getAuthority", "GET");
384                     handler.setShouldUpdateRevNumber(AuthorityServiceUtils.DONT_UPDATE_REV); // Never update rev number on sync calls
385                     neededSync = getRepositoryClient(ctx).synchronize(ctx, specifier, handler);
386                     payloadOut = ctx.getOutput();
387                 } catch (Exception e) {
388                     throw bigReThrow(e, ServiceMessages.SYNC_FAILED, identifier);
389                 }
390         
391                 //
392                 // If a sync was needed and was successful, return a copy of the updated resource.  Acts like an UPDATE.
393                 //
394                 if (neededSync == true) {
395                         result = payloadOut.getBytes();
396                 } else {
397                         result = String.format("Authority resource '%s' was already in sync with shared authority server.",
398                                         specifier.value).getBytes();
399                         Response response = Response.status(Response.Status.NOT_MODIFIED).entity(result).type("text/plain").build();
400                     throw new CSWebApplicationException(response);
401                 }
402         }
403                 
404         return result;
405     }
406         
407     /**
408      * Gets the authority.
409      * 
410      * @param specifier either a CSID or one of the urn forms
411      * 
412      * @return the authority
413      */
414     @GET
415     @Path("{csid}")
416     @Override
417     public byte[] get(
418             @Context Request request,
419             @Context UriInfo ui,
420             @PathParam("csid") String specifier) {
421         PoxPayloadOut result = null;
422         try {
423             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(ui);
424             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
425
426             Specifier spec = Specifier.getSpecifier(specifier, "getAuthority", "GET");
427             if (spec.form == SpecifierForm.CSID) {
428                 if (logger.isDebugEnabled()) {
429                     logger.debug("getAuthority with csid=" + spec.value);
430                 }
431                 getRepositoryClient(ctx).get(ctx, spec.value, handler);
432             } else {
433                 String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value);
434                 DocumentFilter myFilter = new NuxeoDocumentFilter(whereClause, 0, 1);
435                 handler.setDocumentFilter(myFilter);
436                 getRepositoryClient(ctx).get(ctx, handler);
437             }
438             result = ctx.getOutput();
439
440         } catch (Exception e) {
441             throw bigReThrow(e, ServiceMessages.GET_FAILED, specifier);
442         }
443
444         if (result == null) {
445             Response response = Response.status(Response.Status.NOT_FOUND).entity(
446                     "Get failed, the requested Authority specifier:" + specifier + ": was not found.").type(
447                     "text/plain").build();
448             throw new CSWebApplicationException(response);
449         }
450
451         return result.getBytes();
452     }
453
454     /**
455      * Finds and populates the authority list.
456      * 
457      * @param ui the ui
458      * 
459      * @return the authority list
460      */
461     @GET
462     @Produces("application/xml")
463     public AbstractCommonList getAuthorityList(@Context UriInfo uriInfo) { //FIXME - REM 5/3/2012 - This is not reachable from the JAX-RS dispatcher.  Instead the equivalent method in ResourceBase is getting called.
464         AbstractCommonList result = null;
465         
466         try {
467             MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
468             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(uriInfo);
469             
470             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
471             DocumentFilter myFilter = handler.getDocumentFilter();
472             // Need to make the default sort order for authority items
473             // be on the displayName field
474             String sortBy = queryParams.getFirst(IClientQueryParams.ORDER_BY_PARAM);
475             if (sortBy == null || sortBy.isEmpty()) {
476                 String qualifiedDisplayNameField = authorityCommonSchemaName + ":"
477                         + AuthorityItemJAXBSchema.DISPLAY_NAME;
478                 myFilter.setOrderByClause(qualifiedDisplayNameField);
479             }
480             String nameQ = queryParams.getFirst("refName");
481             if (nameQ != null) {
482                 myFilter.setWhereClause(authorityCommonSchemaName + ":refName='" + nameQ + "'");
483             }
484             getRepositoryClient(ctx).getFiltered(ctx, handler);
485             result = handler.getCommonPartList();
486         } catch (Exception e) {
487             throw bigReThrow(e, ServiceMessages.GET_FAILED);
488         }
489         
490         return result;
491     }
492     
493     /**
494      * Overriding this methods to see if we should update the revision number during the update.  We don't
495      * want to update the rev number of synchronization operations.
496      */
497     @Override
498     protected PoxPayloadOut update(String csid,
499             PoxPayloadIn theUpdate, // not used in this method, but could be used by an overriding method
500             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
501             throws Exception {
502         AuthorityDocumentModelHandler handler = (AuthorityDocumentModelHandler) createDocumentHandler(ctx);
503         Boolean shouldUpdateRev = (Boolean) ctx.getProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY);
504         if (shouldUpdateRev != null) {
505                 handler.setShouldUpdateRevNumber(shouldUpdateRev);
506         }
507         getRepositoryClient(ctx).update(ctx, csid, handler);
508         return ctx.getOutput();
509     }
510     
511     /**
512      * Update authority.
513      *
514      * @param specifier the csid or id
515      *
516      * @return the multipart output
517      */
518     @PUT
519     @Path("{csid}")
520     public byte[] updateAuthority(
521             @PathParam("csid") String specifier,
522             String xmlPayload) {
523         PoxPayloadOut result = null;
524         try {
525             PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload);
526             Specifier spec = Specifier.getSpecifier(specifier, "updateAuthority", "UPDATE");
527             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(theUpdate);
528             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
529             String csid;
530             if (spec.form == SpecifierForm.CSID) {
531                 csid = spec.value;
532             } else {
533                 String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value);
534                 csid = getRepositoryClient(ctx).findDocCSID(null, ctx, whereClause);
535             }
536             getRepositoryClient(ctx).update(ctx, csid, handler);
537             result = ctx.getOutput();
538         } catch (Exception e) {
539             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED);
540         }
541         return result.getBytes();
542     }
543
544     /**
545      * Delete authority.
546      * 
547      * @param csid the csid
548      * 
549      * @return the response
550      */
551     @Deprecated
552 //    @DELETE
553     @Path("{csid}")
554     public Response old_deleteAuthority(@PathParam("csid") String csid) {
555         if (logger.isDebugEnabled()) {
556             logger.debug("deleteAuthority with csid=" + csid);
557         }
558         try {
559             ensureCSID(csid, ServiceMessages.DELETE_FAILED, "Authority.csid");
560             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext();
561             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
562             getRepositoryClient(ctx).delete(ctx, csid, handler);
563             return Response.status(HttpResponseCodes.SC_OK).build();
564         } catch (Exception e) {
565             throw bigReThrow(e, ServiceMessages.DELETE_FAILED, csid);
566         }
567     }
568     
569     /**
570      * Delete authority
571      * 
572      * @param csid the csid or a URN specifier form -e.g., urn:cspace:name(OurMuseumPersonAuthority)
573      * 
574      * @return the response
575      */
576     @DELETE
577     @Path("{csid}")
578     public Response deleteAuthority(
579             @Context Request request,
580             @Context UriInfo ui,
581             @PathParam("csid") String specifier) {
582         if (logger.isDebugEnabled()) {
583             logger.debug("deleteAuthority with specifier=" + specifier);
584         }
585         
586         try {
587             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(ui);
588             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
589
590             Specifier spec = Specifier.getSpecifier(specifier, "getAuthority", "GET");
591             if (spec.form == SpecifierForm.CSID) {
592                 if (logger.isDebugEnabled()) {
593                     logger.debug("deleteAuthority with csid=" + spec.value);
594                 }
595                 ensureCSID(spec.value, ServiceMessages.DELETE_FAILED, "Authority.csid");
596                 getRepositoryClient(ctx).delete(ctx, spec.value, handler);
597             } else {
598                 if (logger.isDebugEnabled()) {
599                     logger.debug("deleteAuthority with specifier=" + spec.value);
600                 }               
601                 String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value);
602                 getRepositoryClient(ctx).deleteWithWhereClause(ctx, whereClause, handler);
603             }
604             
605             return Response.status(HttpResponseCodes.SC_OK).build();
606         } catch (Exception e) {
607             throw bigReThrow(e, ServiceMessages.DELETE_FAILED, specifier);
608         }
609     }
610     
611     /**
612      * 
613      * @param ctx
614      * @param parentspecifier           - ID of the container. Can be URN or CSID form
615      * @param shouldUpdateRevNumber - Indicates if the revision number should be updated on create -won't do this when synching with SAS
616      * @param isProposed                                - In a shared authority context, indicates if this item just a proposed item and not yet part of the SAS authority
617      * @return
618      * @throws Exception
619      */
620     protected Response createAuthorityItem(ServiceContext ctx, String parentIdentifier,
621                 boolean shouldUpdateRevNumber,
622                 boolean isProposed,
623                 boolean isSasItem) throws Exception {
624         Response result = null;
625         
626         // Note: must have the parentShortId, to do the create.
627         CsidAndShortIdentifier parent = lookupParentCSIDAndShortIdentifer(ctx, parentIdentifier, "createAuthorityItem", "CREATE_ITEM", null);
628         AuthorityItemDocumentModelHandler handler = 
629                 (AuthorityItemDocumentModelHandler) createItemDocumentHandler(ctx, parent.CSID, parent.shortIdentifier);
630         handler.setShouldUpdateRevNumber(shouldUpdateRevNumber);
631         handler.setIsProposed(isProposed);
632         handler.setIsSASItem(isSasItem);
633         // Make the client call
634         String itemcsid = getRepositoryClient(ctx).create(ctx, handler);
635         
636         // Build the JAX-RS response
637         UriBuilder path = UriBuilder.fromResource(resourceClass);
638         path.path(parent.CSID + "/items/" + itemcsid);
639         result = Response.created(path.build()).build();
640
641         return result;
642     }
643
644     /**
645      * Called with an existing context.
646      * @param parentCtx
647      * @param parentIdentifier
648      * @param input
649      * @return
650      * @throws Exception
651      */
652     public Response createAuthorityItemWithParentContext(ServiceContext parentCtx,
653                 String parentIdentifier,
654                 PoxPayloadIn input,
655                 boolean shouldUpdateRevNumber,
656                 boolean isProposed,
657                 boolean isSASItem) throws Exception {
658         Response result = null;
659         
660         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), input,
661                         parentCtx.getResourceMap(), parentCtx.getUriInfo());
662         if (parentCtx.getCurrentRepositorySession() != null) {
663                 ctx.setCurrentRepositorySession(parentCtx.getCurrentRepositorySession());
664         }
665         result = this.createAuthorityItem(ctx, parentIdentifier, shouldUpdateRevNumber, isProposed, isSASItem);
666
667         return result;
668     }
669         
670     /*************************************************************************
671      * Create an AuthorityItem - this is a sub-resource of Authority
672      * @param specifier either a CSID or one of the urn forms
673      * @return Authority item response
674      *************************************************************************/
675     @POST
676     @Path("{csid}/items")
677     public Response createAuthorityItem(
678                 @Context ResourceMap resourceMap,
679                 @Context UriInfo uriInfo, 
680                 @PathParam("csid") String parentIdentifier, // Either a CSID or a URN form -e.g., a8ad38ec-1d7d-4bf2-bd31 or urn:cspace:name(bugsbunny)
681                 String xmlPayload) {
682         Response result = null;
683         
684         try {
685             PoxPayloadIn input = new PoxPayloadIn(xmlPayload);
686             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), input, resourceMap, uriInfo);
687             result = this.createAuthorityItem(ctx, parentIdentifier, AuthorityServiceUtils.UPDATE_REV,
688                         AuthorityServiceUtils.PROPOSED, AuthorityServiceUtils.NOT_SAS_ITEM);
689         } catch (Exception e) {
690             throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
691         }
692         
693         return result;
694     }
695
696     @GET
697     @Path("{csid}/items/{itemcsid}" + WorkflowClient.SERVICE_PATH)
698     public byte[] getItemWorkflow(
699             @PathParam("csid") String csid,
700             @PathParam("itemcsid") String itemcsid) {
701         PoxPayloadOut result = null;
702
703         try {
704             ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx = createServiceContext(getItemServiceName());
705             String parentWorkspaceName = parentCtx.getRepositoryWorkspaceName();
706
707             MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(WorkflowClient.SERVICE_NAME);
708             WorkflowDocumentModelHandler handler = createWorkflowDocumentHandler(ctx);
709             ctx.setRespositoryWorkspaceName(parentWorkspaceName); //find the document in the parent's workspace
710             getRepositoryClient(ctx).get(ctx, itemcsid, handler);
711             result = ctx.getOutput();
712         } catch (Exception e) {
713             throw bigReThrow(e, ServiceMessages.READ_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, csid);
714         }
715         return result.getBytes();
716     }
717
718     //FIXME: This method is almost identical to the method org.collectionspace.services.common.updateWorkflowWithTransition() so
719     // they should be consolidated -be DRY (D)on't (R)epeat (Y)ourself.
720     @PUT
721     @Path("{csid}/items/{itemcsid}" + WorkflowClient.SERVICE_PATH + "/{transition}")
722     public byte[] updateItemWorkflowWithTransition(
723             @Context UriInfo uriInfo,
724             @PathParam("csid") String parentIdentifier,
725             @PathParam("itemcsid") String itemIdentifier,
726             @PathParam("transition") String transition) {
727         PoxPayloadOut result = null;
728         
729         try {
730             ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo);
731             result = updateItemWorkflowWithTransition(ctx, 
732                         parentIdentifier, itemIdentifier, transition, AuthorityServiceUtils.UPDATE_REV);
733         } catch (Exception e) {
734             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, parentIdentifier);
735         }
736         
737         return result.getBytes();
738     }
739     
740     /**
741      * Update an authority item's workflow state.
742      * @param existingContext
743      * @param csid
744      * @param itemcsid
745      * @param transition
746      * @return
747      * @throws DocumentReferenceException 
748      */
749     public PoxPayloadOut updateItemWorkflowWithTransition(ServiceContext existingContext,
750             String parentIdentifier,
751             String itemIdentifier,
752             String transition,
753             boolean updateRevNumber) throws DocumentReferenceException {
754         PoxPayloadOut result = null;
755         
756         try {
757                 //
758                 // We need CSIDs for both the parent authority and the authority item
759                 //
760             CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(existingContext, parentIdentifier, "updateItemWorkflowWithTransition(parent)", "UPDATE_ITEM", null);
761             String itemCsid = lookupItemCSID(existingContext, itemIdentifier, csidAndShortId.CSID, "updateAuthorityItem(item)", "UPDATE_ITEM");
762
763             //
764                 // Create an empty workflow_commons input part and set it into a new "workflow" sub-resource context
765                 //
766                 PoxPayloadIn input = new PoxPayloadIn(WorkflowClient.SERVICE_PAYLOAD_NAME, new WorkflowCommon(), 
767                                 WorkflowClient.SERVICE_COMMONPART_NAME);
768             MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(WorkflowClient.SERVICE_NAME, input);
769             if (existingContext != null && existingContext.getCurrentRepositorySession() != null) {
770                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());// If a repo session is already open, we need to use it and not create a new one
771             }
772             //
773             // Create a service context and document handler for the target resource -not the workflow resource itself.
774             //
775             ServiceContext<PoxPayloadIn, PoxPayloadOut> targetCtx = createServiceContext(getItemServiceName(), existingContext.getUriInfo());
776             AuthorityItemDocumentModelHandler targetDocHandler = (AuthorityItemDocumentModelHandler) this.createDocumentHandler(targetCtx);
777             targetDocHandler.setShouldUpdateRevNumber(updateRevNumber);
778             ctx.setProperty(WorkflowClient.TARGET_DOCHANDLER, targetDocHandler); //added as a context param for the workflow document handler -it will call the parent's dochandler "prepareForWorkflowTranstion" method
779             //
780             // When looking for the document, we need to use the parent/target resource's workspace name -not the "workflow" workspace name
781             //
782             String targetWorkspaceName = targetCtx.getRepositoryWorkspaceName();
783             ctx.setRespositoryWorkspaceName(targetWorkspaceName); //find the document in the parent's workspace
784             
785                 // Get the type of transition we're being asked to make and store it as a context parameter -used by the workflow document handler
786             TransitionDef transitionDef = getTransitionDef(targetCtx, transition);
787             if (transitionDef == null) {
788                 throw new DocumentException(String.format("The document with ID='%s' does not support the workflow transition '%s'.",
789                                 itemIdentifier, transition));
790             }
791             ctx.setProperty(WorkflowClient.TRANSITION_ID, transitionDef);
792             
793             WorkflowDocumentModelHandler handler = createWorkflowDocumentHandler(ctx);
794             getRepositoryClient(ctx).update(ctx, itemCsid, handler);
795             result = ctx.getOutput();
796         } catch (DocumentReferenceException de) {
797                 throw de;
798         } catch (Exception e) {
799             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, itemIdentifier);
800         }
801         
802         return result;
803     }
804     
805     private PoxPayloadOut getAuthorityItem(
806                 ServiceContext ctx,
807             String parentIdentifier,
808             String itemIdentifier) throws Exception {
809         PoxPayloadOut result = null;
810         
811         String parentcsid = lookupParentCSID(ctx, parentIdentifier, "getAuthorityItem(parent)", "GET_ITEM", null);
812         // We omit the parentShortId, only needed when doing a create...
813         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createItemDocumentHandler(ctx, parentcsid, null);
814
815         Specifier itemSpec = Specifier.getSpecifier(itemIdentifier, "getAuthorityItem(item)", "GET_ITEM");
816         if (itemSpec.form == SpecifierForm.CSID) {
817             // TODO should we assert that the item is in the passed vocab?
818             getRepositoryClient(ctx).get(ctx, itemSpec.value, handler);
819         } else {
820             String itemWhereClause =
821                         RefNameServiceUtils.buildWhereForAuthItemByName(authorityItemCommonSchemaName, itemSpec.value, parentcsid);
822             DocumentFilter myFilter = new NuxeoDocumentFilter(itemWhereClause, 0, 1); // start at page 0 and get 1 item
823             handler.setDocumentFilter(myFilter);
824             getRepositoryClient(ctx).get(ctx, handler);
825         }
826         
827         result = (PoxPayloadOut) ctx.getOutput();
828         if (result != null) {
829                 String inAuthority = XmlTools.getElementValue(result.getDOMDocument(), "//" + AuthorityItemJAXBSchema.IN_AUTHORITY);
830                 if (inAuthority.equalsIgnoreCase(parentcsid) == false) {
831                         throw new Exception(String.format("Looked up item = '%s' and found with inAuthority = '%s', but expected inAuthority = '%s'.",
832                                         itemSpec.value, inAuthority, parentcsid));
833                 }
834         }
835         
836         return result;
837     }
838
839     public PoxPayloadOut getAuthorityItemWithExistingContext(
840                 ServiceContext existingCtx,
841             String parentIdentifier,
842             String itemIdentifier) throws Exception {
843         PoxPayloadOut result = null;
844         
845         ServiceContext ctx = createServiceContext(getItemServiceName(), existingCtx.getResourceMap(), existingCtx.getUriInfo());
846         if (existingCtx.getCurrentRepositorySession() != null) {
847                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession()); // Reuse the current repo session if one exists
848                 ctx.setProperties(existingCtx.getProperties());
849         }
850         result = getAuthorityItem(ctx, parentIdentifier, itemIdentifier);
851         
852         return result;
853     }
854     
855     /**
856      * Gets the authority item.
857      * 
858      * @param parentspecifier either a CSID or one of the urn forms
859      * @param itemspecifier either a CSID or one of the urn forms
860      * 
861      * @return the authority item
862      */
863     @GET
864     @Path("{csid}/items/{itemcsid}")
865     public byte[] getAuthorityItem(
866             @Context Request request,
867             @Context UriInfo uriInfo,
868                 @Context ResourceMap resourceMap,            
869             @PathParam("csid") String parentIdentifier,
870             @PathParam("itemcsid") String itemIdentifier) {
871         PoxPayloadOut result = null;
872         try {
873             RemoteServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = 
874                         (RemoteServiceContext<PoxPayloadIn, PoxPayloadOut>) createServiceContext(getItemServiceName(), resourceMap, uriInfo);
875
876             JaxRsContext jaxRsContext = new JaxRsContext(request, uriInfo); // Needed for getting account permissions part of the resource
877             ctx.setJaxRsContext(jaxRsContext);
878             
879             result = getAuthorityItem(ctx, parentIdentifier, itemIdentifier);
880         } catch (DocumentNotFoundException dnf) {
881             throw bigReThrow(dnf, ServiceMessages.resourceNotFoundMsg(itemIdentifier));
882         } catch (Exception e) {
883             throw bigReThrow(e, ServiceMessages.GET_FAILED);
884         }
885                 
886         return result.getBytes();
887     }
888
889     /*
890      * Most of the authority child classes will/should use this implementation.  However, the Vocabulary service's item schema is
891      * different enough that it will have to override this method in it's resource class.
892      */
893     @Override
894         protected String getOrderByField(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
895                 String result = null;
896
897                 result = NuxeoUtils.getPrimaryElPathPropertyName(
898                                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(),
899                                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
900
901                 return result;
902         }
903         
904     @Override
905         protected String getPartialTermMatchField(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
906                 String result = null;
907                 
908                 result = NuxeoUtils.getMultiElPathPropertyName(
909                                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(),
910                                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
911
912                 return result;
913         }
914     
915     /**
916      * Gets the authorityItem list for the specified authority
917      * If partialPerm is specified, keywords will be ignored.
918      * 
919      * @param authorityIdentifier either a CSID or one of the urn forms
920      * @param partialTerm if non-null, matches partial terms
921      * @param keywords if non-null, matches terms in the keyword index for items
922      * @param ui passed to include additional parameters, like pagination controls
923      *
924      */
925     public AbstractCommonList getAuthorityItemList(ServiceContext existingContext,
926                 String authorityIdentifier,
927             UriInfo uriInfo) throws Exception {
928         AbstractCommonList result = null;
929         
930         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
931         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
932         if (existingContext != null && existingContext.getCurrentRepositorySession() != null) { // Merge some of the existing context properties with our new context
933                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());
934                 ctx.setProperties(existingContext.getProperties());
935         }
936             
937         String orderBy = queryParams.getFirst(IClientQueryParams.ORDER_BY_PARAM);
938         String termStatus = queryParams.getFirst(SEARCH_TYPE_TERMSTATUS);
939         String keywords = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_KW);
940         String advancedSearch = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_AS);
941         String partialTerm = queryParams.getFirst(IQueryManager.SEARCH_TYPE_PARTIALTERM);
942
943         // For the wildcard case, parentcsid is null, but docHandler will deal with this.
944         // We omit the parentShortId, only needed when doing a create...
945         String parentcsid = PARENT_WILDCARD.equals(authorityIdentifier) ? null :
946                         lookupParentCSID(ctx, authorityIdentifier, "getAuthorityItemList", "LIST", uriInfo);
947         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler =
948                 createItemDocumentHandler(ctx, parentcsid, null);
949         
950         DocumentFilter myFilter = handler.getDocumentFilter();
951         // If we are not wildcarding the parent, add a restriction
952         if (parentcsid != null) {
953             myFilter.appendWhereClause(authorityItemCommonSchemaName + ":"
954                     + AuthorityItemJAXBSchema.IN_AUTHORITY + "="
955                     + "'" + parentcsid + "'",
956                     IQueryManager.SEARCH_QUALIFIER_AND);
957         }
958
959         if (Tools.notBlank(termStatus)) {
960                 // Start with the qualified termStatus field
961                 String qualifiedTermStatusField = authorityItemCommonSchemaName + ":"
962                     + AuthorityItemJAXBSchema.TERM_STATUS;
963                 String[] filterTerms = termStatus.trim().split("\\|");
964                 String tsClause = QueryManager.createWhereClauseToFilterFromStringList(qualifiedTermStatusField, filterTerms, IQueryManager.FILTER_EXCLUDE);
965             myFilter.appendWhereClause(tsClause, IQueryManager.SEARCH_QUALIFIER_AND);
966         }
967
968         result = search(ctx, handler, uriInfo, orderBy, keywords, advancedSearch, partialTerm);            
969         
970         return result;
971     }
972     
973     /**
974      * Gets the authorityItem list for the specified authority
975      * If partialPerm is specified, keywords will be ignored.
976      * 
977      * @param authorityIdentifier either a CSID or one of the urn forms
978      * @param partialTerm if non-null, matches partial terms
979      * @param keywords if non-null, matches terms in the keyword index for items
980      * @param ui passed to include additional parameters, like pagination controls
981      * 
982      * @return the authorityItem list
983      */
984     @GET
985     @Path("{csid}/items")
986     @Produces("application/xml")
987     public AbstractCommonList getAuthorityItemList(@PathParam("csid") String authorityIdentifier,
988             @Context UriInfo uriInfo) {
989         AbstractCommonList result = null;
990         
991         try {
992             result = getAuthorityItemList(NULL_CONTEXT, authorityIdentifier, uriInfo);    
993         } catch (Exception e) {
994             throw bigReThrow(e, ServiceMessages.LIST_FAILED);
995         }
996         
997         return result;
998     }
999
1000     /**
1001      * @return the name of the property used to specify references for items in this type of
1002      * authority. For most authorities, it is ServiceBindingUtils.AUTH_REF_PROP ("authRef").
1003      * Some types (like Vocabulary) use a separate property.
1004      */
1005     protected String getRefPropName() {
1006         return ServiceBindingUtils.AUTH_REF_PROP;
1007     }
1008     
1009     /**
1010      * Gets the entities referencing this Authority item instance. The service type
1011      * can be passed as a query param "type", and must match a configured type
1012      * for the service bindings. If not set, the type defaults to
1013      * ServiceBindingUtils.SERVICE_TYPE_PROCEDURE.
1014      *
1015      * @param parentspecifier either a CSID or one of the urn forms
1016      * @param itemspecifier either a CSID or one of the urn forms
1017      * @param ui the ui
1018      * 
1019      * @return the info for the referencing objects
1020      */
1021     @GET
1022     @Path("{csid}/items/{itemcsid}/refObjs")
1023     @Produces("application/xml")
1024     public AuthorityRefDocList getReferencingObjects(
1025             @PathParam("csid") String parentSpecifier,
1026             @PathParam("itemcsid") String itemSpecifier,
1027             @Context UriTemplateRegistry uriTemplateRegistry,
1028             @Context UriInfo uriInfo) {
1029         AuthorityRefDocList authRefDocList = null;
1030         try {
1031             authRefDocList = getReferencingObjects(null, parentSpecifier, itemSpecifier, uriTemplateRegistry, uriInfo);
1032         } catch (Exception e) {
1033             throw bigReThrow(e, ServiceMessages.GET_FAILED);
1034         }
1035         
1036         if (authRefDocList == null) {
1037             Response response = Response.status(Response.Status.NOT_FOUND).entity(
1038                     "Get failed, the requested Item CSID:" + itemSpecifier + ": was not found.").type(
1039                     "text/plain").build();
1040             throw new CSWebApplicationException(response);
1041         }
1042         return authRefDocList;
1043     }
1044     
1045     public AuthorityRefDocList getReferencingObjects(
1046                 ServiceContext existingContext,
1047             String parentspecifier,
1048             String itemspecifier,
1049             UriTemplateRegistry uriTemplateRegistry,
1050             UriInfo uriInfo) throws Exception {
1051         AuthorityRefDocList authRefDocList = null;
1052  
1053         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1054         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1055         //
1056         // Merge parts of existing context with our new context
1057         //
1058         if (existingContext != null && existingContext.getCurrentRepositorySession() != null) {
1059                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());  // If one exists, use the existing repo session
1060                 ctx.setProperties(existingContext.getProperties());
1061         }
1062
1063         String parentcsid = lookupParentCSID(ctx, parentspecifier, "getReferencingObjects(parent)", "GET_ITEM_REF_OBJS", uriInfo);
1064         String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "getReferencingObjects(item)", "GET_ITEM_REF_OBJS");
1065
1066         List<String> serviceTypes = queryParams.remove(ServiceBindingUtils.SERVICE_TYPE_PROP);
1067         if (serviceTypes == null || serviceTypes.isEmpty()) {
1068                 serviceTypes = ServiceBindingUtils.getCommonServiceTypes(true); //CSPACE-5359: Should now include objects, procedures, and authorities
1069         }
1070             
1071         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, null);
1072         authRefDocList = handler.getReferencingObjects(ctx, uriTemplateRegistry, serviceTypes, getRefPropName(), itemcsid);
1073
1074         return authRefDocList;
1075     }
1076
1077     /**
1078      * Gets the authority terms used in the indicated Authority item.
1079      *
1080      * @param parentspecifier either a CSID or one of the urn forms
1081      * @param itemspecifier either a CSID or one of the urn forms
1082      * @param ui passed to include additional parameters, like pagination controls
1083      *
1084      * @return the authority refs for the Authority item.
1085      */
1086     @GET
1087     @Path("{csid}/items/{itemcsid}/authorityrefs")
1088     @Produces("application/xml")
1089     public AuthorityRefList getAuthorityItemAuthorityRefs(
1090             @PathParam("csid") String parentspecifier,
1091             @PathParam("itemcsid") String itemspecifier,
1092             @Context UriInfo uriInfo) {
1093         AuthorityRefList authRefList = null;
1094         
1095         try {
1096             // Note that we have to create the service context for the Items, not the main service
1097             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1098             MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1099             String parentcsid = lookupParentCSID(parentspecifier, "getAuthorityItemAuthRefs(parent)", "GET_ITEM_AUTH_REFS", uriInfo);
1100             // We omit the parentShortId, only needed when doing a create...
1101             DocumentModelHandler<?, AbstractCommonList> handler =
1102                     (DocumentModelHandler<?, AbstractCommonList>)createItemDocumentHandler(ctx, parentcsid, null /*no parent short ID*/);
1103
1104             String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "getAuthorityItemAuthRefs(item)", "GET_ITEM_AUTH_REFS");
1105
1106             List<RefNameServiceUtils.AuthRefConfigInfo> authRefsInfo = RefNameServiceUtils.getConfiguredAuthorityRefs(ctx);
1107             authRefList = handler.getAuthorityRefs(itemcsid, authRefsInfo);
1108         } catch (Exception e) {
1109             throw bigReThrow(e, ServiceMessages.GET_FAILED + " parentspecifier: " + parentspecifier + " itemspecifier:" + itemspecifier);
1110         }
1111         
1112         return authRefList;
1113     }
1114     
1115     /**
1116      * Synchronizes a local authority item with a share authority server (SAS) item.
1117      * @param ctx
1118      * @param parentIdentifier
1119      * @param itemIdentifier
1120      * @return
1121      * @throws Exception
1122      */
1123     private PoxPayloadOut synchronizeItem(
1124                 ServiceContext ctx,
1125             String parentIdentifier,
1126             String itemIdentifier,
1127             boolean syncHierarchicalRelationships) throws Exception {
1128         PoxPayloadOut result = null;
1129         AuthorityItemSpecifier specifier;
1130         boolean neededSync = false;
1131
1132         CsidAndShortIdentifier parent = lookupParentCSIDAndShortIdentifer(ctx, parentIdentifier, "syncAuthorityItem(parent)", "SYNC_ITEM", null);
1133         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parent.CSID, parent.shortIdentifier);
1134         handler.setIsProposed(AuthorityServiceUtils.NOT_PROPOSED); // In case it was formally locally proposed, clear the proposed flag
1135         handler.setIsSASItem(AuthorityServiceUtils.SAS_ITEM); // Since we're sync'ing, this is now a SAS controlled item
1136         handler.setShouldSyncHierarchicalRelationships(syncHierarchicalRelationships);
1137         // Create an authority item specifier
1138         Specifier parentSpecifier = Specifier.getSpecifier(parent.CSID, "getAuthority", "GET");
1139         Specifier itemSpecifier = Specifier.getSpecifier(itemIdentifier, "getAuthorityItem", "GET");
1140         specifier = new AuthorityItemSpecifier(parentSpecifier, itemSpecifier);
1141         //
1142         neededSync = getRepositoryClient(ctx).synchronize(ctx, specifier, handler);
1143         if (neededSync == true) {
1144                 result = (PoxPayloadOut) ctx.getOutput();
1145         }
1146         
1147         return result;
1148     }
1149
1150     /**
1151      * Using the parent and item ID, sync the local item with the SAS (shared authority server)
1152      * Used by the AuthorityItemDocumentModelHandler when synchronizing a list of remote authority items with a
1153      * local authority.  The parent context was created for the authority (parent) because the sync started there.
1154      * @param existingCtx
1155      * @param parentIdentifier
1156      * @param itemIdentifier
1157      * @return
1158      * @throws Exception
1159      */
1160     public PoxPayloadOut synchronizeItemWithExistingContext(
1161                 ServiceContext existingCtx,
1162             String parentIdentifier,
1163             String itemIdentifier,
1164             boolean syncHierarchicalRelationships
1165             ) throws Exception {
1166         PoxPayloadOut result = null;
1167         
1168         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(),
1169                         existingCtx.getResourceMap(),
1170                         existingCtx.getUriInfo());
1171         if (existingCtx.getCurrentRepositorySession() != null) {
1172                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession());
1173         }
1174         result = synchronizeItem(ctx, parentIdentifier, itemIdentifier, syncHierarchicalRelationships);
1175         
1176         return result;
1177     }
1178     
1179     /**
1180      * Synchronizes an authority item and with a Shared Authority Server (SAS) item.
1181      * 
1182      * @param specifier either CSIDs and/or one of the urn forms
1183      * 
1184      * @return the authority item if it was updated/synchronized with SAS item; otherwise empty
1185      */
1186     @POST
1187     @Path("{csid}/items/{itemcsid}/sync")
1188     public byte[] synchronizeItem(
1189                 @Context ResourceMap resourceMap,
1190             @Context UriInfo uriInfo,
1191             @PathParam("csid") String parentIdentifier,
1192             @PathParam("itemcsid") String itemIdentifier) {
1193         byte[] result;
1194         boolean neededSync = false;
1195         PoxPayloadOut payloadOut = null;
1196         
1197         try {
1198             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), null, resourceMap, uriInfo);
1199             payloadOut = this.synchronizeItem(ctx, parentIdentifier, itemIdentifier, true);
1200             if (payloadOut != null) {
1201                 neededSync = true;
1202             }
1203         } catch (Exception e) {
1204             throw bigReThrow(e, ServiceMessages.SYNC_FAILED, itemIdentifier);
1205         }
1206
1207         //
1208         // If a sync was needed and was successful, return a copy of the updated resource.  Acts like an UPDATE.
1209         //
1210         if (neededSync == true) {
1211                 result = payloadOut.getBytes();
1212         } else {
1213                 result = String.format("Authority item resource '%s' was already in sync with shared authority server.",
1214                                 itemIdentifier).getBytes();
1215                 Response response = Response.status(Response.Status.NOT_MODIFIED).entity(result).type("text/plain").build();
1216             throw new CSWebApplicationException(response);
1217         }
1218         
1219         return result;
1220     }
1221     
1222     /**
1223      * Update authorityItem.
1224      * 
1225      * @param parentspecifier either a CSID or one of the urn forms
1226      * @param itemspecifier either a CSID or one of the urn forms
1227      *
1228      * @return the multipart output
1229      */
1230     @PUT
1231     @Path("{csid}/items/{itemcsid}")
1232     public byte[] updateAuthorityItem(
1233                 @Context ResourceMap resourceMap, 
1234             @Context UriInfo uriInfo,
1235             @PathParam("csid") String parentSpecifier,
1236             @PathParam("itemcsid") String itemSpecifier,
1237             String xmlPayload) {
1238         PoxPayloadOut result = null;
1239         
1240         try {
1241             PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload);
1242             result = updateAuthorityItem(null, resourceMap, uriInfo, parentSpecifier, itemSpecifier, theUpdate,
1243                         AuthorityServiceUtils.UPDATE_REV,                       // passing TRUE so rev num increases, passing
1244                         AuthorityServiceUtils.NO_CHANGE,        // don't change the state of the "proposed" field -we could be performing a sync or just a plain update
1245                         AuthorityServiceUtils.NO_CHANGE);       // don't change the state of the "sas" field -we could be performing a sync or just a plain update
1246         } catch (Exception e) {
1247             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED);
1248         }
1249         
1250         return result.getBytes();
1251     }
1252     
1253     public PoxPayloadOut updateAuthorityItem(
1254                 ServiceContext itemServiceCtx, // Ok to be null.  Will be null on PUT calls, but not on sync calls
1255                 ResourceMap resourceMap, 
1256             UriInfo uriInfo,
1257             String parentspecifier,
1258             String itemspecifier,
1259             PoxPayloadIn theUpdate,
1260             boolean shouldUpdateRevNumber,
1261             Boolean isProposed,
1262             Boolean isSASItem
1263             ) throws Exception {
1264         PoxPayloadOut result = null;
1265         
1266         CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(itemServiceCtx, parentspecifier, "updateAuthorityItem(parent)", "UPDATE_ITEM", null);
1267         String parentcsid = csidAndShortId.CSID;
1268         String parentShortId = csidAndShortId.shortIdentifier;
1269         //
1270         // If the itemServiceCtx context is not null, use it.  Otherwise, create a new context
1271         //
1272         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = itemServiceCtx;
1273         if (ctx == null) {
1274                 ctx = createServiceContext(getItemServiceName(), theUpdate, resourceMap, uriInfo);
1275         } else {
1276                 ctx.setInput(theUpdate); // the update payload
1277         }
1278         
1279         String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "updateAuthorityItem(item)", "UPDATE_ITEM"); //use itemServiceCtx if it is not null
1280
1281         // We omit the parentShortId, only needed when doing a create...
1282         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, parentShortId);
1283         handler.setShouldUpdateRevNumber(shouldUpdateRevNumber);
1284         if (isProposed != null) {
1285                 handler.setIsProposed(isProposed);
1286         }
1287         if (isSASItem != null) {
1288                 handler.setIsSASItem(isSASItem);
1289         }
1290         getRepositoryClient(ctx).update(ctx, itemcsid, handler);
1291         result = ctx.getOutput();
1292
1293         return result;
1294     }
1295
1296     /**
1297      * Delete authorityItem.
1298      * 
1299      * @param parentIdentifier the parentcsid
1300      * @param itemIdentifier the itemcsid
1301      * 
1302      * @return the response
1303      */
1304     @DELETE
1305     @Path("{csid}/items/{itemcsid}")
1306     public Response deleteAuthorityItem(
1307             @Context UriInfo uriInfo,
1308             @PathParam("csid") String parentIdentifier,
1309             @PathParam("itemcsid") String itemIdentifier) {
1310         Response result = null;
1311
1312         ensureCSID(parentIdentifier, ServiceMessages.DELETE_FAILED, "AuthorityItem.parentcsid");
1313         ensureCSID(itemIdentifier, ServiceMessages.DELETE_FAILED, "AuthorityItem.itemcsid");
1314         if (logger.isDebugEnabled()) {
1315             logger.debug("deleteAuthorityItem with parentcsid=" + parentIdentifier + " and itemcsid=" + itemIdentifier);
1316         }
1317         
1318         try {
1319             ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo);
1320             deleteAuthorityItem(ctx, parentIdentifier, itemIdentifier, AuthorityServiceUtils.UPDATE_REV);
1321             result = Response.status(HttpResponseCodes.SC_OK).build();
1322         } catch (Exception e) {
1323             throw bigReThrow(e, ServiceMessages.DELETE_FAILED + "  itemcsid: " + itemIdentifier + " parentcsid:" + parentIdentifier);
1324         }
1325
1326         return result;
1327     }
1328
1329     /**
1330      * 
1331      * @param existingCtx
1332      * @param parentIdentifier
1333      * @param itemIdentifier
1334      * @throws Exception
1335      */
1336     @SuppressWarnings("rawtypes")
1337         public void deleteAuthorityItem(ServiceContext existingCtx,
1338             String parentIdentifier,
1339             String itemIdentifier,
1340             boolean shouldUpdateRevNumber
1341             ) throws Exception {
1342         Response result = null;
1343         
1344         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), existingCtx.getUriInfo());
1345         if (existingCtx != null && existingCtx.getCurrentRepositorySession() != null) {
1346                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession());
1347                 ctx.setProperties(existingCtx.getProperties());
1348         }
1349         
1350         String parentcsid = null;
1351         try {
1352                 parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null);
1353         } catch (DocumentNotFoundException de) {
1354                 logger.warn(String.format("Could not find parent with ID='%s' when trying to delete item ID='%s'",
1355                                 parentIdentifier, itemIdentifier));
1356         }
1357         String itemCsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null
1358         
1359         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
1360                 ctx.setProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY, shouldUpdateRevNumber);  // Sometimes we can only soft-delete, so if it is during a sync we dont' update the revision number
1361
1362         getRepositoryClient(ctx).delete(ctx, itemCsid, handler);
1363     }
1364
1365     @GET
1366     @Path("{csid}/items/{itemcsid}/" + hierarchy)
1367     @Produces("application/xml")
1368     public String getHierarchy(
1369                 @PathParam("csid") String parentIdentifier,
1370             @PathParam("itemcsid") String itemIdentifier,
1371             @Context UriInfo uriInfo) throws Exception {
1372         String result = null;
1373
1374         try {
1375                 //
1376             // All items in dive can look at their child uri's to get uri.  So we calculate the very first one.  We could also do a GET and look at the common part uri field, but why...?
1377                 //
1378             String calledUri = uriInfo.getPath();
1379             String uri = "/" + calledUri.substring(0, (calledUri.length() - ("/" + hierarchy).length()));
1380             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1381             
1382             String parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null);
1383             String itemcsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null
1384             
1385             String direction = uriInfo.getQueryParameters().getFirst(Hierarchy.directionQP);
1386             if (Tools.notBlank(direction) && Hierarchy.direction_parents.equals(direction)) {
1387                 result = Hierarchy.surface(ctx, itemcsid, uri);
1388             } else {
1389                 result = Hierarchy.dive(ctx, itemcsid, uri);
1390             }            
1391         } catch (Exception e) {
1392             throw bigReThrow(e, "Error showing hierarchy for authority item: ", itemIdentifier);
1393         }
1394         
1395         return result;
1396     }
1397     
1398     /**
1399      * 
1400      * @param tenantId
1401      * @return
1402      */
1403     protected String getItemDocType(String tenantId) {
1404         return getDocType(tenantId, getItemServiceName());
1405     }
1406         
1407     /**
1408      * Returns a UriRegistry entry: a map of tenant-qualified URI templates
1409      * for the current resource, for all tenants
1410      * 
1411      * @return a map of URI templates for the current resource, for all tenants
1412      */
1413     @Override
1414     public Map<UriTemplateRegistryKey,StoredValuesUriTemplate> getUriRegistryEntries() {
1415         Map<UriTemplateRegistryKey,StoredValuesUriTemplate> uriRegistryEntriesMap =
1416                 super.getUriRegistryEntries();
1417         List<String> tenantIds = getTenantBindingsReader().getTenantIds();
1418         for (String tenantId : tenantIds) {
1419                 uriRegistryEntriesMap.putAll(getUriRegistryEntries(tenantId, getItemDocType(tenantId), UriTemplateFactory.ITEM));
1420         }
1421         return uriRegistryEntriesMap;
1422     }  
1423 }