Tuesday, October 14, 2014

Slim3 REST Controller Example

I was playing with AngularJS and slim3, then bum into REST. So here is an example for sharing.

At the client side I have angular code calling PUT:
$http.put('organization', {name: $scope.name}).success(
 function(data){
  alert('Success');
 });
the GET, POST and DELETE will be more or less the same. $http.get(), $http.post(), $http.delete().

One thing I found interesting about slim3 is the URL rewrite with AppRouter.java. Here is what I do (with evil smile):
addRouting(
 "/secured/{entity}",
 "/secured/rest?entity={entity}");
addRouting(
 "/secured/{entity}/{resourceId}",
 "/secured/rest?entity={entity}&resourceId={resourceId}");

I route both URL to the same Controller. So here is my Controller. My root package is "smis". (I know, I know...)


package smis.controller.secured;

import java.io.BufferedReader;
import java.lang.reflect.Method;
import java.util.List;

import org.apache.log4j.Logger;
import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import org.slim3.datastore.Datastore;
import org.slim3.datastore.ModelMeta;

import com.google.appengine.api.datastore.Key;

public class RestController extends Controller {

 private Logger logger = Logger.getLogger(getClass());

 @Override
 public Navigation run() throws Exception {
  String entityName = asString("entity");
  String resourceId = asString("resourceId");

  // hack class name. :p
entityName = entityName.substring(0, 1).toUpperCase()
    + entityName.substring(1);

  Class entityClass = Class.forName("smis.model." + entityName);
  Class modelMetaClass = Class.forName("smis.meta." + entityName
    + "Meta");
  Method method = modelMetaClass.getMethod("get");
  Object modelMetaInstance = method.invoke(null);

  logger.debug(entityClass);
  logger.debug(modelMetaClass);
  logger.debug(modelMetaInstance);

  logger.debug("Method = " + request.getMethod());

  if (isGet()) {
   if (resourceId == null) {
    return handleList((ModelMeta) modelMetaInstance);
   } else {
    return handleDetail(entityClass,
      (ModelMeta) modelMetaInstance);
   }
  } else if (isPost()) {
   return handleCreate((ModelMeta) modelMetaInstance);
  } else if (isPut()) {
   return handleCreate((ModelMeta) modelMetaInstance);
  } else if (isDelete()) {
   return handleDelete(entityClass);
  }

  return null;
 }

 private Navigation handleDelete(Class entityClass) throws Exception {
  Key key = asKey("resourceId");
  Datastore.delete(key);
  Object t = Datastore.get(entityClass, key);
  logger.debug(t == null);
  response.setContentType("text/plain");
  response.setCharacterEncoding("UTF-8");
  response.getWriter().print(t == null);
  response.flushBuffer();
  return null;
 }

 private Navigation handleCreate(ModelMeta modelMeta) throws Exception {
  StringBuffer jb = new StringBuffer();
  String line = null;
  try {
   BufferedReader reader = request.getReader();
   while ((line = reader.readLine()) != null)
    jb.append(line);
  } catch (Exception e) { /* report an error */
   e.printStackTrace();
  }

  String json = jb.toString();
  logger.debug(json);
  Object t = modelMeta.jsonToModel(json);
  boolean isComplete = Datastore.put(t).isComplete();
  logger.debug(isComplete);
  response.setContentType("text/plain");
  response.setCharacterEncoding("UTF-8");
  response.getWriter().print(isComplete);
  response.flushBuffer();
  return null;
 }

 private Navigation handleDetail(Class entityClass, ModelMeta modelMeta)
   throws Exception {
  Key key = asKey("resourceId");
  logger.debug(key);
  Object t = Datastore.get(entityClass, key);
  String json = modelMeta.modelToJson(t);
  logger.debug(json);
  response.setContentType("application/json");
  response.setCharacterEncoding("UTF-8");
  response.getWriter().print(json);
  response.flushBuffer();
  return null;
 }

 private Navigation handleList(ModelMeta modelMeta) throws Exception {
  List list = Datastore.query(modelMeta).asList();
  String json = modelMeta.modelsToJson(list);
  logger.debug(json);
  response.setContentType("application/json");
  response.setCharacterEncoding("UTF-8");
  response.getWriter().print(json);
  response.flushBuffer();
  return null;
 }
}
Because my lazy nature, I use reflection to create the Class for my entity and also the instance of the Meta class. Thanks to how slim3 name the model and meta class. There is a high security risk here! Not recommended for production, OK?

Then based on the isXXX() functions I call the handleXXXXX() accordingly. The implementation explains itself.

Some more interesting would be the asKey("my_key"), it does the same thing like:
 request.getParameter("my_key") + parse it into a Key.
Very handy.

Last thing to point out is how to digest the request data:

StringBuffer jb = new StringBuffer();
String line = null;
try {
 BufferedReader reader = request.getReader();
 while ((line = reader.readLine()) != null)
  jb.append(line);
} catch (Exception e) { /* report an error */
 e.printStackTrace();
}

The experiment was fun. I enjoy writing and playing with it. Hope it is the same for you too.

No comments: