A possible answer within the OSGi context is probably a well managed Bundle Repository. Well managed means that it should have up-to-date machine and human read-able information available about what is inside the repository.
Now, basically your repository will be a directory on some harddisk, and the information you need is already in the bundles. The task may be visualized as follows:

Building and Ant Task
I have been working with Apache Ant as a build tool since a long time, and I still prefer it over other possibilities, because it's really versatile. Usually all I needed was provided right out of the box or by some task you could find searching, but this time it didn't seem to be the case. Thus, I decided to get some hands on excercise in writing the task myself.
I found all I needed to get started quickly in Writing Ant Tasks. The rest I put together from the API documentation and experience with Java (which came in handy for a very specific problem I will discuss later).
There are about four basic elements to writing an Ant task:
- the right imports
- extending Task and knowing which members are useful
- understanding how attributes can be set to make things configurable
- understanding how to use your task in a build
Lets attack them one by one.
The right imports
Actually all I needed to import where the most basic classes from Ant that you will use in almost any Task you write:
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;
That's the exception to be thrown if something goes wrong (you can nest the cause), the Task class that we will extend, and the Project class, that will provide us some basic functionality and access to build properties (if required).
Extending Task
I decided to call the class GenerateRepositoryDescriptor, so all it needs is a proper class definition
and to implement the execute() method that will execute your task implementation when the task is
called from a build file.
public class GenerateRepositoryDescriptor extends Task {
public void execute()
throws BuildException {
}//execute
}//class GenerateRepositoryDescriptor
That's actually all, not really complicated :)
Probably the most important member you will need is project that is a Project (the one that gives you access to project properties and basic functionality).
Enabling build attributes
Next you may need some attributes that make your task configurable. Again this is really simple. All you need are standard Java bean modifiers. In my example, I decided that I need three attributes:
- dir: the directory that holds the Bundle JARs
- outfile: the path for the repository descriptor file (XML/OBR)
- name: the repository name to be used in the descriptor
In code, this translates to (javadoc omitted):
public void setDir(String dir) {
m_Repository = project.resolveFile(dir);
}//setDir
public void setOutfile(String outfile) {
m_RepositoryDescriptor = project.resolveFile(outfile);
}//setOutfile
public void setName(String name) {
m_RepositoryName = name;
}//setName
Pretty straightforward I would say. The only special thing in the code is the fact that we use the project member of the task to resolve filenames (see the API docs for more details).
Using the task in a build
This requires two essential steps:
- Packaging the task into a jar file and dropping it into your ANT_HOME/lib directory.
- Defining the task and calling it in you build file:
<taskdef name="genrepdesc" classname="net.coalevo.ant.GenerateRepositoryDescriptor" />
[...]
<target name="generate" description="Generates the bundle repository descriptor (OBR/XML).">
<genrepdesc dir="jars" outfile="repository.xml" name="Coalevo Bundle Repository" />
</target>
Try it out with some simple log statements, it works just perfect:
public void execute()
throws BuildException {
project.log(this, m_Repository.toString(),Project.MSG_INFO);
project.log(this, m_RepositoryDescriptor.toString(),Project.MSG_INFO);
project.log(this, m_RepositoryName,Project.MSG_INFO);
}//execute
Doing the real work
Now, what is left is the logic to process the bundle JARs and write the repository descriptor. I won't reproduce all of the details here, rather I will concentrate on two important elements:
- Extracting the Manifest information from a JAR
- Handling OSGi R4 localized bundles
Extracting Manifest Information
The simplest way to do this is to resolve each bundle:
//list all jar files in the repository
String[] jars = m_Repository.list(new FilenameFilter() {
public boolean accept(File dir,String name) {
return (m_Repository.equals(dir) && name.endsWith(".jar"));
}//accept
});
//iterate over them
for(int i = 0; i < jars.length; i++) {
generateDescriptor(new File(m_Repository,jars[i]),pw);
}
and then access it programmatically through the java.util.jar API:
protected void generateDescriptor(File f,PrintWriter pw) throws Exception {
[...]
//Get Manifest
JarFile jf = new JarFile(f);
Manifest mf = jf.getManifest();
Attributes attr = mf.getMainAttributes();
[...]
}//generateDescriptor
All it needs then is to resolve the right attribute values, normally using
Object o = attr.getValue(key);
where the keys point to the OSGi related attributes we are interested in (e.g. Bundle-Name, Bundle-Version etc.) when generating the repository descriptor. However, there is a small pitfall here. OSGi R4 defines a new mechanism for localization, that allows some of these attributes to be localized. It makes sense, given that it is information that we pass to the user, however, it complicates our task a little bit.
Handling localized Manifest values
The basic knowledge required are two elements from the OSGi R4 specifications:
- Localized variables are prefixed with a %
- Localized strings are to be stored in property files in the OSGI-INF/l10n directory, and should be resolved similar to the way it works for java.util.PropertyResourceBundle with the basename bundle
With this information and some Java experience the problem can be resolved quickly:
//1. Prepare a URLClassLoader that has access to the JAR file
URL[] urls = new URL[1];
urls[0] = new URL("jar:" + f.toURL() + "!/");
URLClassLoader cl = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());
//2. Use a resource bundle, but pass our custom class loader
ResourceBundle rb = ResourceBundle.getBundle("/OSGI-INF/l10n/bundle",Locale.getDefault(),cl);
All that is missing is to add a routine that automates resolving if possible/required
public String getValue(ResourceBundle rb, Attributes attr, String key) {
//1. Try to get Attribute value
Object o = attr.getValue(key);
if(o==null) {
return "unspecified";
}
//2. transform
String val = o.toString();
if(val.startsWith("%") && rb !=null) {
val = rb.getString(val.substring(1));
}
return val;
}//getValue
and use
getValue(rb,attr,"Bundle-Name")
instead of
att.getValue("Bundle-Name").
Transformation
All that is left is a word on transformation. I won't start with details about XSL, if you need something to get started from you may use the one we use for the coalevo bundle repository (available from the same directory as repository.xsl;also demonstrates the result of all the above). However, I just wanted to show you how you can use Ant to generate a HTML version at the same time:
<xslt in="repository.xml" out="index.html" style="repository.xsl" />
That's it ! :)


0 comments:
Post a Comment