]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
81e0bd860076ee2e0b0d5c4f225b78523d363efe
[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 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     @GET
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         try {
371             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(ui);
372             /*
373              * Make sure this authority service supports synchronization
374              */
375             if (supportsReplicating(ctx.getTenantId(), ctx.getServiceName()) == false) {
376                 throw new DocumentException(Response.Status.FORBIDDEN.getStatusCode());
377             }
378             AuthorityDocumentModelHandler handler = (AuthorityDocumentModelHandler)createDocumentHandler(ctx);
379             specifier = Specifier.getSpecifier(identifier, "getAuthority", "GET");
380             handler.setShouldUpdateRevNumber(AuthorityServiceUtils.DONT_UPDATE_REV); // Never update rev number on sync calls
381             neededSync = getRepositoryClient(ctx).synchronize(ctx, specifier, handler);
382             payloadOut = ctx.getOutput();
383         } catch (Exception e) {
384             throw bigReThrow(e, ServiceMessages.SYNC_FAILED, identifier);
385         }
386
387         //
388         // If a sync was needed and was successful, return a copy of the updated resource.  Acts like an UPDATE.
389         //
390         if (neededSync == true) {
391                 result = payloadOut.getBytes();
392         } else {
393                 result = String.format("Authority resource '%s' was already in sync with shared authority server.",
394                                 specifier.value).getBytes();
395                 Response response = Response.status(Response.Status.NOT_MODIFIED).entity(result).type("text/plain").build();
396             throw new CSWebApplicationException(response);
397         }
398                 return result;
399     }
400         
401     /**
402      * Gets the authority.
403      * 
404      * @param specifier either a CSID or one of the urn forms
405      * 
406      * @return the authority
407      */
408     @GET
409     @Path("{csid}")
410     @Override
411     public byte[] get(
412             @Context Request request,
413             @Context UriInfo ui,
414             @PathParam("csid") String specifier) {
415         PoxPayloadOut result = null;
416         try {
417             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(ui);
418             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
419
420             Specifier spec = Specifier.getSpecifier(specifier, "getAuthority", "GET");
421             if (spec.form == SpecifierForm.CSID) {
422                 if (logger.isDebugEnabled()) {
423                     logger.debug("getAuthority with csid=" + spec.value);
424                 }
425                 getRepositoryClient(ctx).get(ctx, spec.value, handler);
426             } else {
427                 String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value);
428                 DocumentFilter myFilter = new NuxeoDocumentFilter(whereClause, 0, 1);
429                 handler.setDocumentFilter(myFilter);
430                 getRepositoryClient(ctx).get(ctx, handler);
431             }
432             result = ctx.getOutput();
433
434         } catch (Exception e) {
435             throw bigReThrow(e, ServiceMessages.GET_FAILED, specifier);
436         }
437
438         if (result == null) {
439             Response response = Response.status(Response.Status.NOT_FOUND).entity(
440                     "Get failed, the requested Authority specifier:" + specifier + ": was not found.").type(
441                     "text/plain").build();
442             throw new CSWebApplicationException(response);
443         }
444
445         return result.getBytes();
446     }
447
448     /**
449      * Finds and populates the authority list.
450      * 
451      * @param ui the ui
452      * 
453      * @return the authority list
454      */
455     @GET
456     @Produces("application/xml")
457     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.
458         AbstractCommonList result = null;
459         
460         try {
461             MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
462             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(uriInfo);
463             
464             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
465             DocumentFilter myFilter = handler.getDocumentFilter();
466             // Need to make the default sort order for authority items
467             // be on the displayName field
468             String sortBy = queryParams.getFirst(IClientQueryParams.ORDER_BY_PARAM);
469             if (sortBy == null || sortBy.isEmpty()) {
470                 String qualifiedDisplayNameField = authorityCommonSchemaName + ":"
471                         + AuthorityItemJAXBSchema.DISPLAY_NAME;
472                 myFilter.setOrderByClause(qualifiedDisplayNameField);
473             }
474             String nameQ = queryParams.getFirst("refName");
475             if (nameQ != null) {
476                 myFilter.setWhereClause(authorityCommonSchemaName + ":refName='" + nameQ + "'");
477             }
478             getRepositoryClient(ctx).getFiltered(ctx, handler);
479             result = handler.getCommonPartList();
480         } catch (Exception e) {
481             throw bigReThrow(e, ServiceMessages.GET_FAILED);
482         }
483         
484         return result;
485     }
486     
487     /**
488      * Overriding this methods to see if we should update the revision number during the update.  We don't
489      * want to update the rev number of synchronization operations.
490      */
491     @Override
492     protected PoxPayloadOut update(String csid,
493             PoxPayloadIn theUpdate, // not used in this method, but could be used by an overriding method
494             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
495             throws Exception {
496         AuthorityDocumentModelHandler handler = (AuthorityDocumentModelHandler) createDocumentHandler(ctx);
497         Boolean shouldUpdateRev = (Boolean) ctx.getProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY);
498         if (shouldUpdateRev != null) {
499                 handler.setShouldUpdateRevNumber(shouldUpdateRev);
500         }
501         getRepositoryClient(ctx).update(ctx, csid, handler);
502         return ctx.getOutput();
503     }
504     
505     /**
506      * Update authority.
507      *
508      * @param specifier the csid or id
509      *
510      * @return the multipart output
511      */
512     @PUT
513     @Path("{csid}")
514     public byte[] updateAuthority(
515             @PathParam("csid") String specifier,
516             String xmlPayload) {
517         PoxPayloadOut result = null;
518         try {
519             PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload);
520             Specifier spec = Specifier.getSpecifier(specifier, "updateAuthority", "UPDATE");
521             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(theUpdate);
522             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
523             String csid;
524             if (spec.form == SpecifierForm.CSID) {
525                 csid = spec.value;
526             } else {
527                 String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value);
528                 csid = getRepositoryClient(ctx).findDocCSID(null, ctx, whereClause);
529             }
530             getRepositoryClient(ctx).update(ctx, csid, handler);
531             result = ctx.getOutput();
532         } catch (Exception e) {
533             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED);
534         }
535         return result.getBytes();
536     }
537
538     /**
539      * Delete authority.
540      * 
541      * @param csid the csid
542      * 
543      * @return the response
544      */
545     @Deprecated
546 //    @DELETE
547     @Path("{csid}")
548     public Response old_deleteAuthority(@PathParam("csid") String csid) {
549         if (logger.isDebugEnabled()) {
550             logger.debug("deleteAuthority with csid=" + csid);
551         }
552         try {
553             ensureCSID(csid, ServiceMessages.DELETE_FAILED, "Authority.csid");
554             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext();
555             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
556             getRepositoryClient(ctx).delete(ctx, csid, handler);
557             return Response.status(HttpResponseCodes.SC_OK).build();
558         } catch (Exception e) {
559             throw bigReThrow(e, ServiceMessages.DELETE_FAILED, csid);
560         }
561     }
562     
563     /**
564      * Delete authority
565      * 
566      * @param csid the csid or a URN specifier form -e.g., urn:cspace:name(OurMuseumPersonAuthority)
567      * 
568      * @return the response
569      */
570     @DELETE
571     @Path("{csid}")
572     public Response deleteAuthority(
573             @Context Request request,
574             @Context UriInfo ui,
575             @PathParam("csid") String specifier) {
576         if (logger.isDebugEnabled()) {
577             logger.debug("deleteAuthority with specifier=" + specifier);
578         }
579         
580         try {
581             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(ui);
582             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
583
584             Specifier spec = Specifier.getSpecifier(specifier, "getAuthority", "GET");
585             if (spec.form == SpecifierForm.CSID) {
586                 if (logger.isDebugEnabled()) {
587                     logger.debug("deleteAuthority with csid=" + spec.value);
588                 }
589                 ensureCSID(spec.value, ServiceMessages.DELETE_FAILED, "Authority.csid");
590                 getRepositoryClient(ctx).delete(ctx, spec.value, handler);
591             } else {
592                 if (logger.isDebugEnabled()) {
593                     logger.debug("deleteAuthority with specifier=" + spec.value);
594                 }               
595                 String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value);
596                 getRepositoryClient(ctx).deleteWithWhereClause(ctx, whereClause, handler);
597             }
598             
599             return Response.status(HttpResponseCodes.SC_OK).build();
600         } catch (Exception e) {
601             throw bigReThrow(e, ServiceMessages.DELETE_FAILED, specifier);
602         }
603     }
604     
605     /**
606      * 
607      * @param ctx
608      * @param parentspecifier           - ID of the container. Can be URN or CSID form
609      * @param shouldUpdateRevNumber - Indicates if the revision number should be updated on create -won't do this when synching with SAS
610      * @param isProposed                                - In a shared authority context, indicates if this item just a proposed item and not yet part of the SAS authority
611      * @return
612      * @throws Exception
613      */
614     protected Response createAuthorityItem(ServiceContext ctx, String parentIdentifier,
615                 boolean shouldUpdateRevNumber,
616                 boolean isProposed,
617                 boolean isSasItem) throws Exception {
618         Response result = null;
619         
620         // Note: must have the parentShortId, to do the create.
621         CsidAndShortIdentifier parent = lookupParentCSIDAndShortIdentifer(ctx, parentIdentifier, "createAuthorityItem", "CREATE_ITEM", null);
622         AuthorityItemDocumentModelHandler handler = 
623                 (AuthorityItemDocumentModelHandler) createItemDocumentHandler(ctx, parent.CSID, parent.shortIdentifier);
624         handler.setShouldUpdateRevNumber(shouldUpdateRevNumber);
625         handler.setIsProposed(isProposed);
626         handler.setIsSASItem(isSasItem);
627         // Make the client call
628         String itemcsid = getRepositoryClient(ctx).create(ctx, handler);
629         
630         // Build the JAX-RS response
631         UriBuilder path = UriBuilder.fromResource(resourceClass);
632         path.path(parent.CSID + "/items/" + itemcsid);
633         result = Response.created(path.build()).build();
634
635         return result;
636     }
637
638     /**
639      * Called with an existing context.
640      * @param parentCtx
641      * @param parentIdentifier
642      * @param input
643      * @return
644      * @throws Exception
645      */
646     public Response createAuthorityItemWithParentContext(ServiceContext parentCtx,
647                 String parentIdentifier,
648                 PoxPayloadIn input,
649                 boolean shouldUpdateRevNumber,
650                 boolean isProposed,
651                 boolean isSASItem) throws Exception {
652         Response result = null;
653         
654         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), input,
655                         parentCtx.getResourceMap(), parentCtx.getUriInfo());
656         if (parentCtx.getCurrentRepositorySession() != null) {
657                 ctx.setCurrentRepositorySession(parentCtx.getCurrentRepositorySession());
658         }
659         result = this.createAuthorityItem(ctx, parentIdentifier, shouldUpdateRevNumber, isProposed, isSASItem);
660
661         return result;
662     }
663         
664     /*************************************************************************
665      * Create an AuthorityItem - this is a sub-resource of Authority
666      * @param specifier either a CSID or one of the urn forms
667      * @return Authority item response
668      *************************************************************************/
669     @POST
670     @Path("{csid}/items")
671     public Response createAuthorityItem(
672                 @Context ResourceMap resourceMap,
673                 @Context UriInfo uriInfo, 
674                 @PathParam("csid") String parentIdentifier, // Either a CSID or a URN form -e.g., a8ad38ec-1d7d-4bf2-bd31 or urn:cspace:name(bugsbunny)
675                 String xmlPayload) {
676         Response result = null;
677         
678         try {
679             PoxPayloadIn input = new PoxPayloadIn(xmlPayload);
680             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), input, resourceMap, uriInfo);
681             result = this.createAuthorityItem(ctx, parentIdentifier, AuthorityServiceUtils.UPDATE_REV,
682                         AuthorityServiceUtils.PROPOSED, AuthorityServiceUtils.NOT_SAS_ITEM);
683         } catch (Exception e) {
684             throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
685         }
686         
687         return result;
688     }
689
690     @GET
691     @Path("{csid}/items/{itemcsid}" + WorkflowClient.SERVICE_PATH)
692     public byte[] getItemWorkflow(
693             @PathParam("csid") String csid,
694             @PathParam("itemcsid") String itemcsid) {
695         PoxPayloadOut result = null;
696
697         try {
698             ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx = createServiceContext(getItemServiceName());
699             String parentWorkspaceName = parentCtx.getRepositoryWorkspaceName();
700
701             MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(WorkflowClient.SERVICE_NAME);
702             WorkflowDocumentModelHandler handler = createWorkflowDocumentHandler(ctx);
703             ctx.setRespositoryWorkspaceName(parentWorkspaceName); //find the document in the parent's workspace
704             getRepositoryClient(ctx).get(ctx, itemcsid, handler);
705             result = ctx.getOutput();
706         } catch (Exception e) {
707             throw bigReThrow(e, ServiceMessages.READ_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, csid);
708         }
709         return result.getBytes();
710     }
711
712     //FIXME: This method is almost identical to the method org.collectionspace.services.common.updateWorkflowWithTransition() so
713     // they should be consolidated -be DRY (D)on't (R)epeat (Y)ourself.
714     @PUT
715     @Path("{csid}/items/{itemcsid}" + WorkflowClient.SERVICE_PATH + "/{transition}")
716     public byte[] updateItemWorkflowWithTransition(
717             @Context UriInfo uriInfo,
718             @PathParam("csid") String parentIdentifier,
719             @PathParam("itemcsid") String itemIdentifier,
720             @PathParam("transition") String transition) {
721         PoxPayloadOut result = null;
722         
723         try {
724             ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo);
725             result = updateItemWorkflowWithTransition(ctx, 
726                         parentIdentifier, itemIdentifier, transition, AuthorityServiceUtils.UPDATE_REV);
727         } catch (Exception e) {
728             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, parentIdentifier);
729         }
730         
731         return result.getBytes();
732     }
733     
734     /**
735      * Update an authority item's workflow state.
736      * @param existingContext
737      * @param csid
738      * @param itemcsid
739      * @param transition
740      * @return
741      * @throws DocumentReferenceException 
742      */
743     public PoxPayloadOut updateItemWorkflowWithTransition(ServiceContext existingContext,
744             String parentIdentifier,
745             String itemIdentifier,
746             String transition,
747             boolean updateRevNumber) throws DocumentReferenceException {
748         PoxPayloadOut result = null;
749         
750         try {
751                 //
752                 // We need CSIDs for both the parent authority and the authority item
753                 //
754             CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(existingContext, parentIdentifier, "updateItemWorkflowWithTransition(parent)", "UPDATE_ITEM", null);
755             String itemCsid = lookupItemCSID(existingContext, itemIdentifier, csidAndShortId.CSID, "updateAuthorityItem(item)", "UPDATE_ITEM");
756
757             //
758                 // Create an empty workflow_commons input part and set it into a new "workflow" sub-resource context
759                 //
760                 PoxPayloadIn input = new PoxPayloadIn(WorkflowClient.SERVICE_PAYLOAD_NAME, new WorkflowCommon(), 
761                                 WorkflowClient.SERVICE_COMMONPART_NAME);
762             MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(WorkflowClient.SERVICE_NAME, input);
763             if (existingContext != null && existingContext.getCurrentRepositorySession() != null) {
764                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());// If a repo session is already open, we need to use it and not create a new one
765             }
766             //
767             // Create a service context and document handler for the target resource -not the workflow resource itself.
768             //
769             ServiceContext<PoxPayloadIn, PoxPayloadOut> targetCtx = createServiceContext(getItemServiceName(), existingContext.getUriInfo());
770             AuthorityItemDocumentModelHandler targetDocHandler = (AuthorityItemDocumentModelHandler) this.createDocumentHandler(targetCtx);
771             targetDocHandler.setShouldUpdateRevNumber(updateRevNumber);
772             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
773             //
774             // When looking for the document, we need to use the parent/target resource's workspace name -not the "workflow" workspace name
775             //
776             String targetWorkspaceName = targetCtx.getRepositoryWorkspaceName();
777             ctx.setRespositoryWorkspaceName(targetWorkspaceName); //find the document in the parent's workspace
778             
779                 // Get the type of transition we're being asked to make and store it as a context parameter -used by the workflow document handler
780             TransitionDef transitionDef = getTransitionDef(targetCtx, transition);
781             if (transitionDef == null) {
782                 throw new DocumentException(String.format("The document with ID='%s' does not support the workflow transition '%s'.",
783                                 itemIdentifier, transition));
784             }
785             ctx.setProperty(WorkflowClient.TRANSITION_ID, transitionDef);
786             
787             WorkflowDocumentModelHandler handler = createWorkflowDocumentHandler(ctx);
788             getRepositoryClient(ctx).update(ctx, itemCsid, handler);
789             result = ctx.getOutput();
790         } catch (DocumentReferenceException de) {
791                 throw de;
792         } catch (Exception e) {
793             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, itemIdentifier);
794         }
795         
796         return result;
797     }
798     
799     private PoxPayloadOut getAuthorityItem(
800                 ServiceContext ctx,
801             String parentIdentifier,
802             String itemIdentifier) throws Exception {
803         PoxPayloadOut result = null;
804         
805         String parentcsid = lookupParentCSID(ctx, parentIdentifier, "getAuthorityItem(parent)", "GET_ITEM", null);
806         // We omit the parentShortId, only needed when doing a create...
807         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createItemDocumentHandler(ctx, parentcsid, null);
808
809         Specifier itemSpec = Specifier.getSpecifier(itemIdentifier, "getAuthorityItem(item)", "GET_ITEM");
810         if (itemSpec.form == SpecifierForm.CSID) {
811             // TODO should we assert that the item is in the passed vocab?
812             getRepositoryClient(ctx).get(ctx, itemSpec.value, handler);
813         } else {
814             String itemWhereClause =
815                         RefNameServiceUtils.buildWhereForAuthItemByName(authorityItemCommonSchemaName, itemSpec.value, parentcsid);
816             DocumentFilter myFilter = new NuxeoDocumentFilter(itemWhereClause, 0, 1); // start at page 0 and get 1 item
817             handler.setDocumentFilter(myFilter);
818             getRepositoryClient(ctx).get(ctx, handler);
819         }
820         
821         result = (PoxPayloadOut) ctx.getOutput();
822         if (result != null) {
823                 String inAuthority = XmlTools.getElementValue(result.getDOMDocument(), "//" + AuthorityItemJAXBSchema.IN_AUTHORITY);
824                 if (inAuthority.equalsIgnoreCase(parentcsid) == false) {
825                         throw new Exception(String.format("Looked up item = '%s' and found with inAuthority = '%s', but expected inAuthority = '%s'.",
826                                         itemSpec.value, inAuthority, parentcsid));
827                 }
828         }
829         
830         return result;
831     }
832
833     public PoxPayloadOut getAuthorityItemWithExistingContext(
834                 ServiceContext existingCtx,
835             String parentIdentifier,
836             String itemIdentifier) throws Exception {
837         PoxPayloadOut result = null;
838         
839         ServiceContext ctx = createServiceContext(getItemServiceName(), existingCtx.getResourceMap(), existingCtx.getUriInfo());
840         if (existingCtx.getCurrentRepositorySession() != null) {
841                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession()); // Reuse the current repo session if one exists
842                 ctx.setProperties(existingCtx.getProperties());
843         }
844         result = getAuthorityItem(ctx, parentIdentifier, itemIdentifier);
845         
846         return result;
847     }
848     
849     /**
850      * Gets the authority item.
851      * 
852      * @param parentspecifier either a CSID or one of the urn forms
853      * @param itemspecifier either a CSID or one of the urn forms
854      * 
855      * @return the authority item
856      */
857     @GET
858     @Path("{csid}/items/{itemcsid}")
859     public byte[] getAuthorityItem(
860             @Context Request request,
861             @Context UriInfo uriInfo,
862                 @Context ResourceMap resourceMap,            
863             @PathParam("csid") String parentIdentifier,
864             @PathParam("itemcsid") String itemIdentifier) {
865         PoxPayloadOut result = null;
866         try {
867             RemoteServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = 
868                         (RemoteServiceContext<PoxPayloadIn, PoxPayloadOut>) createServiceContext(getItemServiceName(), resourceMap, uriInfo);
869
870             JaxRsContext jaxRsContext = new JaxRsContext(request, uriInfo); // Needed for getting account permissions part of the resource
871             ctx.setJaxRsContext(jaxRsContext);
872             
873             result = getAuthorityItem(ctx, parentIdentifier, itemIdentifier);
874         } catch (DocumentNotFoundException dnf) {
875             throw bigReThrow(dnf, ServiceMessages.resourceNotFoundMsg(itemIdentifier));
876         } catch (Exception e) {
877             throw bigReThrow(e, ServiceMessages.GET_FAILED);
878         }
879                 
880         return result.getBytes();
881     }
882
883     /*
884      * Most of the authority child classes will/should use this implementation.  However, the Vocabulary service's item schema is
885      * different enough that it will have to override this method in it's resource class.
886      */
887     @Override
888         protected String getOrderByField(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
889                 String result = null;
890
891                 result = NuxeoUtils.getPrimaryElPathPropertyName(
892                                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(),
893                                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
894
895                 return result;
896         }
897         
898     @Override
899         protected String getPartialTermMatchField(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
900                 String result = null;
901                 
902                 result = NuxeoUtils.getMultiElPathPropertyName(
903                                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(),
904                                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
905
906                 return result;
907         }
908     
909     /**
910      * Gets the authorityItem list for the specified authority
911      * If partialPerm is specified, keywords will be ignored.
912      * 
913      * @param authorityIdentifier either a CSID or one of the urn forms
914      * @param partialTerm if non-null, matches partial terms
915      * @param keywords if non-null, matches terms in the keyword index for items
916      * @param ui passed to include additional parameters, like pagination controls
917      *
918      */
919     public AbstractCommonList getAuthorityItemList(ServiceContext existingContext,
920                 String authorityIdentifier,
921             UriInfo uriInfo) throws Exception {
922         AbstractCommonList result = null;
923         
924         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
925         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
926         if (existingContext != null && existingContext.getCurrentRepositorySession() != null) { // Merge some of the existing context properties with our new context
927                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());
928                 ctx.setProperties(existingContext.getProperties());
929         }
930             
931         String orderBy = queryParams.getFirst(IClientQueryParams.ORDER_BY_PARAM);
932         String termStatus = queryParams.getFirst(SEARCH_TYPE_TERMSTATUS);
933         String keywords = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_KW);
934         String advancedSearch = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_AS);
935         String partialTerm = queryParams.getFirst(IQueryManager.SEARCH_TYPE_PARTIALTERM);
936
937         // For the wildcard case, parentcsid is null, but docHandler will deal with this.
938         // We omit the parentShortId, only needed when doing a create...
939         String parentcsid = PARENT_WILDCARD.equals(authorityIdentifier) ? null :
940                         lookupParentCSID(ctx, authorityIdentifier, "getAuthorityItemList", "LIST", uriInfo);
941         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler =
942                 createItemDocumentHandler(ctx, parentcsid, null);
943         
944         DocumentFilter myFilter = handler.getDocumentFilter();
945         // If we are not wildcarding the parent, add a restriction
946         if (parentcsid != null) {
947             myFilter.appendWhereClause(authorityItemCommonSchemaName + ":"
948                     + AuthorityItemJAXBSchema.IN_AUTHORITY + "="
949                     + "'" + parentcsid + "'",
950                     IQueryManager.SEARCH_QUALIFIER_AND);
951         }
952
953         if (Tools.notBlank(termStatus)) {
954                 // Start with the qualified termStatus field
955                 String qualifiedTermStatusField = authorityItemCommonSchemaName + ":"
956                     + AuthorityItemJAXBSchema.TERM_STATUS;
957                 String[] filterTerms = termStatus.trim().split("\\|");
958                 String tsClause = QueryManager.createWhereClauseToFilterFromStringList(qualifiedTermStatusField, filterTerms, IQueryManager.FILTER_EXCLUDE);
959             myFilter.appendWhereClause(tsClause, IQueryManager.SEARCH_QUALIFIER_AND);
960         }
961
962         result = search(ctx, handler, uriInfo, orderBy, keywords, advancedSearch, partialTerm);            
963         
964         return result;
965     }
966     
967     /**
968      * Gets the authorityItem list for the specified authority
969      * If partialPerm is specified, keywords will be ignored.
970      * 
971      * @param authorityIdentifier either a CSID or one of the urn forms
972      * @param partialTerm if non-null, matches partial terms
973      * @param keywords if non-null, matches terms in the keyword index for items
974      * @param ui passed to include additional parameters, like pagination controls
975      * 
976      * @return the authorityItem list
977      */
978     @GET
979     @Path("{csid}/items")
980     @Produces("application/xml")
981     public AbstractCommonList getAuthorityItemList(@PathParam("csid") String authorityIdentifier,
982             @Context UriInfo uriInfo) {
983         AbstractCommonList result = null;
984         
985         try {
986             result = getAuthorityItemList(NULL_CONTEXT, authorityIdentifier, uriInfo);    
987         } catch (Exception e) {
988             throw bigReThrow(e, ServiceMessages.LIST_FAILED);
989         }
990         
991         return result;
992     }
993
994     /**
995      * @return the name of the property used to specify references for items in this type of
996      * authority. For most authorities, it is ServiceBindingUtils.AUTH_REF_PROP ("authRef").
997      * Some types (like Vocabulary) use a separate property.
998      */
999     protected String getRefPropName() {
1000         return ServiceBindingUtils.AUTH_REF_PROP;
1001     }
1002     
1003     /**
1004      * Gets the entities referencing this Authority item instance. The service type
1005      * can be passed as a query param "type", and must match a configured type
1006      * for the service bindings. If not set, the type defaults to
1007      * ServiceBindingUtils.SERVICE_TYPE_PROCEDURE.
1008      *
1009      * @param parentspecifier either a CSID or one of the urn forms
1010      * @param itemspecifier either a CSID or one of the urn forms
1011      * @param ui the ui
1012      * 
1013      * @return the info for the referencing objects
1014      */
1015     @GET
1016     @Path("{csid}/items/{itemcsid}/refObjs")
1017     @Produces("application/xml")
1018     public AuthorityRefDocList getReferencingObjects(
1019             @PathParam("csid") String parentSpecifier,
1020             @PathParam("itemcsid") String itemSpecifier,
1021             @Context UriTemplateRegistry uriTemplateRegistry,
1022             @Context UriInfo uriInfo) {
1023         AuthorityRefDocList authRefDocList = null;
1024         try {
1025             authRefDocList = getReferencingObjects(null, parentSpecifier, itemSpecifier, uriTemplateRegistry, uriInfo);
1026         } catch (Exception e) {
1027             throw bigReThrow(e, ServiceMessages.GET_FAILED);
1028         }
1029         
1030         if (authRefDocList == null) {
1031             Response response = Response.status(Response.Status.NOT_FOUND).entity(
1032                     "Get failed, the requested Item CSID:" + itemSpecifier + ": was not found.").type(
1033                     "text/plain").build();
1034             throw new CSWebApplicationException(response);
1035         }
1036         return authRefDocList;
1037     }
1038     
1039     public AuthorityRefDocList getReferencingObjects(
1040                 ServiceContext existingContext,
1041             String parentspecifier,
1042             String itemspecifier,
1043             UriTemplateRegistry uriTemplateRegistry,
1044             UriInfo uriInfo) throws Exception {
1045         AuthorityRefDocList authRefDocList = null;
1046  
1047         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1048         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1049         //
1050         // Merge parts of existing context with our new context
1051         //
1052         if (existingContext != null && existingContext.getCurrentRepositorySession() != null) {
1053                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());  // If one exists, use the existing repo session
1054                 ctx.setProperties(existingContext.getProperties());
1055         }
1056
1057         String parentcsid = lookupParentCSID(ctx, parentspecifier, "getReferencingObjects(parent)", "GET_ITEM_REF_OBJS", uriInfo);
1058         String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "getReferencingObjects(item)", "GET_ITEM_REF_OBJS");
1059
1060         List<String> serviceTypes = queryParams.remove(ServiceBindingUtils.SERVICE_TYPE_PROP);
1061         if (serviceTypes == null || serviceTypes.isEmpty()) {
1062                 serviceTypes = ServiceBindingUtils.getCommonServiceTypes(true); //CSPACE-5359: Should now include objects, procedures, and authorities
1063         }
1064             
1065         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, null);
1066         authRefDocList = handler.getReferencingObjects(ctx, uriTemplateRegistry, serviceTypes, getRefPropName(), itemcsid);
1067
1068         return authRefDocList;
1069     }
1070
1071     /**
1072      * Gets the authority terms used in the indicated Authority item.
1073      *
1074      * @param parentspecifier either a CSID or one of the urn forms
1075      * @param itemspecifier either a CSID or one of the urn forms
1076      * @param ui passed to include additional parameters, like pagination controls
1077      *
1078      * @return the authority refs for the Authority item.
1079      */
1080     @GET
1081     @Path("{csid}/items/{itemcsid}/authorityrefs")
1082     @Produces("application/xml")
1083     public AuthorityRefList getAuthorityItemAuthorityRefs(
1084             @PathParam("csid") String parentspecifier,
1085             @PathParam("itemcsid") String itemspecifier,
1086             @Context UriInfo uriInfo) {
1087         AuthorityRefList authRefList = null;
1088         
1089         try {
1090             // Note that we have to create the service context for the Items, not the main service
1091             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1092             MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1093             String parentcsid = lookupParentCSID(parentspecifier, "getAuthorityItemAuthRefs(parent)", "GET_ITEM_AUTH_REFS", uriInfo);
1094             // We omit the parentShortId, only needed when doing a create...
1095             DocumentModelHandler<?, AbstractCommonList> handler =
1096                     (DocumentModelHandler<?, AbstractCommonList>)createItemDocumentHandler(ctx, parentcsid, null /*no parent short ID*/);
1097
1098             String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "getAuthorityItemAuthRefs(item)", "GET_ITEM_AUTH_REFS");
1099
1100             List<RefNameServiceUtils.AuthRefConfigInfo> authRefsInfo = RefNameServiceUtils.getConfiguredAuthorityRefs(ctx);
1101             authRefList = handler.getAuthorityRefs(itemcsid, authRefsInfo);
1102         } catch (Exception e) {
1103             throw bigReThrow(e, ServiceMessages.GET_FAILED + " parentspecifier: " + parentspecifier + " itemspecifier:" + itemspecifier);
1104         }
1105         
1106         return authRefList;
1107     }
1108     
1109     /**
1110      * Synchronizes a local authority item with a share authority server (SAS) item.
1111      * @param ctx
1112      * @param parentIdentifier
1113      * @param itemIdentifier
1114      * @return
1115      * @throws Exception
1116      */
1117     private PoxPayloadOut synchronizeItem(
1118                 ServiceContext ctx,
1119             String parentIdentifier,
1120             String itemIdentifier) throws Exception {
1121         PoxPayloadOut result = null;
1122         AuthorityItemSpecifier specifier;
1123         boolean neededSync = false;
1124
1125         CsidAndShortIdentifier parent = lookupParentCSIDAndShortIdentifer(ctx, parentIdentifier, "syncAuthorityItem(parent)", "SYNC_ITEM", null);
1126         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parent.CSID, parent.shortIdentifier);
1127         handler.setIsProposed(AuthorityServiceUtils.NOT_PROPOSED); // In case it was formally locally proposed, clear the proposed flag
1128         handler.setIsSASItem(AuthorityServiceUtils.SAS_ITEM); // Since we're sync'ing, this is now a SAS controlled item
1129         // Create an authority item specifier
1130         Specifier parentSpecifier = Specifier.getSpecifier(parent.CSID, "getAuthority", "GET");
1131         Specifier itemSpecifier = Specifier.getSpecifier(itemIdentifier, "getAuthorityItem", "GET");
1132         specifier = new AuthorityItemSpecifier(parentSpecifier, itemSpecifier);
1133         //
1134         neededSync = getRepositoryClient(ctx).synchronize(ctx, specifier, handler);
1135         if (neededSync == true) {
1136                 result = (PoxPayloadOut) ctx.getOutput();
1137         }
1138         
1139         return result;
1140     }
1141
1142     /**
1143      * Using the parent and item ID, sync the local item with the SAS (shared authority server)
1144      * Used by the AuthorityItemDocumentModelHandler when synchronizing a list of remote authority items with a
1145      * local authority.  The parent context was created for the authority (parent) because the sync started there.
1146      * @param existingCtx
1147      * @param parentIdentifier
1148      * @param itemIdentifier
1149      * @return
1150      * @throws Exception
1151      */
1152     public PoxPayloadOut synchronizeItemWithExistingContext(
1153                 ServiceContext existingCtx,
1154             String parentIdentifier,
1155             String itemIdentifier
1156             ) throws Exception {
1157         PoxPayloadOut result = null;
1158         
1159         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(),
1160                         existingCtx.getResourceMap(),
1161                         existingCtx.getUriInfo());
1162         if (existingCtx.getCurrentRepositorySession() != null) {
1163                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession());
1164         }
1165         result = synchronizeItem(ctx, parentIdentifier, itemIdentifier);
1166         
1167         return result;
1168     }
1169     
1170     /**
1171      * Synchronizes an authority item and with a Shared Authority Server (SAS) item.
1172      * 
1173      * @param specifier either CSIDs and/or one of the urn forms
1174      * 
1175      * @return the authority item if it was synchronized with SAS
1176      */
1177     @GET
1178     @Path("{csid}/items/{itemcsid}/sync")
1179     public byte[] synchronizeItem(
1180                 @Context ResourceMap resourceMap,
1181             @Context UriInfo uriInfo,
1182             @PathParam("csid") String parentIdentifier,
1183             @PathParam("itemcsid") String itemIdentifier) {
1184         byte[] result;
1185         boolean neededSync = false;
1186         PoxPayloadOut payloadOut = null;
1187         
1188         try {
1189             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), null, resourceMap, uriInfo);
1190             payloadOut = this.synchronizeItem(ctx, parentIdentifier, itemIdentifier);
1191             if (payloadOut != null) {
1192                 neededSync = true;
1193             }
1194         } catch (Exception e) {
1195             throw bigReThrow(e, ServiceMessages.SYNC_FAILED, itemIdentifier);
1196         }
1197
1198         //
1199         // If a sync was needed and was successful, return a copy of the updated resource.  Acts like an UPDATE.
1200         //
1201         if (neededSync == true) {
1202                 result = payloadOut.getBytes();
1203         } else {
1204                 result = String.format("Authority item resource '%s' was already in sync with shared authority server.",
1205                                 itemIdentifier).getBytes();
1206                 Response response = Response.status(Response.Status.NOT_MODIFIED).entity(result).type("text/plain").build();
1207             throw new CSWebApplicationException(response);
1208         }
1209         
1210         return result;
1211     }
1212     
1213     /**
1214      * Update authorityItem.
1215      * 
1216      * @param parentspecifier either a CSID or one of the urn forms
1217      * @param itemspecifier either a CSID or one of the urn forms
1218      *
1219      * @return the multipart output
1220      */
1221     @PUT
1222     @Path("{csid}/items/{itemcsid}")
1223     public byte[] updateAuthorityItem(
1224                 @Context ResourceMap resourceMap, 
1225             @Context UriInfo uriInfo,
1226             @PathParam("csid") String parentSpecifier,
1227             @PathParam("itemcsid") String itemSpecifier,
1228             String xmlPayload) {
1229         PoxPayloadOut result = null;
1230         
1231         try {
1232             PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload);
1233             result = updateAuthorityItem(null, resourceMap, uriInfo, parentSpecifier, itemSpecifier, theUpdate,
1234                         AuthorityServiceUtils.UPDATE_REV,                       // passing TRUE so rev num increases, passing
1235                         AuthorityServiceUtils.NO_CHANGE,        // don't change the state of the "proposed" field -we could be performing a sync or just a plain update
1236                         AuthorityServiceUtils.NO_CHANGE);       // don't change the state of the "sas" field -we could be performing a sync or just a plain update
1237         } catch (Exception e) {
1238             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED);
1239         }
1240         
1241         return result.getBytes();
1242     }
1243     
1244     public PoxPayloadOut updateAuthorityItem(
1245                 ServiceContext itemServiceCtx, // Ok to be null.  Will be null on PUT calls, but not on sync calls
1246                 ResourceMap resourceMap, 
1247             UriInfo uriInfo,
1248             String parentspecifier,
1249             String itemspecifier,
1250             PoxPayloadIn theUpdate,
1251             boolean shouldUpdateRevNumber,
1252             Boolean isProposed,
1253             Boolean isSASItem
1254             ) throws Exception {
1255         PoxPayloadOut result = null;
1256         
1257         CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(itemServiceCtx, parentspecifier, "updateAuthorityItem(parent)", "UPDATE_ITEM", null);
1258         String parentcsid = csidAndShortId.CSID;
1259         String parentShortId = csidAndShortId.shortIdentifier;
1260         //
1261         // If the itemServiceCtx context is not null, use it.  Otherwise, create a new context
1262         //
1263         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = itemServiceCtx;
1264         if (ctx == null) {
1265                 ctx = createServiceContext(getItemServiceName(), theUpdate, resourceMap, uriInfo);
1266         } else {
1267                 ctx.setInput(theUpdate); // the update payload
1268         }
1269         
1270         String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "updateAuthorityItem(item)", "UPDATE_ITEM"); //use itemServiceCtx if it is not null
1271
1272         // We omit the parentShortId, only needed when doing a create...
1273         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, parentShortId);
1274         handler.setShouldUpdateRevNumber(shouldUpdateRevNumber);
1275         if (isProposed != null) {
1276                 handler.setIsProposed(isProposed);
1277         }
1278         if (isSASItem != null) {
1279                 handler.setIsSASItem(isSASItem);
1280         }
1281         getRepositoryClient(ctx).update(ctx, itemcsid, handler);
1282         result = ctx.getOutput();
1283
1284         return result;
1285     }
1286
1287     /**
1288      * Delete authorityItem.
1289      * 
1290      * @param parentIdentifier the parentcsid
1291      * @param itemIdentifier the itemcsid
1292      * 
1293      * @return the response
1294      */
1295     @DELETE
1296     @Path("{csid}/items/{itemcsid}")
1297     public Response deleteAuthorityItem(
1298             @Context UriInfo uriInfo,
1299             @PathParam("csid") String parentIdentifier,
1300             @PathParam("itemcsid") String itemIdentifier) {
1301         Response result = null;
1302
1303         ensureCSID(parentIdentifier, ServiceMessages.DELETE_FAILED, "AuthorityItem.parentcsid");
1304         ensureCSID(itemIdentifier, ServiceMessages.DELETE_FAILED, "AuthorityItem.itemcsid");
1305         if (logger.isDebugEnabled()) {
1306             logger.debug("deleteAuthorityItem with parentcsid=" + parentIdentifier + " and itemcsid=" + itemIdentifier);
1307         }
1308         
1309         try {
1310             ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo);
1311             deleteAuthorityItem(ctx, parentIdentifier, itemIdentifier, AuthorityServiceUtils.UPDATE_REV);
1312             result = Response.status(HttpResponseCodes.SC_OK).build();
1313         } catch (Exception e) {
1314             throw bigReThrow(e, ServiceMessages.DELETE_FAILED + "  itemcsid: " + itemIdentifier + " parentcsid:" + parentIdentifier);
1315         }
1316
1317         return result;
1318     }
1319
1320     /**
1321      * 
1322      * @param existingCtx
1323      * @param parentIdentifier
1324      * @param itemIdentifier
1325      * @throws Exception
1326      */
1327     @SuppressWarnings("rawtypes")
1328         public void deleteAuthorityItem(ServiceContext existingCtx,
1329             String parentIdentifier,
1330             String itemIdentifier,
1331             boolean shouldUpdateRevNumber
1332             ) throws Exception {
1333         Response result = null;
1334         
1335         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), existingCtx.getUriInfo());
1336         if (existingCtx != null && existingCtx.getCurrentRepositorySession() != null) {
1337                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession());
1338                 ctx.setProperties(existingCtx.getProperties());
1339         }
1340         
1341         String parentcsid = null;
1342         try {
1343                 parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null);
1344         } catch (DocumentNotFoundException de) {
1345                 logger.warn(String.format("Could not find parent with ID='%s' when trying to delete item ID='%s'",
1346                                 parentIdentifier, itemIdentifier));
1347         }
1348         String itemCsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null
1349         
1350         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
1351                 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
1352
1353         getRepositoryClient(ctx).delete(ctx, itemCsid, handler);
1354     }
1355
1356     @GET
1357     @Path("{csid}/items/{itemcsid}/" + hierarchy)
1358     @Produces("application/xml")
1359     public String getHierarchy(
1360                 @PathParam("csid") String parentIdentifier,
1361             @PathParam("itemcsid") String itemIdentifier,
1362             @Context UriInfo uriInfo) throws Exception {
1363         String result = null;
1364
1365         try {
1366                 //
1367             // 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...?
1368                 //
1369             String calledUri = uriInfo.getPath();
1370             String uri = "/" + calledUri.substring(0, (calledUri.length() - ("/" + hierarchy).length()));
1371             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1372             
1373             String parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null);
1374             String itemcsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null
1375             
1376             String direction = uriInfo.getQueryParameters().getFirst(Hierarchy.directionQP);
1377             if (Tools.notBlank(direction) && Hierarchy.direction_parents.equals(direction)) {
1378                 result = Hierarchy.surface(ctx, itemcsid, uri);
1379             } else {
1380                 result = Hierarchy.dive(ctx, itemcsid, uri);
1381             }            
1382         } catch (Exception e) {
1383             throw bigReThrow(e, "Error showing hierarchy for authority item: ", itemIdentifier);
1384         }
1385         
1386         return result;
1387     }
1388     
1389     /**
1390      * 
1391      * @param tenantId
1392      * @return
1393      */
1394     protected String getItemDocType(String tenantId) {
1395         return getDocType(tenantId, getItemServiceName());
1396     }
1397         
1398     /**
1399      * Returns a UriRegistry entry: a map of tenant-qualified URI templates
1400      * for the current resource, for all tenants
1401      * 
1402      * @return a map of URI templates for the current resource, for all tenants
1403      */
1404     @Override
1405     public Map<UriTemplateRegistryKey,StoredValuesUriTemplate> getUriRegistryEntries() {
1406         Map<UriTemplateRegistryKey,StoredValuesUriTemplate> uriRegistryEntriesMap =
1407                 super.getUriRegistryEntries();
1408         List<String> tenantIds = getTenantBindingsReader().getTenantIds();
1409         for (String tenantId : tenantIds) {
1410                 uriRegistryEntriesMap.putAll(getUriRegistryEntries(tenantId, getItemDocType(tenantId), UriTemplateFactory.ITEM));
1411         }
1412         return uriRegistryEntriesMap;
1413     }
1414   
1415 }