Saturday, July 21, 2012

Dynamic Classes with AngularJS

Here's a solution someone posted: http://jsfiddle.net/rur_d/tNZAm/

Some say it's bad idea to have your controllers being aware of classes. Well it's a solution that works.

Document Ready Event When Using AngularJS With JQuery

When you are making a page out of multiple partials/fragments in AngularJS, the document ready event for JQuery seems to fire before the page is fully assembled.

The correct way to do something when the page is fully loaded in AngularJS is by using the '$viewContentLoaded' event listener, by putting the following code into your controller.
$scope.$on('$viewContentLoaded', function(){
// do something
});

Reference: http://www.aleaiactaest.ch/angular-js-and-dom-readyness-for-jquery/

AngularJS: Calling a Javascript Function in the Controller

This is done by using "ng-click".

Example assuming you already created a function in the controller called "doSomething":
<a href ng-click="doSomething()">Do It</a>

Friday, July 20, 2012

Jersey Exception Handling

Jersey offers centralized Exception handling with "ExceptionMappers". This means you can allow Exceptions to be thrown up to the framework and then you can define how to handle them. It also means you are able to "catch" and handle exceptions that occur before and after the code that you write is being run.

 Let's say you want to handle "MyWebServiceException", you can create an ExceptionMapper "MyWebServiceExceptionMapper" like this:
