Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Thursday, January 3, 2013

A mobile device photo organizer (using OSGi)

When people think about OSGi applications they often think of complex server-side applications or embedded programs that are running as part of a set-top-box of some sort. Or they think of Eclipse-based RCP applications which are also based on OSGi.

When I started writing a little program to organize photos from the various mobile devices in my home it was unlike many of those applications. It was small, it had a (Swing-based) GUI and it wasn't running on the server side. Still using OSGi helped me enormously with the development. In this blog post I will discuss how.

The application
I'm sure I'm not the only person where the house is full of devices that can take photos. I like to store these pictures centrally on a NAS drive where they are neatly organized. Most devices come with a photo management solution of some sort, but they are often only working with a certain device and not with others and they mostly store the photos in different ways on disk. On top of that I want to keep the photos in a directory structure that looks like this: year/date-taken where I don't mean date-copied. All in all I couldn't find a solution that did this the way I wanted so I started a little tool for this over the holiday season.

While most of the actual work is done by headless processing logic, I needed a little GUI to kick it off. While there are tons of options available, I chose to create a simple Java Swing GUI, made to look nice with one of the awesome look and feels from the JTattoo guys.

While the project itself (which is available here) has the ASL2 license, I am using a variety of libraries to extract information from photo files, video files and for example to access my Android device, because I want to be able to copy photos directly from that.

I need modularization
Most of the libraries that I'm using have a license compatible with my ASL2 license, but I really wanted to access my Android phone directly. Android phones, as well as other mobile devices cannot easily be mounted as a directory on the file system. They need to be accessed using MTP. I found a library that allowed me to access my phone from Java: jusbpmp. It worked fine for the most part but there was one issue: it's GPL-licensed. I don't want to get into which open source license is better, but the viral effects of GPL are well known and I already decided that my application was to be ASL2-licensed. I didn't want to relicense my whole project because of the fact that one dependency has this other license.

If I could single out the functionality that uses this library and only license that piece GPL (as needed) and then plug it into the rest of my application that would limit the scope and the amount of code that is required to have the GPL license. Ideally I want that component to be loosely coupled so that it can be downloaded separately and plugged into the main application.

OSGi Bundles to the rescue. And in particular OSGi Services! OSGi Services use a contribution model where service implementations contribute them to the Service Registry. Consumers find them there. It all works by defining the Service APIs in a separate module that they all communicate through.

The MTP library allows me to access the files on my Android phone. So I started by defining an API module to create an Iterable that can give me entries that serve the photo information from anything any type of source. The PhotoIterable interface can represent a file directory-based photo store, but also one that comes from an MTP device or other device. It looks somewhat like this:

public interface PhotoIterable extends Iterable<PhotoIterable.Entry> {
  /**
   * Get a human readable identification of the location.
   * @return The location string.
   */
  String getLocationString();

  /**
   * This class represents a photo object that can be read.
   */
  public interface Entry {
    /**
     * Returns the file name to use for the photo.
     * @return The file name without path information, for example IMG_01429.JPG
     */
    String getName();

    /**
     * @return The stream to read the photo bytes from.
     */
    InputStream getInputStream();
  }
}

Now I need a way to obtain such a PhotoIterable. Typically the user wants to select a location on the device to copy the photos from (instead of getting all the image files on the device) and to do this  I defined the PhotoSource interface. This is what I will register in the OSGi Service Registry. For every supported type of source a corresponding service will be registered. The core bundle ships with one for loading photos from a file system directory, and the phototools.mtp bundle registers one to handle MTP devices.

public interface PhotoSource {
  /**
   * The label for this source, for example 'File System Directory' or 'Android'.
   * @return The label to use.
   */
  String getLabel();

  /**
   * Calling this method should open a selection window where the user can select where the
   * photos are to be copied or downloaded from.
   * @return A PhotoIterable to obtain photos from the selected location.
   */
  PhotoIterable getPhotoIterable();
}

