/*
* Copyright (C) 2013 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.jaxrs2.server.resource;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intel.mtwilson.jaxrs2.server.PATCH;
import com.intel.dcsg.cpg.io.UUID;
import com.intel.dcsg.cpg.validation.ValidationUtil;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import com.intel.mtwilson.jaxrs2.Document;
import com.intel.mtwilson.jaxrs2.DocumentCollection;
import com.intel.mtwilson.repository.FilterCriteria;
import com.intel.mtwilson.repository.Locator;
import com.intel.mtwilson.jaxrs2.Patch;
import com.intel.mtwilson.jaxrs2.PatchLink;
import com.intel.mtwilson.jaxrs2.mediatype.DataMediaType;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.BeanParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* Reference: https://jersey.java.net/documentation/latest/user-guide.html
* https://jersey.java.net/documentation/latest/media.html
* https://jersey.java.net/documentation/latest/deployment.html
* https://wikis.oracle.com/display/Jersey/Overview+of+JAX-RS+1.0+Features
* http://docs.oracle.com/cd/E19776-01/820-4867/6nga7f5o5/index.html
*
* When listing the media types in
*
* @Produces annotations, it's important to put application/json before
* application/vnd.api+json so it will be chosen as the default if the browser
* doesn't specify an Accept header. If application/json is first, accessing
* /hosts will display the JSON output in the browser. If
* application/vnd.api+json is first, the browser will not recognize that
* content type (even though it has +json) and will download it as a file. The
* .json file extension maps to application/json specifically. JSON API clients
* must set the Accept header if they want to receive the output with
* Content-Type: application/vnd.api+json
*
*
* Example simple JSON output:
*
*
* {"hosts":[{"id":"06285da4-e170-4322-a843-480f3a55feec","name":"hostabc","connection_url":"http://1.2.3.4","description":"test
* host","bios_mle":"bios-4.3.2"}]}
*
* Example XML output:
* *
<host_collection><hosts><host><id>bd7094d2-2ed3-468e-9c16-40999f9e4b8c</id><name>hostabc</name><connectionUrl>http://1.2.3.4</connectionUrl><description>test
* host</description><biosMLE>bios-4.3.2</biosMLE></host></hosts></host_collection>
*
* This abstract class defines the following HTTP interface:
*
* GET /collection -> application/vnd.api+json, application/json,
* application/xml, application/yaml, text/yaml
*
* POST /collection Content-Type: application/json, application/xml,
* application/yaml, text/yaml -> application/json, application/xml,
* application/yaml, text/yaml
*
*
*
* @author jbuhacoff
*/
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, DataMediaType.APPLICATION_YAML, DataMediaType.TEXT_YAML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, DataMediaType.APPLICATION_YAML, DataMediaType.TEXT_YAML})
public abstract class AbstractSimpleResource<T extends Document, C extends DocumentCollection<T>, F extends FilterCriteria<T>, P extends PatchLink<T>, L extends Locator<T>> {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractSimpleResource.class);
private ObjectMapper mapper = new ObjectMapper(); // for debugging only
/*
private SimpleRepository<T,C,F,L> repository;
public void setRepository(SimpleRepository<T,C,F,L> repository) {
this.repository = repository;
}
protected SimpleRepository<T,C,F,L> getRepository() { return repository; }
*/
protected abstract DocumentRepository<T,C,F,L> getRepository();
@GET
public C searchCollection(@BeanParam F selector) {
try { log.debug("searchCollection: {}", mapper.writeValueAsString(selector)); } catch(JsonProcessingException e) { log.debug("searchCollection: cannot serialize selector: {}", e.getMessage()); }
ValidationUtil.validate(selector); // throw new MWException(e, ErrorCode.AS_INPUT_VALIDATION_ERROR, input, method.getName());
return getRepository().search(selector);
}
/**
* Add an item to the collection. Input Content-Type is any of
* application/json, application/xml, application/yaml, or text/yaml Output
* Content-Type is any of application/json, application/xml,
* application/yaml, or text/yaml
*
* The input must represent a single item NOT wrapped in a collection.
*
* @param item
* @return
*/
@POST
public T createOne(@BeanParam L locator, T item) {
try { log.debug("createOne: {}", mapper.writeValueAsString(locator)); } catch(JsonProcessingException e) { log.debug("createOne: cannot serialize locator: {}", e.getMessage()); }
locator.copyTo(item);
ValidationUtil.validate(item); // throw new MWException(e, ErrorCode.AS_INPUT_VALIDATION_ERROR, input, method.getName());
if (item.getId() == null) {
item.setId(new UUID());
}
getRepository().create(item);
return item;
}
// the delete method is on a specific resource id and because we don't return any content it's the same whether its simple object or json api
// jersey automatically returns status code 204 No Content (successful) to the client because
// we have a void return type
@Path("/{id}")
@DELETE
public void deleteOne(@BeanParam L locator) {
try { log.debug("deleteOne: {}", mapper.writeValueAsString(locator)); } catch(JsonProcessingException e) { log.debug("deleteOne: cannot serialize locator: {}", e.getMessage()); }
T item = getRepository().retrieve(locator); // subclass is responsible for validating the id in whatever manner it needs to; most will return null if !UUID.isValid(id) but we don't do it here because a resource might want to allow using something other than uuid as the url key, for example uuid OR hostname for hosts
if (item == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
getRepository().delete(locator);
/*
T item = getRepository().retrieve(id); // subclass is responsible for validating the id in whatever manner it needs to; most will return null if !UUID.isValid(id) but we don't do it here because a resource might want to allow using something other than uuid as the url key, for example uuid OR hostname for hosts
if (item == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
getRepository().delete(id);*/
/*
// C collection = getRepository().search(selector);
// if( collection.getDocuments().isEmpty() ) {
// throw new WebApplicationException(Response.Status.NOT_FOUND);
// }
// T item = collection.getDocuments().get(0);
// getRepository().delete(item.getId().toString());
* */
}
@DELETE
public void deleteCollection(@BeanParam F selector) {
try { log.debug("deleteCollection: {}", mapper.writeValueAsString(selector)); } catch(JsonProcessingException e) { log.debug("deleteCollection: cannot serialize selector: {}", e.getMessage()); }
C collection = getRepository().search(selector);
if( collection.getDocuments().isEmpty() ) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
// Do the delete here after search
getRepository().delete(selector);
/*for(T item : collection.getDocuments()) {
getRepository().delete();
}*/
}
/**
* Retrieve an item from the collection. Input Content-Type is not
* applicable. Output Content-Type is any of application/json,
* application/xml, application/yaml, or text/yaml
*
* The output represents a single item NOT wrapped in a collection.
*
* @param id
* @return
*/
@Path("/{id}")
@GET
public T retrieveOne(@BeanParam L locator) {
try { log.debug("retrieveOne: {}", mapper.writeValueAsString(locator)); } catch(JsonProcessingException e) { log.debug("retrieveOne: cannot serialize locator: {}", e.getMessage()); }
/*
T item = getRepository().retrieve(id); // subclass is responsible for validating the id in whatever manner it needs to; most will return null if !UUID.isValid(id) but we don't do it here because a resource might want to allow using something other than uuid as the url key, for example uuid OR hostname for hosts
if (item == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}*/
/*
// C collection = getRepository().search(selector);
// if( collection.getDocuments().isEmpty() ) {
// throw new WebApplicationException(Response.Status.NOT_FOUND);
// }
// T item = collection.getDocuments().get(0);
* */
T existing = getRepository().retrieve(locator); // subclass is responsible for validating the id in whatever manner it needs to; most will return null if !UUID.isValid(id) but we don't do it here because a resource might want to allow using something other than uuid as the url key, for example uuid OR hostname for hosts
if (existing == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return existing;
}
/**
* Replace an item in the collection. Input Content-Type is any of
* application/json, application/xml, application/yaml, or text/yaml Output
* Content-Type is any of application/json, application/xml,
* application/yaml, or text/yaml
*
* The input is a single item NOT wrapped in a collection. The output is the
* single item NOT wrapped in a collection.
*
* @param id
* @param item
* @return
*/
@Path("/{id}")
@PUT
public T storeOne(@BeanParam L locator, T item) {
try { log.debug("storeOne: {}", mapper.writeValueAsString(locator)); } catch(JsonProcessingException e) { log.debug("storeOne: cannot serialize locator: {}", e.getMessage()); }
ValidationUtil.validate(item);
// item.setId(UUID.valueOf(id));
locator.copyTo(item);
T existing = getRepository().retrieve(locator); // subclass is responsible for validating id
if (existing == null) {
getRepository().create(item);
} else {
getRepository().store(item);
}
return item;
}
// the patch method only accepts the patch content type
/**
* Update an item in the collection. Input Content-Type is a special patch
* document format. Output Content-Type is any of application/json,
* application/xml, application/yaml, or text/yaml.
*
* The input is a patch format for a single item. The output is a single
* item after applying the patch, NOT wrapped in a collection.
*
* @param id
* @return
*/
@Path("/{id}")
@PATCH
@Consumes({DataMediaType.APPLICATION_RELATIONAL_PATCH_JSON})
public T patchOne(@BeanParam L locator, Patch<T, F, P>[] patchArray) {
try { log.debug("patchOne: {}", mapper.writeValueAsString(locator)); } catch(JsonProcessingException e) { log.debug("patchOne: cannot serialize locator: {}", e.getMessage()); }
T item = getRepository().retrieve(locator); // subclass is responsible for validating id
if (item == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
locator.copyTo(item);
ValidationUtil.validate(patchArray);
for (int i = 0; i < patchArray.length; i++) {
log.debug("Processing patch #{} of {}", i + 1, patchArray.length);
if (false /* error during processing */) {
// 400 bad request or 500 internal server error
return null;
}
}
//return new Host();
// return patch(null);
return null;
}
}