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). 

Thursday, September 25, 2014

Implementation of immutable Value Object

Was reading article about Domain Driven Design here and wrote this snippet (in Java) about immutable Value Object. By setting the parameter to final will make it immutable, which will restrict the developer to change it's value.

Hope this is what the author means :p


Friday, October 19, 2012

Processing BPMN xml with javascript

My Code
Require: xml2json(xml, tab) from http://goessner.net/. Modified to not use @

var xmlDoc; // used for debug
var myjson; // used for debug
function dummyBPMNTest() {

 function parseXml(xml) {
  console.log('xml = ' + xml);
  var json = eval('(' + xml2json(xml, "") + ')');
  console.log(json);
  myjson = json;
  console.log('Looking for start event.');
  var startEvent = json.definitions.process.startEvent; 
//  var endEvent = json.definitions.process.endEvent; 
  var processProperties = {};
  if( isArray(json.definitions.process.property)) {
   for(var i in json.definitions.process.property) {
    processProperties[json.definitions.process.property[i].id] = null; // not type 
   }
  } else {
   processProperties[json.definitions.process.property.id] = null;
  }
  console.log(processProperties);
  
  function loopSequenceFlow(id) {
   console.log('looking for next node.');
   var nextSequence = [];
//   console.log(json.definitions.process.sequenceFlow);
   for(var i in json.definitions.process.sequenceFlow) {
    var sequence = json.definitions.process.sequenceFlow[i];
//    console.log('sequence.sourceRef = ' + sequence.sourceRef + ', startEvent.id = ' + startEvent.id);
    if(sequence.sourceRef == id) {
     nextSequence.push(sequence);
    }
   }
   if(nextSequence.length < 1) {
    console.log('No more task to run.');
    return;
   }
   console.log(nextSequence);
   var nextNode = null;
   // sort
   nextSequence.sort(function(a, b){
    return a['tns:priority']  -  b['tns:priority'];
   });
   console.log('After sort.');
   console.log(nextSequence);
   // process
   if(nextSequence.length > 1){
    for(var i in nextSequence) {
//     try the conditionaExpression
     if(nextSequence[i].conditionExpression) {
      console.log(nextSequence[i].conditionExpression['#text']);
      var r = eval(nextSequence[i].conditionExpression['#text']);
      console.log('r = ' + r);
      if(r) {
       console.log('next node id = ' + nextSequence[i].targetRef);
       nextNode = eval('(' + xml2json(xml.getElementById(nextSequence[i].targetRef), "") + ')');
       break;
      }
     }
    }
   } else {
    console.log('next node id = ' + nextSequence[0].targetRef);
    nextNode = eval('(' + xml2json(xml.getElementById(nextSequence[0].targetRef), "") + ')');
   }
   
   console.log(nextNode);
   if(nextNode == null) {
    return;
   }
   var nodeType = null;
   for(x in nextNode) {
    nodeType = x;
//    console.log('object is a ' + x);
   }
   var node = nextNode[nodeType];
   console.log('Running [' + nodeType + ']:' + node.name);
   switch(nodeType) {
   case 'scriptTask':
//    console.log('Running ' + nodeType + ':' + nextNode[nodeType].name);
//    console.log('         ' + nextNode[nodeType].script);
    if(isArray(nextNode[nodeType].script)) {
     for(var i in nextNode[nodeType].script) {
      eval(nextNode[nodeType].script[i]);
     }
    } else {
     eval(nextNode[nodeType].script); 
    }
    break;
   case 'exclusiveGateway':
    break;
   case 'userTask':
    // creating task variables
    var dataInput = {};
    if( isArray( node.ioSpecification.dataInput)) {
     for(var i in node.ioSpecification.dataInput) {
      dataInput[node.ioSpecification.dataInput[i].id] = {name: node.ioSpecification.dataInput[i].name, value:null};
     }
    } else {
     dataInput[node.ioSpecification.dataInput.id] = {name: node.ioSpecification.dataInput.name, value:null};
    }
    var dataOutput = {};
    if( isArray( node.ioSpecification.dataOutput)) {
     for(var i in node.ioSpecification.dataOutput) {
      dataOutput[node.ioSpecification.dataOutput[i].id] = {name: node.ioSpecification.dataOutput[i].name, value:null};
     }
    } else {
     dataOutput[node.ioSpecification.dataOutput.id] = {name: node.ioSpecification.dataOutput.name, value:null};
    }
    console.log(dataInput);
    console.log(dataOutput);
    // Associate dataInput
    var dataInputAssociation = node.dataInputAssociation;
    if(dataInputAssociation != null) {
     function associateDataInput(o) {
      if(o.sourceRef != null) {
       dataInput[o.targetRef].value = processProperties[o.sourceRef];
      }
      if(o.assignment != null) {
       dataInput[o.targetRef].value = o.assignment.from['#text'];
      }
     }
     if(isArray(dataInputAssociation)) {
      for( var i in dataInputAssociation) {
       var o = dataInputAssociation[i];
       associateDataInput(o);
      }
     } else {
      var o = dataInputAssociation;
      associateDataInput(o);
     }
    }
    console.log('After association.');
    console.log(dataInput);
    
    function getDataInput(name) {
     for(var i in dataInput) {
      if(dataInput[i].name == name) {
       return dataInput[i].value;
      }
     }
    }
    eval(node.extensionElements['tns:onEntry-script'].script );
    // TODO write your own handler function to handle user task.
    // What if we want to map more than 1 value? --> use eval() to call a function.
    // How the user return more than one value? --> use object
    var userResponse = eval(getDataInput('Content'));
    console.log(userResponse);
    // map the response to dataOutput
    for(var i in dataOutput) {
     for(var j in userResponse) {
      if(dataOutput[i].name == j) {
       console.log('mapping userResponse.' + j + ' to dataOutput.' + i);
       dataOutput[i].value = userResponse[j];
      }
     }
    }
    console.log('After map response');
    console.log(dataOutput);
    // Associate dataOutput to processProperties
    var dataOutputAssociation = node.dataOutputAssociation;
    if(dataOutputAssociation != null) {
     function associateDataOutput(o) {
      if(o.sourceRef != null) {
       console.log('assigning value from ' + o.sourceRef + ' to ' + o.targetRef);
//       dataOutput[o.targetRef].value = processProperties[o.sourceRef];
       processProperties[o.targetRef] = dataOutput[o.sourceRef].value;
      }
      if(o.assignment != null) {
       dataOutput[o.targetRef].value = o.assignment.from['#text'];
      }
     }
     if(isArray(dataOutputAssociation)) {
      for( var i in dataOutputAssociation) {
       var o = dataOutputAssociation[i];
       associateDataOutput(o);
      }
     } else {
      var o = dataOutputAssociation;
      associateDataOutput(o);
     }
    }
    console.log(processProperties);
    
    // onExit script
    eval(node.extensionElements['tns:onExit-script'].script );
    break;
   case 'endEvent':
    console.log('reach end event.');
    return;
   }
   loopSequenceFlow(nextNode[nodeType].id);
  }
  
  loopSequenceFlow(startEvent.id);
  
 }

 $.ajax({
  type : "GET",
  url : "ScanCage.bpmn.xml",
  dataType : "xml",
  success : parseXml
 });

}

