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