At this stage I have 3 bundles: the API bundle, the core implementation bundle and the MTP implementation bundle.


This separation is nice because:
  • The API bundle is small. Anyone who wants to write support for another mobile device has only a very small number of interfaces to look at. No distracting implementation code to get in the way.
  • I can find all the PhotoSource implementations by looking them up in the OSGi Service Registry, for example with bundleContext.getServiceReferences(PhotoSource.class, null))
  • I can contribute support for additional devices without changing the rest of the code. Just add the bundle that contains the support (and registers the PhotoSource service) and it will appear (even the GUI will react to this).
  • I didn't have to write my own plugin mechanism. OSGi Bundles and Services provide that to me.
  • I could isolate the functionality that depends on a GPL library in a separate bundle. This means that the main application is still ASL2 licensed. The GPL-based bundle is optional and can be provided separately if I want to create a pure ASL2-based product.
The PhotoSource that the MTP provide bundle contributes is visible as a widget in the main screen (the Mobile Device via USB Radio Button). When I click Select I can see the custom MTP selection GUI in action:

More OSGi Bundles and Services to keep things clean
While the phototools.core bundle can deal with extracting metadata from some of the Photo formats (thanks to Drew's metadata-extractor) I also want my application to handle movie files. I found another nice library that could handle mp4 files for me: Sebastian's mp4parser. Although mp4parser is ASL2 licensed I didn't want to include it the core bundle because it was getting fairly heavy on embedded libraries and also there were still a number of photo/video formats unsupported. Getting the core bundle to support them all didn't seem the right thing to do. Allowing separate bundles to contribute a format handler did! So I defined an additional Service API:

public interface PhotoMetadataProvider {
  /**
   * Get metadata for a photo or video file
   * @param f The file to process.
   * @return The metadata found.
   */
  Metadata getMetaData(File f);

  public interface Metadata {
    /**
     * Obtain the date the photo was taken (not necessarily the file date).
     * @return The date taken.
     */
    Date getDateTaken();

    /**
     * Obtain a small preview file for the photo or movie. If available,
     * the preview file will always be a JPEG file.
     * @return The preview file.
     */
    File getPreviewFile();
  }
}

I can find an appropriate Metadata Provider for my photos by looking up one from the OSGi Service Registry that is registered for the relevant extension. This is done by using OSGi Service Registration properties. Each Photo Metadata Provider registers the formats it can handle with the format property. Then I can look one up by querying on the extension of the file I want to process e.g:
  ServiceReferences[] refs = bundleContext.getServiceReferences(
    PhotoMetadataProvider.class, "(format=.jpeg)");
When I find a service that can handle my format, get it to process the file:
  PhotoMetadataProvider p = bundleContext.getService(sref);
  Metadata metadata = p.getMetaData(myPhotoOrVideoFile);
  Date dateTaken = metadata.getDateTaken();

The mp4 handling code is now nicely separated in its own bundle. In addition, if I ever want to add support for other formats (.AVI for example) I can do this by simply adding another bundle, which drastically reduces the scope of my changes and also reduces the amount of code that I may have to look at.

Conclusion? Well my little project is not finished yet, it's still work in progress. But OSGi really helped me by providing a nice plug-in architecture and its modularity almost forced me to write nice interface-based components which will be easier to maintain in the long run. Because the bundles have a clear scope they tend to be quite small and when making changes the amount of code you have to look at as a developer is much smaller than if this was part of a monolithic application. This is great because I generally only sporadically have time to go back to my hobby projects and having less code to refresh my brain is good :) Oh, and the fact that I'm using OSGi is completely hidden to the end user. It's really just an architectural choice under the covers.

Happy new year, everyone.

Thursday, June 17, 2010

Eek! Java reflection can cause NoClassDefFoundError after 15 calls

Now this came as a bit of a surprise to me. Reading this thread on the Felix mailing list...

I can't believe this: when using reflection after about 15 calls, the internal behaviour of the Sun JRE changes. With the changed behaviour comes in a package dependency on the sun.reflect package! When running under OSGi you are very much in charge of what comes in via the classloader so a new package that comes in over the same code path after about 15 calls is bad news - you really want to know your package dependencies up front.
The solution is obviously to import that sun.reflect package in your affected bundle but how are you supposed to know? Let's say you have a test suite that properly tests your code and everything is looking nice. I would imagine that a testsuite doesn't test every method call 15 times to make sure its still ok. Moreover, how do we know it's 15? There could be another optimization lurking in the JRE that requires 300 calls in order for it to be exposed.
Another problem with importing sun.reflect is that it's implementation specific. Do other implementations have a similar issue?

Here's a message to all JVM implementors: optimize all you can but please don't change the package dependency graph after a number of invocations. At the very least make your implementation fail fast, not after x method calls.

There is a bug for this at sun: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6265952
Accepted in 2005 but still open...

Tuesday, July 7, 2009

An IRC alerter written using Apache Camel and Java

IRC is a great tool if you have a bunch of people working on the same thing in different locations. One of the biggest problems I found with it is that I sometimes miss pings. I don’t always have the IRC window open, and although the client I use most (Trillian) has nice popups in the corner, I still sometimes miss a ping, simply because I’m away from my computer. I know there are some other IRC clients that can send you special alerts, but I just happen to like Trillian, so I didn’t want to abandon it just because I was missing one little thing...

While being annoyed with this for a little while, I came across the Apache Camel IRC component. Great - that should sort me out. I’ll just simply write a Camel route that connects to the IRC channel and sends me an alert of some sort when someone pings me.

After running the Camel Maven archetype for Java projects:
>mvn archetype:generate -DarchetypeGroupId=org.apache.camel.archetypes -DarchetypeArtifactId=camel-archetype-java -DarchetypeVersion=2.0-SNAPSHOT -DarchetypeRepository=https://repository.apache.org/content/groups/snapshots-group
I was able to get this going fairly quickly! The archetype creates a Maven Camel project for you with an example route in it. You can run it now with:

mvn install camel:run

Next step: write a Camel route that reads my IRC messages for me.
For this I’m simply using a URL in Camel to connect to the IRC server, e.g. I’m using the following to connect to the CXF IRC channel at irc.codehaus.org:
irc://my_user@irc.codehaus.org/#cxf?nickname=my_user
Once logged in you simply receive all the messages sent over the channel, in the process() method of your Camel route, so I can configure my route something like this:
public void configure() {
  String url =
    "irc://my_user@irc.codehaus.org/#cxf?nickname=my_user";
  from(url).process(new Processor() {
    public void process(Exchange ex) throws Exception {
      String msg = ex.getIn().toString();
      System.out.println(msg);
    }
  }
}

The process() callback gets called whenever a message is sent over the IRC channel, so now we can do something with it!

This is where I started experimenting :) I wrote a little generic IRC client that can connect to any number of IRC channels and searches any messages coming in for a certain text string.
If there is a match, it opens a dialog on my screen that will stay there until I click it away, now I don’t miss it any more! I’m also using the new Java 6 tray icon functionality, to show a little popup balloon.




I put the code (under the Apache license) in SVN over here: http://coderthoughts.googlecode.com/svn/trunk/irc-alerter. No built executables yet, but you can simply check it out and run it with the following command:
mvn install camel:run -Dirc.alerter.channels=c:\path\to\channels.xml
since it’s pure java, it should work on mac, linux etc too :) I used Maven 2.0.9 and Java 6.