@Provider
public class MyWebServiceExceptionMapper implements ExceptionMapper<MyWebServiceException> {

@Override
public Response toResponse(Exception e) {
GenericEntity<String> entity = new GenericEntity<String>(
   “{\"statusCode\":\"ERR-100\",\"responseData\":\"Error Processing Web Service\"}” ){};
return Response.status(Status.OK).entity(entity).type(MediaType.APPLICATION_JSON).build();
}
}

In order for this to be registered with Jersey, you'll need to put it in a package that's being scanned by Jersey to be resources or providers, so in the web.xml, have something like this:
<init-param>
   <param-name>com.sun.jersey.config.property.packages</param-name>
   <param-value>com.myws.resource;com.myws.provider</param-value>
</init-param>

In this example, I separated the packages for resources and providers, providing 2 packages to be scanned.

To handle each exception differently, just create as many ExceptionMapper classes as you wish, and put them in the same package to be picked up and registered.

You can also use this to return a different HTTP status code for different errors, but as mentioned in the comments of an earlier POST, that's not going to work well with cross-domain Javascript REST requests.

Thursday, July 19, 2012

Jackson Polymorphism

You can handle polymorphism with Jackson using the following annotations at the class level:
j@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "accountType")
@JsonSubTypes({
   @Type(value = AdminAccount.class, name =”ADM”),
   @Type(value = NormalAccount.class, name =”NML”)
})

This will tell Jackson to map the object to the "AdminAccount" class when the "accountType" property is "ADM", and to the "NormalAccount" class when the "accountType" property is "NML".

Other options are available, such as directly using the class name.

One "gotcha" I discovered is that the "accountType" property must not be implemented in the POJO class. If there is an "accountType" property in the class, it will be null when it's time to work with it in Java. In Java-land, you'll have to use "instanceof" to know what object it is.

A article with more detailed examples: http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html

Wednesday, July 18, 2012

HTTP Status Codes for REST

For REST services, one common pattern is to use HTTP status codes as the status code for the request itself.

A good reference: http://restpatterns.org/HTTP_Status_Codes

ProGuard: Read Obfuscated Stack Traces

You'll need to run retrace.jar.
java -jar retrace.jar proguard.map stacktrace.txt
You'll also need proguard.jar in the same folder.
Link: http://proguard.sourceforge.net/index.html#manual/retrace/examples.html

Sunday, July 15, 2012

iOS: AudioQueue Get Audio Level

A very good example of this is found in the "SpeakHere" reference project provided by Apple:
Link: http://developer.apple.com/library/ios/#samplecode/SpeakHere/Introduction/Intro.html

Just some gotcha's and points to note:

You need to call AudioQueueSetProperty to enable the level metering feature in the first place:
UInt32 enableMetering = 1;
AudioQueueSetProperty(audioQueue, kAudioQueueProperty_EnableLevelMetering, &enableMetering, sizeof(UInt32));

The number returned by the AudioQueue will be a negative number with a maximum value of 0. To convert it to a number on a linear scale (i.e. gain level), use the following formula:
float level = pow(10., 0.05 * averagePower);

Where averagePower is the value returned by the AudioQueue for the audio level average power.

The rest should be pretty straight-forward from studying the example.

Links and References:

Mongo DB: Java API Get Object by ID

You'll get an error if you try to do something along the lines of "{_id: xxxxx}".

This is how it's done:
BasicDBObject searchCriteria = new BasicDBObject("_id", new ObjectId(id));
DBObject dbObj = collection.findOne(searchCriteria);

Jackson: Ignore Unrecognized Fields

By default, Jackson will throw an exception when a field in the JSON is not declared as a member variable in the class it is mapped to. To ignore these fields, add the following annotation to the POJO class:
@JsonIgnoreProperties(ignoreUnknown=true)

AngularJS: Batarang Debugging Tool

Recently, a debugging tool call Batarang was released for AngularJS. It runs as a Google Chrome extension.

Here's a blog about it:
http://blog.angularjs.org/2012/07/introducing-angularjs-batarang.html

Installation instructions:
https://github.com/angular/angularjs-batarang/blob/master/README.md

Things to note:
  1. It doesn't run on the stock Google Chrome. It runs on the "Canary" version which has experimental features for early adopters. You'll have to download, install, and run it as a separate program.
  2. Installing the extension the usual way won't work due to security restrictions. You'll have to drag the "crx" file into the extensions page (i.e. chrome://chrome/extensions/).

iOS: Making the Table Cell Background a Gradient

This is a good guide for doing it:

http://www.raywenderlich.com/2033/core-graphics-101-lines-rectangles-and-gradients

GUID Uniqueness

This is an article explaining how GUID can be almost certainly (if not totally certainly unique).

Link: http://blogs.msdn.com/b/oldnewthing/archive/2008/06/27/8659071.aspx

This is done by including the following information in the GUID:
  1. Machine ID (network card Mac address)
  2. Timestamp
  3. Counter to differentiate entries generated within the same timestamp
  4. Algorithm version identifier
This is the "Version 1" algorithm for GUID generation. There are other versions which rely more on random number generators. I'm not sure if those are as reliable as version 1 in terms of guaranteeing uniqueness, and will be the topic of future research.

MongoDB: Scaling Approach

The first step in scaling is creating Replica sets (master/slave) where the slaves are allowed to service queries. This will be helpful for read-mostly workloads.

After that, the next step is sharding, where you'll need to define a partitioning mechanism. At a very large scale, the topology would be that of multiple shards, within which contain individual replica sets.

Link: http://www.mongodb.org/display/DOCS/Sharding+Introduction

Mongo DB Object ID

It is the unique identifier for each document in a collection. Compared to UUID, it is smaller (12 vs 16 bytes) but should still be workable because the requirement is just to have uniqueness within the DB cluster.

Uniqueness is guaranteed by including the machine ID, process ID, a timestamp and a counter for entries generated within the same second.

Link: http://www.mongodb.org/display/DOCS/Object+IDs

Saturday, July 14, 2012

Git: Conflict Resolution By Choosing A Copy

Often, it is as simple as choosing either the local copy in its entirety or the remote copy in its entirety.

In this case, use the following commands:
  1. Use local copy: git checkout --ours FILENAME
  2. Use remote copy: git checkout --theirs FILENAME
Reference: http://gitready.com/advanced/2009/02/25/keep-either-file-in-merge-conflicts.html

Thursday, July 12, 2012

iOS Activity Indicator

This is how it is done (example is using WebView)
Link: http://stackoverflow.com/questions/11334247/add-activity-indicator-to-web-view

I made some further changes so the indicator is more visible (by putting it inside an alert popup):

- (void)viewDidLoad
{
    [super viewDidLoad];    
    loadingAlert = [[UIAlertView alloc] initWithTitle:@"Loading..."
                                                      message:@"\n"
                                                     delegate:self
                                            cancelButtonTitle:nil
                                            otherButtonTitles:nil, nil];

    activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
    [loadingAlert addSubview:activityIndicator];
}

- (void) showLoadingAlert
{
    [loadingAlert show];    
    activityIndicator.frame = CGRectMake((x,y,w,h);
    [activityIndicator startAnimating];
}
- (void) dismissLoadingAlert
{
    [activityIndicator stopAnimating];
    [loadingAlert dismissWithClickedButtonIndex:0 animated:NO];
}

Note that I set the frame after "show". This is because if I wanted to calculate x,y,w,h relative to the size of the loading alert frame, these values will only be non-zero after show.

NSTimer Gotchas With Threads/RunLoops

Sometimes when you notice that the NSTimer isn't working (i.e. the scheduled task doesn't fire), it can be caused by runloop issues.

By default, it runs in the current run loop from where it is scheduled. If you schedule it from the wrong loop, it might not fire as the loop could be blocked somewhere else.

Which method you use to initialize the timer also makes a difference. The "scheduledTimerWithTimeInterval" creates a timer that runs in the current loop. If the current loop is not suitable for the timer to run, the timer may not fire and you have no idea why.

If you have a problem with the above, you will have to use the "timerWithTimeInterval" methods. However, you have to remember to add the timer to the run loop that you want to add on.

For example, to schedule a timer on the main loop from another loop:

NSTimer *timer = [NSTimer timerWithTimeInterval:waiTime target:self selector:@selector(fireMethod:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]

The various behaviors are documented in the class reference.

Reference: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nstimer_Class/Reference/NSTimer.html

Wednesday, July 11, 2012

iOS: Positioning a View Relative to Parent

This is done via the "frame" property of the View, e.g.

viewObj.frame = CGRectMake(20,20,50,40);

Tuesday, July 10, 2012

MySQL: Dumping With Users and Privileges

A gotcha when trying to clone an entire MySQL DB is that the user privileges are not completely cloned.

The following command would have resulted in users and privileges being cloned (since the "mysql" database would have been dumped), but they do not take effect until a "FLUSH PRIVILEGES" command has been issued:

mysqldump --all-databases > db.sql

This means additional manual step of issuing the "FLUSH PRIVILEGES" command is needed before the DB is usable.

In order to ensure that the "FLUSH PRIVILEGES" command is run during DB restore (so no additional steps needed to get DB up and running), include the "--flush-privileges" option during the dump.

mysqldump --all-databases --flush-privileges > db.sql

Apache Wicket: AjaxSubmitLink No Response

What happens is when you click the AjaxSubmitLink, there is no response, no error messages, and you don't know why there is a problem.

This is potentially a gotcha caused by "setRequired(true)" in your form elements. The form will refuse to act (and won't tell you why) when you leave a required field blank.

Monday, July 9, 2012

MongoDB Connection Pooling

It seems the Java Driver handles this already and there is no need for any additional engineering for this:

Link: http://www.mongodb.org/display/DOCS/Java+Driver+Concurrency

Jackson: From Object to JSON String and Back

This is how it's done:
ObjectMapper jsonMapper = new ObjectMapper();
String jsonStr = jsonMapper.writeValueAsString(jsonObj);

And the other way round:
ObjectMapper jsonMapper = new ObjectMapper();
UserAccount result = jsonMapper.readValue(userAccountJson,  UserAccount.class);

MongoDB: Save Raw JSON With Java API

In MongoDB's Java API, most of the functions relating to saving objects into the DB requires you to either provide a map or add each property one by one. In situations where you already have the JSON document (e.g. submitted via Javascript), you'd want to just directly save the JSON document.

To do this in MongoDB:
DBObject dbObject = (DBObject) JSON.parse(jsonStr);
Where the "JSON" class is "com.mongodb.util.JSON".

Reference: http://www.mkyong.com/mongodb/java-mongodb-convert-json-data-to-dbobject/

Sunday, July 8, 2012

Bootstrapping Jersey With POJO / JSON Mapping

Jersey is the JAX-RS reference implementation by Sun / Oracle. The bootstrapping process is relatively straightforward. It is done by declaring Servlet(s) to handle the Jersey requests.

The basic configuration (without POJO / JSON mapping) can be found here: http://www.mkyong.com/webservices/jax-rs/jersey-hello-world-example/

To add POJO / JSON mapping:
  • Ensure the following jar files are on classpath:
    • asm
    • jackson-core-asl
    • jackson-jaxrs
    • jackson-mapper-asl
    • jackson-xc
    • jersey-core
    • jersey-json
    • jersey-servlet
    • jersey-server
  • web.xml should look something like this:

<servlet>
<servlet-name>WsServlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
    <param-name>com.sun.jersey.config.property.packages</param-name>
    <param-value>com.xxxxx.ws</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>WsServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

Git: Configuring Default Pull/Push Remote Branch

While this is automatically done when you clone from a remote repository, you'll have to configure this yourself when you're the one who shared the repository/branch in the first place.

You'll know you have this problem when you see this error message:

> git pull
You asked me to pull without telling me which branch you
want to merge with, and 'branch.master.merge' in
your configuration file does not tell me, either.


The solution is to add the following (e.g. master branch) to the ".git/config" file.

[branch "master"]
       remote = origin
       merge = refs/heads/master

MongoDB Basics: Database Creation

In MongoDB, databases are lazily created when the first insert is performed on a database after the "use xxxx" command is issued.
It works similarly with collections. It is lazily created upon the first insert.

Saturday, July 7, 2012

Javascript: Convert Object to String

Use the JSON.stringify function i.e.

JSON.stringify(obj);

Note: Does not work on IE6 and 7

Java Servlet Get Post Body

This is how to get the content of the Post body in a HTTP Request:

protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
       BufferedReader reader = req.getReader();
       StringBuilder inputStringBuilder = new StringBuilder();
       String line = null;
       while ((line = reader.readLine()) != null) {
               inputStringBuilder.append(line);
       }
}

Thursday, July 5, 2012

Apache Mina: Reading Binary Data

The "messageReceived" callback returns an Object that represents the message. When the data sent is in raw binary form, you'll need to cast the object to an IOBuffer before you can read it.

Reference: http://stackoverflow.com/questions/9916617/how-to-read-binary-data-from-socket-with-apache-mina

Wednesday, July 4, 2012

MySQL Logging Queries

SQL statements sent to MySQL is logged in the general log file. By default, this log file is disabled. To switch it on, insert into my.cnf the following lines:

general_log=1
general_log_file=/var/log/mysql-general.log

Note: you will need to make sure the file/folder is writable by the user MySQL is running on.

MySQL Table Name Case Sensitivity


When running MySQL in Linux, the default behavior is for table names to be case-sensitive during table name lookups. This can sometimes cause problems, especially if the developer of an application developed on Mac OS or Windows, where the default behavior can be different.

In order to control MySQL case-sensitive behavior, use the "lower_case_table_names" parameter in the my.cnf file. There are 3 possible values:

0: Case sensitive. Table names are stored on disk using the lettercase specified during creation. Name comparisons are case sensitive. Requires case-sensitive filesystem.

1: Case insensitive. Table names are stored in lowercase on disk. MySQL converts all table names to lowercase on storage and lookup.

2: Case insensitive. Table names are stored on disk using the lettercase specified during creation, but MySQL converts them to lowercase on lookup. Name comparisons are not case sensitive. Use only on case insensitive filesystems.

For Linux, choose "lower_case_table_names =1" if you want case insensitive behavior.

Reference: http://dev.mysql.com/doc/refman/5.5/en/identifier-case-sensitivity.html

Monday, July 2, 2012

Hibernate: Capturing Log Entries Under Log4J

Ever since Hibernate migrated to SLF4J, debug entries from within Hibernate no longer shows up in Log4J log files by default.

In order to see Hibernate log entries in the Log4J log files, you'll need to include the "slf4j-log4j12-1.6.4.jar" file into the classpath.

Hibernate: Composite Primary Key Mapping

Here's how to do it:

<composite-id name="id" class="com.xxx.xxx.xxx.KeyClass">
       <key-property name="keyProp1" column="key1" />
       <key-property name="keyProp2" column="key2" />
</composite-id>

Use this in place of the usual "id" tag.

Hibernate: Cache Invalidation Between 2 Separate Apps

Assuming the scenario where you have an admin server managing the database entries and an app server servicing user requests, and the app server is using EHCache for caching, it is possible for the Hibernate/EHCache instances to send cache invalidation messages to each other. This is a particularly useful setup as the entities in the app server might be best configured as "read-only" while the same entity in the admin server might be configured as "read-write".

For the admin server to successfully invalidate the cache on the app servers, just configure both apps to be part of the same JGroups cluster, and take note of the following issue here, and it should work fine. The classes on both ends have to be compatible of course,

Hibernate EHCache: Cache Invalidation Unable to Find SessionFactory

Using Hibernate 3.6.10 and EHCache 2.5.1, I encountered the following error in the log (in the receiving server) when a cache invalidation message was replicated from one node to another.

failed calling receive() in receiverjava.lang.IllegalArgumentException: java.lang.NullPointerException
       at org.jgroups.Message.getObject(Message.java:291)
       at net.sf.ehcache.distribution.jgroups.JGroupsCacheReceiver.
receive(JGroupsCacheReceiver.java:62)
       at org.jgroups.JChannel.up(JChannel.java:1490)
       at org.jgroups.stack.ProtocolStack.up(ProtocolStack.java:1074)
<<<<<<<<<<<<<<<<<<<<< SNIPPED >>>>>>>>>>>>>>>>>>>>>>>
       at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
       at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.NullPointerException
       at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:768)
       at org.hibernate.impl.SessionFactoryObjectFactory.getNamedInstance
(SessionFactoryObjectFactory.java:159)
       at org.hibernate.impl.SessionFactoryImpl.readResolve(SessionFactoryImpl.java:753)
       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25)

<<<<<<<<<<<<<<<<<<<<< SNIPPED >>>>>>>>>>>>>>>>>>>>>>>
       at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)
       at org.jgroups.util.Util.objectFromByteBuffer(Util.java:398)
       at org.jgroups.Message.getObject(Message.java:288)

The cause of this is Hibernate not being able obtain the SessionFactory instance in the receiving server. In Hibernate, it tries to obtain the SessionFactory instance using the UUID. Since this UUID was being generated randomly in both nodes, there is no chance of both UUIDs being the same. This lookup will therefore fail.

However, the fallback mechanism is to lookup using the JNDI name of the SessionFactory, which I did not set. The solution is to set the JNDI name using the "hibernate.session_factory_name" property on both nodes. After I have done that, this problem doesn't happen anymore and replication/cache invalidation is successful.

Hibernate EHCache: ClassCastException in Cluster

I have 2 Hibernate/EHCache nodes in a cluster (managed by JGroups), and after I've done some transactions, I encounter the following error:

java.lang.ClassCastException: java.util.HashMap cannot be cast to org.hibernate.cache.entry.CacheEntry

The reason for this is that one node had "hibernate.cache.use_structured_entries" set to "true" while the other node didn't have this setting configured (presumably set to "false" by default).

After ensuring that both nodes had "hibernate.cache.use_structured_entries" set to "true", this error no longer occurred.