/*
* 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.mediatype.CryptoMediaType;
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;
/**
* An attribute resource is an extended attribute of some other primary resource.
* It does not have its own identifier - the identifier belongs to the primary
* resource.
*
* Attribute resources are singular - there can only be one. Therefore
* only retrieve and store methods are supported. Search is not supported.
* Delete and post are not supported either because an extended
* attribute can only be get or set - not removed or created.
*
* Subclasses of AbstractAttributeResource should be annotated with
* @Path("/primary-resource-name/{id}/attribute-name") where {id} refers
* to the primary resource's id.
*
* There cannot be a collection of extended attributes - a collection means
* the client must be able to distinguish one item from another, which
* in turn means they must have their own identifiers. That would make them
* not extended attributes but related resources and they should subclass
* AbstractSimpleResource or AbstractJsonapiResource instead.
*
*
* @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 AbstractAttributeResource<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(AbstractAttributeResource.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();
/**
* Retrieve the extended attribute.
* 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 extended attribute.
* 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 extended attribute. 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;
}
}