Sunday, November 30, 2014

Alor Setar Cafe

Just want to keep track of these Cafe in Alor Setar. Wonder how long they will last...


  1. Cube Coffee 
  2. Coffeebooks & Rain 
  3. 1973 Espresso Cafe 
  4. Coffee Lips Cafe 
  5. Parrot Espresso Cafe 
  6. Coffee Diem
  7. Oh Bella Cafe
  8. Coffee D
  9. Bon Bon Ya 
  10. Coffee Lunatic 
  11. Mc Cafe 
  12. Habit Art Cafe 
  13. Bites Cafe & Galary
  14. Le' Medina Patisserie
  15. Secret Recipes 
  16. Coffee Lap
  17. Green Leaf 
  18. Uncle Jonh
  19. Georgetown Cafe 
  20. Oldtown Cafe 
  21. Coffebean ( Coming Soon )
  22. Starbucks ( Coming Soon )
  23. Deco Cafe ( Coming Soon )
  24. Coffee Tiffz Gallery ( Coming Soon )
  25. Paper Plane Cafe ( Coming Soon )
  26. The Hat Cabin ( Coming Soon )
  27. Coffee Lips ( Comimg Soon )

Thursday, November 13, 2014

What did "cordova create" do?

Just for curiosity, I ran this command to find out.

$ cordova create myapp1 com.otccomputing.myapp1 "My App 1"
Creating a new cordova project with name "My App 1" and id "com.otccomputing.myapp1" at location "/Users/guest/apps/myapp1"

And here we go.  I expand all the sub folder in myapp1.


Now let's take a look at those files.




config.xml, used by cordova to do compile/build/plugin whatever task. Without it, it is just not a cordova project anymore.
The content in config.xml is simple and easy to digest. 
id = a unique identity used to identify your app
version = as named. etc. 1.0.0, 2.0.0, 3.0.4...
description: write anything you want
author: change it to your detail - email, website
content: which file cordova will start with - don't change this one.

We now look at index.html

The most important 2 lines are:

cordova.js MUST be there. Otherwise your apps just won't work. I don't know what black magic behind it but - leave it there.
index.js is not that important, your apps may still work if it doesn't exist but lets have a look.

The main thing here is event listening. index.js listen to 'deviceready' event, which is fired by cordova after some backend initialization is ready. You might consider trigger some function in your app AFTER 'deviceready' is triggered or they may not work. Most plugin in GitHub will mention this requirement in their documentation. 
the receivedEvent() implementation can be ignored or you can put in your own login here.

So for development strategy, I usually work on my HTML, JS,CSS in an IDE, test them on the browser, looks good, then move then into the www folder and do further integration. 

Bottom line, as long as config.xml is there, cordova.js in included in your index.html, things will work. 





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.