function dummyFunction1() {
 var answer =  prompt('What are you thinking??');
 var somevalue = '123456789';
 return {answer: answer, somevalue: somevalue};
}

dummyBPMNTest();
 


  
  
  
  
  

  

    
     
     
     
    
    
    
    
    
    
    

    
    
    
      
    
    
      
    
    
        
    
    
      
        
          
        
        
          
        
      
      
        
        
        
        
        
        
        
        
          _5_ContentInput
          _5_CommentInput
          _5_SkippableInput
          _5_TaskNameInput
          _5_ContentInput
          _5_PriorityInput
        
        
          _5_answerOutput
        
      
      
        userComms
        _5_ContentInput
      
      
        _5_CommentInput
        
          
          _5_CommentInput
        
      
      
        _5_SkippableInput
        
          true
          _5_SkippableInput
        
      
      
        _5_TaskNameInput
        
          Scan Cage
          _5_TaskNameInput
        
      
      
        _5_ContentInput
        
          dummyFunction1()
          _5_ContentInput
        
      
      
        _5_PriorityInput
        
          
          _5_PriorityInput
        
      
      
        _5_answerOutput
        answer
      
      
        
          #{actor}
        
      
    
    
    

    
    
    
      eval(true)
    
    
    
    
    
    
      
        processProperties.answer != '32123'
      
    

  

  
    
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
      
    
  

Wednesday, June 20, 2012

Installing s3fs on Mac

I'm not a Mac guy but was instructed to do so. Ran through a few web site, try to install pkg-config, fuse, glib, ... ... and end up with the simplest solution.

Step 1:
Install MacPorts from http://www.macports.org/

Step 2:
Install s3fs with MacPorts.
port install s3fs

Done.
Thanks to MacPorts