/*
* Copyright (c) 2012, Soren Davidsen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package scrumdo.meta;
import com.google.gson.*;
import org.joda.time.DateTime;
import scrumdo.transport.ITransport;
import scrumdo.transport.Response;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.logging.Logger;
/**
* Implements operations in the tastypie data model.
*
* We depend on the availability of @ResourcePath and TYPE information for the automatic URL building and deserialization.
*
* @author Soren <soren@tanesha.net>
*/
public class Tastypie {
private Logger LOG = Logger.getLogger(Tastypie.class.getName());
private Gson gson;
private ITransport transport;
private URL baseURL;
private String[] fixedParameters;
public Tastypie(GsonBuilderCallback callback, ITransport transport, URL baseURL, String... fixedParameters) {
this.transport = transport;
this.baseURL = baseURL;
this.fixedParameters = fixedParameters;
// create gson interface.
GsonBuilder builder = new GsonBuilder()
.registerTypeAdapter(DateTime.class, new DateTimeSerializer())
.registerTypeAdapter(ResourceURI.class, new ResourceURIHandler());
callback.doWithBuilder(builder);
this.gson = builder.create();
}
private void appendParameters(StringBuffer url, String... extraParameters) {
boolean first = true;
for (int i = 0; i < fixedParameters.length; i += 2) {
url.append(first ? "?" : "&").
append(URLEncoder.encode(fixedParameters[i])).
append("=").
append(URLEncoder.encode(fixedParameters[i + 1]));
first = false;
}
for (int i = 0; i < extraParameters.length; i += 2) {
url.append(first ? "?" : "&").
append(URLEncoder.encode(extraParameters[i])).
append("=").
append(URLEncoder.encode(extraParameters[i + 1]));
first = false;
}
}
private <T> String buildURL(ResourceURI resourceURI, String... extraParameters) {
try {
StringBuffer url = new StringBuffer(new URL(baseURL, resourceURI.resource_uri).toExternalForm());
appendParameters(url, extraParameters);
return url.toString();
} catch (MalformedURLException mue) {
throw new RuntimeException("URL malformed: " + mue.getMessage(), mue);
}
}
private ResourceURI buildResourceURIFromLocation(String location) {
if (location.startsWith(baseURL.toExternalForm())) {
try {
return new ResourceURI(new URL(location).getPath());
}
catch (MalformedURLException mfe) {
throw new RuntimeException("mfe: " + mfe.getMessage(), mfe);
}
}
else
return null;
}
private <T> ResourceURI buildResourceURI(Class<T> clazz, String id) {
return new ResourceURI(findResourcePath(clazz) + "/" + (id == null ? "" : id + "/"));
}
/**
* Find all objects of a class. Uses a type parameter to infer the generic type in the json deserialization.
*
* @param clazz Class to query for
* @param actualRequestedType Type for the actual FindResult<T>
* @param <T>
* @return
*/
public <T> FindResult<T> findAll(Class<T> clazz, Type actualRequestedType) {
String url = buildURL(buildResourceURI(clazz, null));
Response response = transport.get(url);
return gson.fromJson(response.content, actualRequestedType);
}
/**
* Find one object based on class and a specific resource.
* @param clazz The class to map to
* @param uri The resource URI
* @param <T>
* @return
*/
public <T> T findOne(Class<T> clazz, ResourceURI uri) {
String url = buildURL(uri);
Response response = transport.get(url);
return gson.fromJson(response.content, clazz);
}
/**
* Find one object based on the object's "id" value and it's class.
* @param clazz the class
* @param id the id of the object
* @param <T>
* @return
*/
public <T> T find(Class<T> clazz, String id) {
String url = buildURL(buildResourceURI(clazz, id));
Response response = transport.get(url);
return gson.fromJson(response.content, clazz);
}
/**
* Save an object.
* @param object The object to save.
* @param <T>
* @return
*/
public <T> ResourceURI create(T object) {
String url = buildURL(buildResourceURI(object.getClass(), null));
Response response = transport.post(url, gson.toJson(object));
if (200 <= response.responseCode && response.responseCode < 300) {
return buildResourceURIFromLocation(response.location);
}
else {
LOG.info("create => " + response);
return null;
}
}
public <T> void update(String id, T object) {
String url = buildURL(buildResourceURI(object.getClass(), id));
Response response = transport.put(url, gson.toJson(object));
LOG.info("update => " + response);
}
public <T> void delete(Class<T> clazz, String id) {
String url = buildURL(buildResourceURI(clazz, id));
Response response = transport.delete(url);
LOG.info("delete => " + response);
}
private static String findResourcePath(Class clazz) {
if (clazz.isAnnotationPresent(ResourcePath.class))
return ((ResourcePath) clazz.getAnnotation(ResourcePath.class)).value();
else
return null;
}
private class DateTimeSerializer implements JsonSerializer<DateTime>, JsonDeserializer<DateTime> {
public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return new DateTime(json.getAsJsonPrimitive().getAsString());
}
}
private class ResourceURIHandler implements JsonSerializer<ResourceURI>, JsonDeserializer<ResourceURI> {
public JsonElement serialize(ResourceURI src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.resource_uri);
}
public ResourceURI deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return new ResourceURI(json.getAsJsonPrimitive().getAsString());
}
}
}