diff --git a/.gitignore b/.gitignore
index 88dc327..ee77796 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ javadoc
bin/
lib/
*~
+.svn/
diff --git a/client/src/main/java/br/com/caelum/restfulie/Restfulie.java b/client/src/main/java/br/com/caelum/restfulie/Restfulie.java
index fda1427..f896bc4 100644
--- a/client/src/main/java/br/com/caelum/restfulie/Restfulie.java
+++ b/client/src/main/java/br/com/caelum/restfulie/Restfulie.java
@@ -19,9 +19,11 @@
import java.net.URI;
import java.net.URISyntaxException;
+
import br.com.caelum.restfulie.http.DefaultHttpRequest;
import br.com.caelum.restfulie.http.DefaultRestClient;
import br.com.caelum.restfulie.http.Request;
+import br.com.caelum.restfulie.mediatype.GsonMediaType;
/**
* Restfulie's client API entry point.
@@ -44,6 +46,13 @@ public static RestClient custom() {
return new DefaultRestClient();
}
+ /**
+ * Entry point to configure serialization data prior to accessing the resources (json only).
+ */
+ public static RestClient jsonOnly() {
+ return new DefaultRestClient( new GsonMediaType() );
+ }
+
/**
* Entry point to direct access an uri.
*/
diff --git a/client/src/main/java/br/com/caelum/restfulie/http/DefaultRestClient.java b/client/src/main/java/br/com/caelum/restfulie/http/DefaultRestClient.java
index e6db705..be2b84a 100644
--- a/client/src/main/java/br/com/caelum/restfulie/http/DefaultRestClient.java
+++ b/client/src/main/java/br/com/caelum/restfulie/http/DefaultRestClient.java
@@ -30,6 +30,7 @@
import br.com.caelum.restfulie.http.apache.ApacheDispatcher;
import br.com.caelum.restfulie.mediatype.FormEncoded;
import br.com.caelum.restfulie.mediatype.JsonMediaType;
+import br.com.caelum.restfulie.mediatype.MediaType;
import br.com.caelum.restfulie.mediatype.MediaTypes;
import br.com.caelum.restfulie.mediatype.XmlMediaType;
import br.com.caelum.restfulie.request.RequestDispatcher;
@@ -61,6 +62,21 @@ public DefaultRestClient()
this.threads = Executors.newCachedThreadPool();
}
+ /**
+ * Constructor which only will register the specified media types
+ * @param medias MediaTypes to be registered with this client
+ * @author Felipe Brandao
+ */
+ public DefaultRestClient( MediaType...medias )
+ {
+ this.dispatcher = new ApacheDispatcher(this);
+ this.inflector = new NounPluralizer();
+
+ for( MediaType media : medias ) this.types.register( media );
+
+ this.threads = Executors.newCachedThreadPool();
+ }
+
public DefaultRestClient use(RequestDispatcher executor) {
this.dispatcher = executor;
return this;
diff --git a/client/src/main/java/br/com/caelum/restfulie/http/JsonRestClient.java b/client/src/main/java/br/com/caelum/restfulie/http/JsonRestClient.java
new file mode 100644
index 0000000..bb47fc8
--- /dev/null
+++ b/client/src/main/java/br/com/caelum/restfulie/http/JsonRestClient.java
@@ -0,0 +1,108 @@
+package br.com.caelum.restfulie.http;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.jvnet.inflector.Pluralizer;
+import org.jvnet.inflector.lang.en.NounPluralizer;
+
+import br.com.caelum.restfulie.RestClient;
+import br.com.caelum.restfulie.RestfulieException;
+import br.com.caelum.restfulie.http.apache.ApacheDispatcher;
+import br.com.caelum.restfulie.mediatype.GsonMediaType;
+import br.com.caelum.restfulie.mediatype.MediaTypes;
+import br.com.caelum.restfulie.request.RequestDispatcher;
+
+/**
+ * RestClient implementation based on DefaultRestClient
+ *
+ * This client has been created to allow using restfulie without XML related
+ * libs, which i consider a bit bloat if users really don't need any XML stuff.
+ *
+ * @author Felipe Brandao
+ *
+ */
+public class JsonRestClient implements RestClient{
+
+ private final MediaTypes types = new MediaTypes();
+
+ private RequestDispatcher dispatcher;
+
+ private Pluralizer inflector;
+
+ private URI lastURI = null;
+
+ private final ExecutorService threads;
+
+ public JsonRestClient(){
+ this.dispatcher = new ApacheDispatcher(this);
+ //TODO don't know if really need to use it
+ this.inflector = new NounPluralizer();
+ types.register(new GsonMediaType());
+ this.threads = Executors.newCachedThreadPool();
+ }
+
+ public JsonRestClient use(RequestDispatcher executor) {
+ this.dispatcher = executor;
+ return this;
+ }
+
+ public RequestDispatcher getProvider() {
+ return dispatcher;
+ }
+
+ public MediaTypes getMediaTypes() {
+ return types;
+ }
+
+ /**
+ * Entry point to direct access an uri.
+ */
+ public Request at(URI uri) {
+ lastURI = uri;
+ return createRequestFor(uri);
+ }
+
+ /**
+ * Override this method to use your own Request object
+ *
+ * @param uri
+ * @return
+ */
+ protected Request createRequestFor(URI uri) {
+ return new DefaultHttpRequest(uri, this);
+ }
+
+ /**
+ * Entry point to direct access an uri.
+ * @throws URISyntaxException
+ */
+ public Request at(String uri) {
+ try {
+ return at(new URI(uri));
+ } catch (URISyntaxException e) {
+ throw new RestfulieException("Unable to build an URI for this request.", e);
+ }
+ }
+
+ public URI lastURI() {
+ return lastURI;
+ }
+
+ public Pluralizer inflectionRules() {
+ return inflector;
+ }
+
+ public RestClient withInflector(Pluralizer inflector){
+ this.inflector = inflector;
+ return this;
+ }
+
+ @Override
+ public ExecutorService getThreads() {
+ return threads;
+ }
+
+}
diff --git a/client/src/main/java/br/com/caelum/restfulie/mediatype/GsonMediaType.java b/client/src/main/java/br/com/caelum/restfulie/mediatype/GsonMediaType.java
new file mode 100644
index 0000000..701308f
--- /dev/null
+++ b/client/src/main/java/br/com/caelum/restfulie/mediatype/GsonMediaType.java
@@ -0,0 +1,154 @@
+package br.com.caelum.restfulie.mediatype;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import br.com.caelum.restfulie.RestClient;
+import br.com.caelum.restfulie.mediatype.MediaType;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Media Type implementation for unmarshalling data received in JSON format using GSON library
+ *
+ * @author Felipe Brandao
+ *
+ */
+public class GsonMediaType implements MediaType{
+
+ private final static Pattern rootTypeDetectionRegex = Pattern.compile( "^\\{[ ]*\"([^\"]*)\"[ ]*:(.*)}$" );
+
+ private final List types = Arrays.asList("application/json", "text/json", "json");
+
+ private Map> classes = new HashMap>();
+ private Map collections = new HashMap();
+
+ private Gson gson;
+
+ public GsonMediaType() {
+ this.gson = new Gson();
+ }
+
+ /**
+ * Register a class which should be used for unmarshalling.
+ *
+ * It uses the lower case class name as alias to knows what should be unmarshalled.
+ *
+ * @see #withType(String, Class)
+ * @param javaType Java class to be registered
+ * @return This instance
+ */
+ public GsonMediaType withType( Class> javaType ){
+ String correctAlias = javaType.getSimpleName();
+ correctAlias = correctAlias.substring( 0 , 1 ).toLowerCase() + correctAlias.substring( 1 );
+ return this.withType( correctAlias , javaType );
+ }
+
+ /**
+ * Register a class which should be used for unmarshalling.
+ *
+ * Uses the informed alias to knows what should be unmarshalled.
+ *
+ * @param alias Alias to the class
+ * @param javaType Java class to be registered
+ * @return This instance
+ */
+ public GsonMediaType withType( String alias , Class> javaType ){
+ this.classes.put( alias , javaType );
+ return this;
+ }
+
+ /**
+ * Register an alias to identify a JSON resource (usually top level arrays) which should be unmarshalled for a
+ * specific Collection.
+ *
+ * @see TypeToken
+ * @param alias Collection alias
+ * @param collectionType Java Type which sould be used (use TypeToken)
+ * @return This instance
+ */
+ public GsonMediaType withCollection( String alias , Type collectionType ) {
+ this.collections.put( alias , collectionType );
+ return this;
+ }
+
+ @Override
+ public boolean answersTo( String type ) {
+ return types.contains( type );
+ }
+
+ @Override
+ public void marshal( T payload, Writer writer, RestClient client ) throws IOException {
+ String json = this.gson.toJson( payload );
+ System.out.println( "Marshalled object:" + json );
+ writer.append( json );
+ }
+
+ @Override
+ public T unmarshal( String content, RestClient client ) {
+ JsonData jsonData = new JsonData();
+ jsonData.json = content;
+
+ Class rootClass = detectRootClass( jsonData );
+ if( rootClass != null ){ //we know wich class should be used
+ return this.gson.fromJson( jsonData.json , rootClass );
+ }else{//detection failed, will try to detect as top level array (alias for collection)
+ Type rootType = detectRootType( jsonData );
+
+ if( rootType == null ){//we don't know exactly what to do, so let's blow up
+ throw new IllegalArgumentException( "There's no registered class/collection alias for '" + jsonData.alias + "'" );
+ }
+
+
+ return this.gson.fromJson( jsonData.json , rootType );
+ }
+ }
+
+ /**
+ * Detects which class should be used for unmarshalling
+ * @param
+ * @param jsonData
+ * @return A class object for unmarshalling JSON data
+ */
+ @SuppressWarnings("unchecked")
+ private Class detectRootClass( JsonData jsonData ){
+ Matcher matcher = rootTypeDetectionRegex.matcher( jsonData.json );
+ if( matcher.matches() ){
+ String alias = matcher.group(1);
+ //defines informations inside jsonData, if fails to detect a registered class for unmarshall (could be a collection)
+ jsonData.alias = alias;
+ jsonData.json = matcher.group(2);
+
+ Class javaClass = (Class) this.classes.get( alias );
+ return javaClass;
+ }
+ return null;
+ }
+
+ /**
+ * Detects which type should be used for unmarshalling a top level array
+ * @param jsonData
+ * @return
+ */
+ private Type detectRootType( JsonData jsonData ){
+ return this.collections.get( jsonData.alias );
+ }
+
+
+ /**
+ * Handler for value related with the (un)marshalling
+ * @author felipebn
+ */
+ private final static class JsonData{
+ String alias;
+ String json;
+ }
+}
diff --git a/client/src/test/java/br/com/caelum/restfulie/GsonMediaTypeTest.java b/client/src/test/java/br/com/caelum/restfulie/GsonMediaTypeTest.java
new file mode 100644
index 0000000..3a4b231
--- /dev/null
+++ b/client/src/test/java/br/com/caelum/restfulie/GsonMediaTypeTest.java
@@ -0,0 +1,82 @@
+package br.com.caelum.restfulie;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.jvnet.inflector.lang.en.NounPluralizer;
+
+import br.com.caelum.restfulie.mediatype.GsonMediaType;
+
+import com.google.gson.reflect.TypeToken;
+
+public class GsonMediaTypeTest {
+
+ public static class Order{
+ private int id;
+ private String client;
+ public Order() {}
+ Order(int id, String client) {
+ this.id = id;
+ this.client = client;
+ }
+ @Override
+ public boolean equals( Object obj ) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Order other = (Order) obj;
+ if (client == null) {
+ if (other.client != null)
+ return false;
+ } else if (!client.equals( other.client ))
+ return false;
+ if (id != other.id)
+ return false;
+ return true;
+ }
+ }
+
+ private GsonMediaType mediaType;
+ private RestClient client = mock(RestClient.class);
+
+ @Before
+ public void setup() {
+ Locale.setDefault(Locale.ENGLISH);
+ mediaType = new GsonMediaType()
+ .withType(Order.class)
+ .withCollection( "orders", new TypeToken>(){}.getType() );
+ when(client.inflectionRules()).thenReturn(new NounPluralizer());
+ }
+
+ @Test
+ public void shouldDeserializeWithoutLinks() {
+ String json = "{\"order\":{\"id\":10,\"client\":\"Jonh Doe\"}}";
+ Order expected = new Order(10,"Jonh Doe");
+ Order order = mediaType.unmarshal(json, client);
+ assertThat(order, is(equalTo(expected)));
+ }
+
+ @Test
+ public void shouldDeserializeCollections() {
+ String json = "{\"orders\":[{\"id\":10,\"client\":\"Jonh Doe\"},{\"id\":20,\"client\":\"Jane Doe\"}]}";
+ Order expected = new Order(10,"Jonh Doe");
+ Order expected2 = new Order(20,"Jane Doe");
+ List orders = mediaType.unmarshal(json, client);
+ assertThat(orders, hasItem(equalTo(expected)));
+ assertThat(orders, hasItem(equalTo(expected2)));
+ }
+
+}