The format of the channels.xml file is as follows:

<channels>
 <match string="davidb"/> <!-- the text you want to match -->
 <password file="c:/davidb/etc/camel-irc.pwd"/>
 <channel name="cxf" user="my_user" nick="my_user" host="irc.codehaus.org"/>
 <channel name="somechannel" user="davidb" nick="davidb_camel" host="some_host" port="6667"/>
</channels>

So this one matches any message that contains 'davidb'. Obviously you’d put your match in there.
For each channel you specify the channel name, username, nickname, hostname and optionally the port. You probably want to use a different nick name than the one you use from your ordinary IRC client.
The passwords for the various servers are in a separate file, which you can put in an encrypted directory in your file system. These are properties files, your camel-irc.pwd file could look like this:

my_user@irc.codehaus.org=somepass
someuser@somehost=someotherpass

This has been a lot of fun to do. I’m still amazed how easy it was to create this app using Camel, and I’ll never miss an IRC ping ever again! (Well, I hope).

Source code - can be checked out from Subversion here: http://coderthoughts.googlecode.com/svn/trunk/irc-alerter

Wednesday, November 26, 2008

A Web Site Concentrator to ease AJAX development

Every now and then I do a little AJAX development and there is one thing that I always find frustrating during the development of AJAX apps: The browser's single-origin policy.

