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