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