Now I agree that for non-development cases this policy is absolutely justified, but during development it's a big PITA.
Let's say you are developing an AJAX app that calls into a back-end app over XML and you want to debug both. Like in my case I have a little GWT app that invokes on a Distributed OSGi Service running on the back end in CXF over port 9000. Ultimately you'd deploy it all over a single server, so there wouldn't really be a problem in production, but during development my GWT app is running on http://localhost:8888/org.coderthoughts.dinner.DinnerClient while my CXF back end runs on http://localhost:9000/org/apache/cxf/dosgi/samples/springdm/DinnerService

If I try to invoke my back-end app in this setup my AJAX app complains:


Since your front-end and back-end are probably written in different languages chances are that they have different development and debugging environments, which makes adhering to the single-origin hard to do. Besides, you don't want to go through the hassle of rebuilding your .WAR files (or whatever format you might use) for every single change. I know MSIE has some options to alleviate this, but they don't always work. Also using JSON as a data format gives you some options to bypass the problem, but that only works if your data format is JSON. In my case it's XML.

Enter ProxyServlet. There's a few of these around, but they're generally a bit raw. I particularly liked the one from Frank Spieleck because it has no dependencies, so is simple to use.
Typically these ProxyServlets forward requests from one context to another location, so to build the multi-site concentrator that you need to make the browser think both of them come from the same source you need at least two of them. In my case I need the following proxy settings:

  • org.coderthoughts.dinner.DinnerClient => http://localhost:8888/org.coderthoughts.dinner.DinnerClient

  • org => http://localhost:9000/org

To ease this task I wrote a little commandline wrapper around Frank's ProxyServlet that simply generates a .WAR file with the right name to set the root context, the correct bits in the web.xml etc... So I generate 2 .WAR files:

java -jar genwebproxy.jar \
  org.coderthoughts.dinner.DinnerClient \
  http://localhost:8888/org.coderthoughts.dinner.DinnerClient
  Created: org.coderthoughts.dinner.DinnerClient.war

java -jar genwebproxy.jar org http://localhost:9000/org
  Created: org.war
and simply deploy them into Tomcat by copying them into the webapps directory. These are standard .WAR files so you can deploy them into any Webapp container. I've also put the PAX-WEB headers in, in case you want to deploy them into that.

Et voilĂ . It works!


Note that I access my AJAX app using the Tomcat port of 8080 instead of the GWT debug port of 8888. I did have to change the port number at with my webapp makes an invocation from 9000 to 8080 as well, but that's easy.

With Frank's permission I put the ProxyServlet plus my wrapping code for the genwebproxy.jar file in SVN here: http://coderthoughts.googlecode.com/svn/trunk/GenWebProxy

You can download the binary from here, the command line is:
java -jar genwebproxy.jar [context_root] [real_location]