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