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.

Monday, October 13, 2014

AngularJS Experiment - Dynamic Change Model Value

Experiment

This is my test page.
 and the js


So the result look like this


Now I want the time to change every second



But that does not happened...
Try add this line into it














And it works.
Lets do something a little aggressive...

and the browser didn't break. So we all good. :)

Friday, October 10, 2014

Custom User Account for Google App Engine - Part I

Google App Engine (GAE) is good. I like it, but...

Well, I create an app (in java), I want user to sign in, and GAE want the user to have google account. What if I don't want that, well, I can then implement my own user authentication, which is shit (cannot use request.getUserPrincipal()).
How about using OAuth... may be this can works.

I want to create 100 users to use my app but I don't want to create 100 google account.

Still a basic concept at the moment. Not sure if it will work. Will do an experiment soon.

Sunday, October 5, 2014

Setting up Slim3 Framework for Google App Engine using Eclipse Luna

Slim3, new to me, cool stuff. Official web site: https://sites.google.com/site/slim3appengine/Home

Step 1 - get eclipse for JEE (eclipse-jee-luna-SR1-win32-x86_64) downloaded, unzip, run, install Google plugin, and done.

Step 2 - File > New > Project... > Google > Web Application Project
Here I turn off GWT. Other than that, fill in your project info accordingly.
PS: If you don't see this screen, DON'T PROCEED. There is something very wrong in your setup. lol.

Step 3 - Download slim3 v 1.0.16 from here, then unzip it into a folder. It should look like this.
The structure is similar to your project structure in eclipse, only with few extra (slim3) files.

Step 4 - Copy paste.
Now highlighted are files copied from slim3-blank folder.

Also, some jar as well from WEB-INF/lib


Step 5 - Annotation Processing Factory configuration.
This setting is simple but very important. It actually help generate source code for you. You'll know what it does as you continue working on your project.
Right click your project > properties and find Java Compiler > Annotation Processing.
Enable project specific settings.
Enable annotation processing.

Click on Factory path (left panel).
Click Add JARs...
Find slim3-gen-1.0.16.jar from your project. Add it.
Move it to the top.

Click OK and we leave the settings as it is for now.

Step 6 - web.xml
The web.xml was copied from slim-blank folder, only thing need to change is the slim3.rootPackage. It should be something like "com.mycompany", but...

Anyway, step 7 - Ant build.
As you can see, there is a build.xml file copied from slim3. This file will be heavily used throughout the project. So it will be a good idea to open a Ant view to use it.

To show Ant view, go Window > Show View > Others... > Ant > Ant
Then find and click the little add buildfiles icon and add the build.xml from your project folder. And you will see display as picture above.

From slim3 tutorial, the difference is you don't need to right click the build.xml and Run As...
Just double click target in the Ant View and you are done.

Step 8 - now, let's see how the magic works. Run gen-controller (double click on the ant view). You will see a popup, key in the path to your controller.
Once you click OK, slim3 will create a few files on your project.

Let's look at each file closely.
As I entered '/admin/user/' as controller path, slim3 creates:

  • IndexController.java - the controller class to handle URL http://localhost:8888/admin/user/ or http://localhost:8888/admin/user/index
  • IndexControllerTest.java - test class to test the controller. I love this feature.
  • war/admin/user/index.jsp - the View 
In short, code generator. (for lazy developer like me)

Step 9 - let's look at the controller.
Oh No~!! We have a problem! Help!!!
Nar~! No worries. Just build path issues.
Go to WEB-INF/lib, right click slim3-1.0.16.jar and add to build path. Solved.


Step 10 - not ready to run yet.
GAE is complaining. What to do... 
Go to eclipse  > preference > Java > Installed JREs, add JDK 1.7 into it. Click the checkbox to make it the default. Default is only JRE. Sorry for not covering the details.



Once configured, you should see the changes on your project showing it is using JDK and the error message will be gone.


Step 11 - show time
Right click your project and run as google web application.

Output on the console:

Now we go to http://localhost:8888/admin/user/

Just to make sure what is happening. Edit war/admin/user/index.jsp

Yeap! We got hello world.

In short, path "/admin/user/" is handled by the controller, then the controller forward us to /war/admin/user/index.jsp. Typical mvc, right.

This is my first approach trying slim3. I believe there is a more elegant way to set it up. Any comments for improvement is welcome. Actually there is a slim3 plugin for eclipse too, I only found out after this...

My next assignment was to implement RBAC in slim3. Will post some more stuff when it is ready.

Embedding ActiveMQ in Spring Web Apps

Notes: I won't recommend this approach. It is for prove of concept. Run stand alone instance of your ActiveMQ. May the force be with you.

Problem:


I have a process that need to do calculation on an array of data. The thing is it takes very very very long to complete the calculation. As the result, the user click on something, then they need to wait for the browser to load and think very hard before it show something back to the use, whereas the client doesn't really cares about the outcome of the calculation.

Design:

I initially try make an asynchronous function call but the system requires the data been processed in sequence, one after one on an order. So I went explore if Messaging can help solve my problem.

Environment:

activemq-broker-5.10.0.jar
activemq-client-5.10.0.jar
activemq-spring-5.10.0.jar
javax.jms.jar
javax.management.j2ee.jar
spring-core-4.1.0.RELEASE.jar
spring-jms-4.1.0.RELEASE.jar
spring-messaging-4.1.0.RELEASE.jar
(and others... sorry, I can't list them all)

Implementation:

In applicationContext.xml, add namespace for amq and jms.



Found this somewhere online, create a embedded dummy broker with spring.
Define a queue.

Create a spring connectionFactory to be used later.

JMS Template - for sending message to the broker.

As in the comment, settings for annotated JmsMessageListener in Spring 4.1 (again, must be 4.1)

And always remember to turn it on.

And here is the java code.

After everything works, I expand the settings to include more queues and also set up a proper ActiveMQ broker running by itself (removing the tag and have pointing to my ActiveMQ url).