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