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:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
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.
24 package org.collectionspace.services.vocabulary;
26 import org.collectionspace.services.client.IClientQueryParams;
27 import org.collectionspace.services.client.PayloadInputPart;
28 import org.collectionspace.services.client.PoxPayload;
29 import org.collectionspace.services.client.PoxPayloadIn;
30 import org.collectionspace.services.client.PoxPayloadOut;
31 import org.collectionspace.services.client.VocabularyClient;
32 import org.collectionspace.services.client.workflow.WorkflowClient;
33 import org.collectionspace.services.common.CSWebApplicationException;
34 import org.collectionspace.services.common.ResourceMap;
35 import org.collectionspace.services.common.ServiceMessages;
36 import org.collectionspace.services.common.UriInfoWrapper;
37 import org.collectionspace.services.common.api.Tools;
38 import org.collectionspace.services.common.context.ServiceBindingUtils;
39 import org.collectionspace.services.common.context.ServiceContext;
40 import org.collectionspace.services.common.document.DocumentException;
41 import org.collectionspace.services.common.document.DocumentHandler;
42 import org.collectionspace.services.common.document.JaxbUtils;
43 import org.collectionspace.services.common.repository.RepositoryClient;
44 import org.collectionspace.services.common.vocabulary.AuthorityResource;
45 import org.collectionspace.services.common.vocabulary.AuthorityServiceUtils;
46 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
47 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
48 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm;
49 import org.collectionspace.services.jaxb.AbstractCommonList;
50 import org.collectionspace.services.jaxb.AbstractCommonList.ListItem;
51 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
52 import org.collectionspace.services.nuxeo.client.java.NuxeoRepositoryClientImpl;
53 import org.collectionspace.services.vocabulary.nuxeo.VocabularyItemDocumentModelHandler;
54 import org.collectionspace.services.workflow.WorkflowCommon;
55 import org.nuxeo.ecm.core.api.DocumentModel;
56 import org.nuxeo.ecm.core.api.DocumentModelList;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.w3c.dom.Element;
61 import javax.ws.rs.GET;
62 import javax.ws.rs.POST;
63 import javax.ws.rs.PUT;
64 import javax.ws.rs.Path;
65 import javax.ws.rs.PathParam;
66 import javax.ws.rs.core.Context;
67 import javax.ws.rs.core.MultivaluedMap;
68 import javax.ws.rs.core.Request;
69 import javax.ws.rs.core.Response;
70 import javax.ws.rs.core.UriBuilder;
71 import javax.ws.rs.core.UriInfo;
73 @Path("/" + VocabularyClient.SERVICE_PATH_COMPONENT)
74 public class VocabularyResource extends
75 AuthorityResource<VocabulariesCommon, VocabularyItemDocumentModelHandler> {
81 private final static String vocabularyServiceName = VocabularyClient.SERVICE_PATH_COMPONENT;
83 private final static String VOCABULARIES_COMMON = "vocabularies_common";
85 private final static String vocabularyItemServiceName = "vocabularyitems";
86 private final static String VOCABULARYITEMS_COMMON = "vocabularyitems_common";
88 final Logger logger = LoggerFactory.getLogger(VocabularyResource.class);
90 public VocabularyResource() {
91 super(VocabulariesCommon.class, VocabularyResource.class,
92 VOCABULARIES_COMMON, VOCABULARYITEMS_COMMON);
97 public Response createAuthority(
98 @Context ResourceMap resourceMap,
99 @Context UriInfo uriInfo,
102 // 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
103 // transaction code to deal with a database level UNIQUE constraint violations on the 'shortidentifier' column of the vocabularies_common table.
104 // Therefore, to prevent having multiple authorities with the same shortid, we need to synchronize
105 // the code that creates new authorities. The authority document model handler will first check for authorities with the same short id before
106 // trying to create a new authority.
108 synchronized(AuthorityResource.class) {
110 PoxPayloadIn input = new PoxPayloadIn(xmlPayload);
111 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(input);
112 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = this.getRepositoryClient(ctx);
114 CoreSessionInterface repoSession = repoClient.getRepositorySession(ctx);
116 DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
117 String csid = repoClient.create(ctx, handler);
118 handleItemsPayload(Method.POST, repoSession, csid, resourceMap, uriInfo, input);
119 UriBuilder path = UriBuilder.fromResource(resourceClass);
120 path.path("" + csid);
121 Response response = Response.created(path.build()).build();
123 } catch (Throwable t) {
124 repoSession.setTransactionRollbackOnly();
127 repoClient.releaseRepositorySession(ctx, repoSession);
129 } catch (Exception e) {
130 throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
138 public byte[] updateAuthority(
139 @Context ResourceMap resourceMap,
140 @Context UriInfo uriInfo,
141 @PathParam("csid") String specifier,
143 PoxPayloadOut result = null;
145 PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload);
146 Specifier spec = Specifier.getSpecifier(specifier, "updateAuthority", "UPDATE");
147 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(theUpdate);
148 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = this.getRepositoryClient(ctx);
150 CoreSessionInterface repoSession = repoClient.getRepositorySession(ctx);
152 DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
154 if (spec.form == SpecifierForm.CSID) {
157 String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value);
158 csid = getRepositoryClient(ctx).findDocCSID(null, ctx, whereClause);
160 getRepositoryClient(ctx).update(ctx, csid, handler);
161 handleItemsPayload(Method.PUT, repoSession, csid, resourceMap, uriInfo, theUpdate);
162 result = ctx.getOutput();
163 } catch (Throwable t) {
164 repoSession.setTransactionRollbackOnly();
167 repoClient.releaseRepositorySession(ctx, repoSession);
169 } catch (Exception e) {
170 throw bigReThrow(e, ServiceMessages.UPDATE_FAILED);
172 return result.getBytes();
175 private boolean handleItemsPayload(
177 CoreSessionInterface repoSession,
178 String parentIdentifier,
179 ResourceMap resourceMap,
181 PoxPayloadIn input) throws Exception {
182 boolean result = true;
184 PayloadInputPart abstractCommonListPart = input.getPart(PoxPayload.ABSTRACT_COMMON_LIST_ROOT_ELEMENT_LABEL);
185 if (abstractCommonListPart != null) {
186 AbstractCommonList itemsList = (AbstractCommonList) abstractCommonListPart.getBody();
187 for (ListItem item : itemsList.getListItem()) {
188 String errMsg = null;
189 boolean success = true;
190 Response response = null;
191 PoxPayloadOut payloadOut = null;
192 PoxPayloadIn itemXmlPayload = getItemXmlPayload(item);
195 response = this.createAuthorityItem(repoSession, resourceMap, uriInfo, parentIdentifier, itemXmlPayload);
196 if (response.getStatus() != Response.Status.CREATED.getStatusCode()) {
198 errMsg = String.format("Could not create the term list payload of vocabuary '%s'.", parentIdentifier);
202 String itemSpecifier = getSpecifier(item);
203 if (itemSpecifier != null) {
204 payloadOut = updateAuthorityItem(repoSession, resourceMap, uriInfo, parentIdentifier, itemSpecifier, itemXmlPayload);
205 if (payloadOut == null) {
207 errMsg = String.format("Could not update the term list payload of vocabuary '%s'.", parentIdentifier);
211 errMsg = String.format("Could not update the term list payload of vocabuary '%s' because one of the item is missing a CSID or short identifier value.",
217 // Throw an exception as soon as we have problems with any item
219 if (success == false) {
220 throw new DocumentException(errMsg);
229 * We'll return null if we can create a specifier from the list item.
234 private String getSpecifier(ListItem item) {
235 String result = null;
238 for (Element ele : item.getAny()) {
239 String fieldName = ele.getTagName();
240 String fieldValue = ele.getTextContent();
241 if (fieldName.equalsIgnoreCase("csid")) {
242 result = csid = fieldValue;
248 String shortId = null;
249 for (Element ele : item.getAny()) {
250 String fieldName = ele.getTagName();
251 String fieldValue = ele.getTextContent();
252 if (fieldName.equalsIgnoreCase("shortIdentifier")) {
253 shortId = fieldValue;
258 if (shortId != null) {
259 result = Specifier.createShortIdURNValue(shortId);
267 * This is very brittle. If the class VocabularyitemsCommon changed with new fields we'd have to
268 * update this method.
272 * @throws DocumentException
274 private PoxPayloadIn getItemXmlPayload(ListItem item) throws DocumentException {
275 PoxPayloadIn result = null;
277 VocabularyitemsCommon vocabularyItem = new VocabularyitemsCommon();
278 for (Element ele : item.getAny()) {
279 String fieldName = ele.getTagName();
280 String fieldValue = ele.getTextContent();
283 vocabularyItem.setDisplayName(fieldValue);
286 case "shortIdentifier":
287 vocabularyItem.setShortIdentifier(fieldValue);
291 vocabularyItem.setOrder(fieldValue);
295 vocabularyItem.setSource(fieldValue);
299 vocabularyItem.setSourcePage(fieldValue);
303 vocabularyItem.setDescription(fieldValue);
307 vocabularyItem.setCsid(fieldValue);
311 throw new DocumentException(String.format("Unknown field '%s' in vocabulary item payload.",
316 result = new PoxPayloadIn(VocabularyClient.SERVICE_ITEM_PAYLOAD_NAME, vocabularyItem,
317 VOCABULARYITEMS_COMMON);
322 private Response createAuthorityItem(
323 CoreSessionInterface repoSession,
324 ResourceMap resourceMap,
326 String parentIdentifier, // Either a CSID or a URN form -e.g., a8ad38ec-1d7d-4bf2-bd31 or urn:cspace:name(bugsbunny)
327 PoxPayloadIn input) throws Exception {
328 Response result = null;
330 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), input, resourceMap, uriInfo);
331 ctx.setCurrentRepositorySession(repoSession);
333 result = createAuthorityItem(ctx, parentIdentifier, AuthorityServiceUtils.UPDATE_REV,
334 AuthorityServiceUtils.PROPOSED, AuthorityServiceUtils.NOT_SAS_ITEM);
339 private PoxPayloadOut updateAuthorityItem(
340 CoreSessionInterface repoSession,
341 ResourceMap resourceMap,
343 String parentSpecifier, // Either a CSID or a URN form -e.g., a8ad38ec-1d7d-4bf2-bd31 or urn:cspace:name(bugsbunny)
344 String itemSpecifier, // Either a CSID or a URN form.
345 PoxPayloadIn theUpdate) throws Exception {
346 PoxPayloadOut result = null;
348 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), theUpdate, resourceMap, uriInfo);
349 ctx.setCurrentRepositorySession(repoSession);
351 result = updateAuthorityItem(ctx, resourceMap, uriInfo, parentSpecifier, itemSpecifier, theUpdate,
352 AuthorityServiceUtils.UPDATE_REV, // passing TRUE so rev num increases, passing
353 AuthorityServiceUtils.NO_CHANGE, // don't change the state of the "proposed" field -we could be performing a sync or just a plain update
354 AuthorityServiceUtils.NO_CHANGE); // don't change the state of the "sas" field -we could be performing a sync or just a plain update
363 @Context Request request,
364 @Context UriInfo uriInfo,
365 @PathParam("csid") String specifier) {
366 Response result = null;
367 uriInfo = new UriInfoWrapper(uriInfo);
370 MultivaluedMap<String,String> queryParams = uriInfo.getQueryParameters();
371 String showItemsValue = (String)queryParams.getFirst(VocabularyClient.SHOW_ITEMS_QP);
372 boolean showItems = Tools.isTrue(showItemsValue);
373 if (showItems == true) {
375 // We'll honor paging params if we find any; otherwise we'll set the page size to 0 to get ALL the items
377 if (queryParams.containsKey(IClientQueryParams.PAGE_SIZE_PARAM) == false) {
378 queryParams.add(IClientQueryParams.PAGE_SIZE_PARAM, "0");
382 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(request, uriInfo);
383 PoxPayloadOut payloadout = getAuthority(ctx, request, uriInfo, specifier, showItems);
384 result = buildResponse(ctx, payloadout);
385 } catch (Exception e) {
386 throw bigReThrow(e, ServiceMessages.GET_FAILED, specifier);
389 if (result == null) {
390 Response response = Response.status(Response.Status.NOT_FOUND).entity(
391 "GET request failed. The requested Authority specifier:" + specifier + ": was not found.").type(
392 "text/plain").build();
393 throw new CSWebApplicationException(response);
400 public String getServiceName() {
401 return vocabularyServiceName;
405 public String getItemServiceName() {
406 return vocabularyItemServiceName;
410 public Class<VocabulariesCommon> getCommonPartClass() {
411 return VocabulariesCommon.class;
415 * @return the name of the property used to specify references for items in this type of
416 * authority. For most authorities, it is ServiceBindingUtils.AUTH_REF_PROP ("authRef").
417 * Some types (like Vocabulary) use a separate property.
420 protected String getRefPropName() {
421 return ServiceBindingUtils.TERM_REF_PROP;
425 protected String getOrderByField(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
426 String result = null;
428 result = authorityItemCommonSchemaName + ":" + VocabularyItemJAXBSchema.DISPLAY_NAME;
434 protected String getPartialTermMatchField(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
435 return getOrderByField(ctx);
439 * The item schema for the Vocabulary service does not support a multi-valued term list. Only authorities that support
440 * term lists need to implement this method.
443 public String getItemTermInfoGroupXPathBase() {