/*
* This is eMonocot, a global online biodiversity information resource.
*
* Copyright © 2011–2015 The Board of Trustees of the Royal Botanic Gardens, Kew and The University of Oxford
*
* eMonocot is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* eMonocot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* The complete text of the GNU Affero General Public License is in the source repository as the file
* ‘COPYING’. It is also available from <http://www.gnu.org/licenses/>.
*/
package org.emonocot.portal.controller;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import org.emonocot.api.Service;
import org.emonocot.model.Base;
import org.emonocot.pager.Page;
import org.restdoc.api.GlobalHeader;
import org.restdoc.api.MethodDefinition;
import org.restdoc.api.ParamValidation;
import org.restdoc.api.ResponseDefinition;
import org.restdoc.api.RestDoc;
import org.restdoc.api.RestResource;
import org.restdoc.api.Schema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.orm.hibernate3.HibernateObjectRetrievalFailureException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.util.JSONPObject;
import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper;
/**
*
* @author ben
* @param <T> the type of object served by this controller
* @param <SERVICE> the service supplying this object
*/
public abstract class GenericController<T extends Base,
SERVICE extends Service<T>> {
private static Logger logger = LoggerFactory.getLogger(GenericController.class);
private SERVICE service;
private ObjectMapper objectMapper;
private String directory;
private Class<T> type;
@Autowired
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
public GenericController(String directory, Class<T> type) {
this.directory = directory;
this.type = type;
}
/**
*
* @return the directory where this resource is found
*/
private String getDirectory() {
return directory;
}
/**
* @param identifier
* Set the identifier of the image
* @return A model and view containing a image
*/
@RequestMapping(value = "/{identifier}",
method = RequestMethod.GET,
consumes = "application/json",
produces = "application/json")
public ResponseEntity<T> get(@PathVariable String identifier, @RequestParam(value = "fetch", required = false) String fetch) {
return new ResponseEntity<T>(service.find(identifier,fetch), HttpStatus.OK);
}
/**
* @param identifier
* Set the identifier of the image
* @return A model and view containing a image
*/
@RequestMapping(value = "/{identifier}",
params = "callback",
method = RequestMethod.GET,
produces = "application/javascript")
public ResponseEntity<JSONPObject> getJsonP(@PathVariable String identifier, @RequestParam(value = "fetch", required = false) String fetch,
@RequestParam(value = "callback", required = true) String callback) {
return new ResponseEntity<JSONPObject>(new JSONPObject(callback,service.find(identifier,fetch)), HttpStatus.OK);
}
@RequestMapping(method = RequestMethod.GET,
consumes = "application/json",
produces = "application/json")
public ResponseEntity<Page<T>> list(@RequestParam(value = "limit", required = false, defaultValue = "10") Integer limit,
@RequestParam(value = "start", required = false, defaultValue = "0") Integer start) {
return new ResponseEntity<Page<T>>(service.list(start, limit, null), HttpStatus.OK);
}
/**
* @param object
* the object to save
* @return A response entity containing a newly created image
* @throws Exception
*/
@RequestMapping(method = RequestMethod.POST,
produces = "application/json",
consumes = "application/json")
public ResponseEntity<T> create(@RequestBody T object, UriComponentsBuilder builder) throws Exception {
try {
service.merge(object);
} catch(Exception e) {
logger.error(e.getLocalizedMessage());
for(StackTraceElement ste : e.getStackTrace()) {
logger.error(ste.toString());
}
throw e;
}
T persistedObject = service.find(object.getIdentifier());
HttpHeaders headers = new HttpHeaders();
headers.setLocation(builder.path("/" + getDirectory() + "/{id}").buildAndExpand(persistedObject.getId()).toUri());
return new ResponseEntity<T>(object, headers, HttpStatus.CREATED);
}
/**
* @param identifier
* Set the identifier of the image
* @return A response entity containing the status
*/
@RequestMapping(value = "/{id}",
method = RequestMethod.DELETE,
consumes = "application/json",
produces = "application/json")
public ResponseEntity<T> delete(@PathVariable Long id) {
service.deleteById(id);
return new ResponseEntity<T>(HttpStatus.OK);
}
@RequestMapping(method = RequestMethod.OPTIONS,
produces = "application/json")
public ResponseEntity<RestDoc> optionsResource() throws JsonMappingException {
RestDoc restDoc = new RestDoc();
HashMap<String,Schema> schemas = new HashMap<String,Schema>();
Schema pagerSchema = new Schema();
SchemaFactoryWrapper pageVisitor = new SchemaFactoryWrapper();
objectMapper.acceptJsonFormatVisitor(objectMapper.constructType(Page.class), pageVisitor);
pagerSchema.setSchema(pageVisitor.finalSchema());
schemas.put("http://e-monocot.org#page", pagerSchema);
Schema objectSchema = new Schema();
SchemaFactoryWrapper objectVisitor = new SchemaFactoryWrapper();
objectMapper.acceptJsonFormatVisitor(objectMapper.constructType(type), objectVisitor);
objectSchema.setSchema(objectVisitor.finalSchema());
schemas.put("http://e-monocot.org#" + directory, objectSchema);
restDoc.setSchemas(schemas);
GlobalHeader headers = new GlobalHeader();
headers.request("Content-Type","Must be set to application/json",true);
headers.request("Authorization","Supports HTTP Basic. Users may also use their api key",false);
restDoc.setHeaders(headers);
ParamValidation integerParam = new ParamValidation();
integerParam.setType("match");
integerParam.setPattern("\\d+");
ParamValidation apikeyParam = new ParamValidation();
apikeyParam.setType("match");
apikeyParam.setPattern("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
ParamValidation stringParam = new ParamValidation();
stringParam.setType("match");
stringParam.setPattern("[0-9a-f]+");
Set<RestResource> resources = new HashSet<RestResource>();
RestResource listOfObjects = new RestResource();
listOfObjects.setId(type.getSimpleName() + "List");
listOfObjects.setPath("/" + directory + "{?limit,start,callback,apikey,fetch}");
listOfObjects.param("limit", "The maximum number of resources to return", integerParam);
listOfObjects.param("start", "The number of pages (of size _limit_) offset from the beginning of the recordset", integerParam);
listOfObjects.param("apikey", "The apikey of the user account making the request", apikeyParam);
listOfObjects.param("callback", "The name of the callback function used to wrap the JSON response", stringParam);
listOfObjects.param("fetch", "The name of a valid 'fetch-profile' which will load some or all related objects prior to serialization. Try 'object-page' to return most related objects", stringParam);
MethodDefinition listObjects = new MethodDefinition();
listObjects.description("List " + type.getSimpleName() + " resources");
ResponseDefinition listObjectsResponseDefinition = new ResponseDefinition();
listObjectsResponseDefinition.type("application/json", "http://e-monocot.org#page");
listObjectsResponseDefinition.type("application/javascript", "http://e-monocot.org#page");
listObjects.response(listObjectsResponseDefinition);
listObjects.statusCode("200", "Successfully retrieved a list of 0+ resources");
listOfObjects.method("GET", listObjects);
MethodDefinition createObject = new MethodDefinition();
ResponseDefinition createdResponseDefinition = new ResponseDefinition();
createdResponseDefinition.type("application/json", "http://e-monocot.org#" + type.getSimpleName());
createdResponseDefinition.header("Location", "The location of the created resource", true);
createObject.response(createdResponseDefinition);
createObject.description("Create a new " + type.getSimpleName() + " resource");
createObject.accept("application/json", "http://e-monocot.org#" + type.getSimpleName());
createObject.statusCode("201", "Successfully created the resource");
listOfObjects.method("POST", createObject);
resources.add(listOfObjects);
RestResource singleObject = new RestResource();
singleObject.setId(type.getSimpleName());
singleObject.setPath("/" + directory + "/{identifier}{?apikey,callback}");
singleObject.param("apikey", "The apikey of the user account making the request", apikeyParam);
singleObject.param("callback", "The name of the callback function used to wrap the JSON response", stringParam);
singleObject.param("identifier", "The identifier of the object", stringParam);
MethodDefinition getObject = new MethodDefinition();
getObject.description("Get a " + type.getSimpleName() + " resource");
ResponseDefinition getObjectResponseDefinition = new ResponseDefinition();
getObjectResponseDefinition.type("application/json", "http://e-monocot.org#" + type.getSimpleName());
getObjectResponseDefinition.type("application/javascript", "http://e-monocot.org#" + type.getSimpleName());
getObject.response(getObjectResponseDefinition);
getObject.statusCode("200", "Successfully retrieved a resource");
singleObject.method("GET", getObject);
MethodDefinition updateObject = new MethodDefinition();
ResponseDefinition updatedObjectResponseDefinition = new ResponseDefinition();
updatedObjectResponseDefinition.type("application/json", "http://e-monocot.org#" + type.getSimpleName());
updateObject.response(updatedObjectResponseDefinition);
updateObject.description("Update an existing " + type.getSimpleName() + " resource");
updateObject.accept("application/json", "http://e-monocot.org#" + type.getSimpleName());
updateObject.statusCode("200", "Successfully updated the resource");
singleObject.method("POST", updateObject);
MethodDefinition deleteObject = new MethodDefinition();
ResponseDefinition deletedObjectResponseDefinition = new ResponseDefinition();
deletedObjectResponseDefinition.type("application/json", "http://e-monocot.org#" + type.getSimpleName());
deleteObject.response(deletedObjectResponseDefinition);
deleteObject.description("Delete an existing " + type.getSimpleName() + " resource");
deleteObject.accept("application/json", "http://e-monocot.org#" + type.getSimpleName());
deleteObject.statusCode("200", "Successfully deleted the resource");
singleObject.method("DELETE", deleteObject);
resources.add(singleObject);
restDoc.setResources(resources);
return new ResponseEntity<RestDoc>(restDoc,HttpStatus.OK);
}
/**
*
* @param newService Set the service
*/
public void setService(SERVICE newService) {
this.service = newService;
}
/**
* @return the service
*/
public SERVICE getService() {
return service;
}
@ExceptionHandler(HibernateObjectRetrievalFailureException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public ModelAndView handleObjectNotFoundException(HibernateObjectRetrievalFailureException orfe) {
ModelAndView modelAndView = new ModelAndView("resourceNotFound");
modelAndView.addObject("exception", orfe);
return modelAndView;
}
}