Oscar Bundle Repository

Release version: 1.1.3 - October 3, 2005 ( changes )

Overview

The goal of the Oscar Bundle Repository (OBR) is two-fold:

  1. To simplify deploying and using available bundles with Oscar.
  2. To encourage independent bundle development so that communities of interest can grow separately from Oscar.

OBR achieves the first goal by providing a service that can automatically install a bundle, with its deployment dependencies, from a bundle repository. This makes it easier for people to experiment with existing bundles. The second goal is achieved by raising the visibility of the available bundles and providing access to both the executable bundle and its source code. In the past, all bundle source code was packaged with Oscar, but this did not allow for independent release cycles. Further, it confused end users because it appeared as if all bundles were part of Oscar, which they are not. Hopefully, by making OBR and the bundles themselves more visible, community members will be encouraged to provide or improve service implementations.

Note: OBR provides access to the bundles hosted on the OBR web site by default, but you can also use it to deploy your own bundles by creating a bundle repository meta-data file for your local bundles; see the orb urls command for more details.

Approach

For the most part, OBR is quite simple. There is no "repository server" for OBR, all functionality resides on the client side. OBR provides its functionality by reading an XML-based meta-data file that describes the bundles available to it; the meta-data file, by default, is located on the OBR web site. The meta-data file contains a list of bundles along with information for each that is nearly identical to the OSGi bundle manifest information; the next section describes the meta-data in more detail. From the meta-data, OBR is able to construct dependency information for installing and updating bundles.

One important piece of meta-data is the Bundle-UpdateLocation; this is a standard OSGi bundle manifest attribute that OBR uses to uniquely identify a bundle. For example, if you use OBR to perform an update on a locally installed bundle, OBR gets its update location and searches the repository meta-data for the same location. If the matching update location is found, then OBR tries to update the local bundle, otherwise it stops. Some form of unique key is necessary, since there must be a means to match locally installed bundles to remotely available bundles. A benefit of this approach is that it enables multiple bundle versions to co-exist in a repository at one time; for example, it is possible to have a development version and a stable version of the same bundle available under different update locations.

The following pseudo-Java code depicts OBR's generic deployment algorithm:


    public void deploy(String updateLocation)
    {
        // If the bundle is already installed locally,
        // then try to update it.

        if (isLocallyInstalled(updateLocation))
        {
            if (isRemoteUpdateAvailable(updateLocation))
            {
                deployDependencies(updateLocation);
                update(updateLocation);
            }
        }

        // Else if the bundle is not installed locally, then
        // try to install it.

        else
        {
            deployDependencies(updateLocation);
            install(updateLocation);
        }
    }
    
    public void deployDependencies(String updateLocation)
    {
        Package[] pkgs = getDependencies(updateLocation);
        for (int i = 0; i < pkgs.length; i++)
        {
            if (!isLocallyResolvable(pkgs[i]))
            {
                String resolveUpdateLocation = findResolvingBundle(pkgs[i]);
                deploy(resolveUpdateLocation);
            }
        }
    }
    
    public String findResolvingBundle(Package pkg)
    {
        // Get all candidate bundles in the remote repository that
        // provide a compatible package version. From these candidates,
        // select a single bundle such that if one of the candidates is
        // an update to a locally installed bundle choose it, otherwise
        // choose the first matching candidate.
    }

The above pseudo code is not intended to contain all aspects of the deployment algorithm; for example, it is missing aspects related to starting bundles and disabling dependency resolution. Still, it provides a useful glimpse of the overall process. This algorithm appears simple at first glance, but it is actually somewhat complex. This is due to the nature of deploying independently developed bundles. For example, in an ideal world, if an update for a bundle is made available, then updates for all of the bundles satisifying its dependencies are also made available. Unfortunately, this may not be the case, thus the deployment algorithm might have to install new bundles during an update to satisfying either new dependencies or updated dependencies that can no longer be satisfied by existing local bundles. In response to this type of scenario, the OBR deployment algorithm tries to favor updating existing bundles, if possible, as opposed to installing new bundles to satisfy dependencies. This approach is described in the findResolvingBundle() method in the pseudo-code above.

From the user's perspective, OBR's functionality is accessed indirectly, since OBR is a service API and not a user interface. For interactive access to OBR, a command service is provided for Oscar's shell service; this command service is accessible via shell server user interfaces, such as Oscar's text-based or GUI-based shells. The OBR shell command is discussed below, but first the bundle meta-data is described in the next section.

Bundle Meta-Data

OBR uses an XML-based repository file of bundle meta-data. The meta-data can be divided into three groups: required, human readable, and not currently used.

The following meta-data attributes are required by OBR for its functionality:

The following meta-data attributes are for human consumption:

The following meta-data attributes are not currently used, although future versions of OBR may use of them:

The following XML snippet shows the meta-data for OBR itself:


    <bundle>
        <bundle-name>Bundle Repository</bundle-name>
        <bundle-description>
            A bundle repository service for Oscar.
        </bundle-description>
        <bundle-version>1.0.0</bundle-version>
        <bundle-updatelocation>
            http://oscar-osgi.sf.net/repo/bundlerepository/bundlerepository.jar
        </bundle-updatelocation>
        <bundle-sourceurl>
            http://oscar-osgi.sf.net/repo/bundlerepository/bundlerepository-src.jar
        </bundle-sourceurl>
        <bundle-docurl>
            http://oscar-osgi.sourceforge.net/repo/bundlerepository/index.html
        </bundle-docurl>
        <bundle-category>General</bundle-category>
        <import-package package="org.osgi.framework"/>
        <import-package package="org.ungoverned.osgi.service.shell"
            specification-version="1.0.0"/>
        <export-package package="org.ungoverned.osgi.service.bundlerepository"
            specification-version="1.0.0"/>
    </bundle>

OBR Shell Command

Besides providing a service API, OBR implements an Oscar shell command for accessing its functionality. For the end user, the OBR shell command is accessed using the text-based or GUI-based user interfaces for Oscar's shell service. This section describes the syntax for the OBR shell command.

obr help

Syntax:


    obr help [urls | list | info | install | deploy | start | update | source]

This command is used to display additional information about the other OBR commands.

obr urls

Syntax:


    obr urls [<repository-file-url> ...]

This command gets or sets the URLs to the repository files used by OBR. If no arguments are specified, then the current repository URLs are retrieved. Specify a space-delimited list of URLs to change the repository URLs. Each URL should point to a file containing meta-data about available bundles in XML format. The default repository file URL is http://oscar-osgi.sf.net/repo/repository.xml. You can also change the default repository URLs by defining the oscar.repository.url property and giving it the value that you desire; the Oscar usage document describes how to configure bundle properties.

obr list

Syntax:


    obr list [<string> ...]

This command lists bundles available in the bundle repository. If no arguments are specified, then all available bundles are listed, otherwise any arguments are concatenated with spaces and used as a substring filter on the bundle names.

obr info

Syntax:


    obr info <bundle-name>[;<version>] ...

This command displays the meta-data for the specified bundles. If a bundle's name contains spaces, then it must be surrounded by quotes. It is also possible to specify a precise version if more than one version exists, such as:


    obr info "Bundle Repository";1.0.0

The above example retrieves the meta-data for version "1.0.0" of the bundle named "Bundle Repository".

obr deploy

Syntax:


    obr deploy [-nodeps] <bundle-name>[;<version>] ... | <bundle-id> ...

This command tries to install or update the specified bundles and all of their dependencies by default; use the "-nodeps" switch to ignore dependencies. You can specify either the bundle name or the bundle identifier. If a bundle's name contains spaces, then it must be surrounded by quotes. It is also possible to specify a precise version if more than one version exists, such as:


    obr deploy "Bundle Repository";1.0.0

For the above example, if version "1.0.0" of "Bundle Repository" is already installed locally, then the command will attempt to update it and all of its dependencies; otherwise, the command will install it and all of its dependencies.

obr install

Syntax:


    obr install [-nodeps] <bundle-name>[;<version>] ...

This command installs the specified bundles and all of their dependencies by default; use the "-nodeps" switch to ignore dependencies. If a bundle's name contains spaces, then it must be surrounded by quotes. If a specified bundle is already installed, then this command has no effect. It is also possible to specify a precise version if more than one version exists, such as:


    obr install "Bundle Repository";1.0.0

The above example installs the version "1.0.0" of the bundle named "Bundle Repository" and its dependencies.

obr start

Syntax:


    obr start [-nodeps] <bundle-name>[;<version>] ...

This command installs and starts the specified bundles and all of their dependencies by default; use the "-nodeps" switch to ignore dependencies. If a bundle's name contains spaces, then it must be surrounded by quotes. If a specified bundle is already installed, then this command has no effect. It is also possible to specify a precise version if more than one version exists, such as:


    obr start "Bundle Repository";1.0.0

The above example installs and starts the "1.0.0" version of the bundle named "Bundle Repository" and its dependencies.

obr update

Syntax:


    obr update -check
    obr update [-nodeps] <bundle-name>[;<version>] ... | <bundle-id> ...

This command updates the specified locally installed bundles and all of their dependencies by default; use the "-nodeps" switch to ignore dependencies. You can specify either the bundle name or the bundle identifier. If a bundle's name contains spaces, then it must be surrounded by quotes. If a specified bundle is not already installed, then this command has no effect. It is also possible to specify a precise version if more than one version exists, such as:


    obr update "Bundle Repository";1.0.0

The above example updates version "1.0.0" of the bundle named "Bundle Repository" and its dependencies. The update command may install new bundles if the updated bundles have new dependencies.

obr source

Syntax:


    obr source [-x] <local-dir> <bundle-name>[;<version>] ...

This command retrieves the source archives of the specified bundles and saves them to the specified local directory; use the "-x" switch to automatically extract the source archives. If a bundle name contains spaces, then it must be surrounded by quotes. It is also possible to specify a precise version if more than one version exists, such as:


    obr source /home/rickhall/tmp "Bundle Repository";1.0.0

The above example retrieves the source archive of version "1.0.0" of the bundle named "Bundle Repository" and saves it to the specified local directory.

Using OBR with a Proxy

If you use a proxy for Web access, then OBR will not work for you in its default configuration; certain system properties must be set to enable OBR to work with a proxy. These properties are:

These system properties can be set directly on the command line when starting the JVM using the standard "-D<prop>=<value>" syntax or you can put them in the lib/system.properties file of your Oscar installation; see documentation on configuring Oscar for more information.

Bundle Source Packaging

Besides making it easier to experiment with available bundles, OBR also provides easy access to bundle source code, which is important because the best way for people to contribute and learn is by poking around in the source. Bundle source code is packaged in a way such that each bundle is a completely self-contained project, but it can also be integrated into the overall Oscar build process. By using the obr source -x command, it is possible to download and extract bundle source code directly into Oscar's src-bundle/ directory for immediate integration into Oscar's build process. To get a deeper understanding of Oscar's build process, refer to Oscar's build document. In short, Oscar's build process allows you to build only Oscar, only bundles, or to build both. To achieve this, bundle source archives are created according to a few simple rules.

The rules for a bundle source archive are as follows:

As long as these rules are followed when creating a bundle source archive, then it will be possible to build bundles independently or integrate them into Oscar's overall build process by placing it into the src-bundle/ directory in Oscar's installation directory. As an example, consider an imaginary bundle project, called Foo, with the following directory structure:


   foo/
      LICENSE.txt
      build.xml
      classes/
      doc/
      embeddedlib/
      lib/
      src/
         org/
            mydomain/
               foo/
                  manifest.mf
                  Activator.java
                  ...

Given the above bundle project directory structure, the following is a build.xml file for Ant that conforms to the above rules:


<project name="foo" default="all" basedir=".">
                                                                                
    <!-- Set global properties. -->
    <property name="bundle.name" value="foo"/>
    <property name="src.dir" value="src"/>
    <property name="lib.dir" value="lib"/>
    <property name="embeddedlib.dir" value="embeddedlib"/>
    <property name="output.dir" value="classes"/>
    <property name="bundle.dir" value="."/>
    <property name="doc.dir" value="doc"/>
    <property name="apidoc.dir" value="${doc.dir}/api"/>
    <property name="dist.dir" value="."/>
    <property name="debug.flag" value="on"/>
                                                                                
    <!-- Create class path from lib and output directories. -->
    <path id="classpath">
        <pathelement location="${output.dir}"/>
        <fileset dir="${lib.dir}">
            <include name="*.jar"/>
        </fileset>
        <fileset dir="${embeddedlib.dir}">
            <include name="*.jar"/>
        </fileset>
    </path>
                                                                                
    <!-- Create output directory. -->
    <target name="init">
        <mkdir dir="${output.dir}"/>
    </target>
                                                                                
    <!-- Compile and JAR everything -->
    <target name="all" depends="init">
        <antcall target="compile"/>
        <antcall target="jar"/>
    </target>
                                                                                
    <!-- Compile everything. -->
    <target name="compile" depends="init">
        <javac srcdir="${src.dir}" destdir="${output.dir}"
         debug="${debug.flag}" verbose="no" deprecation="no">
            <classpath refid="classpath"/>
            <include name="**/*.java"/>
        </javac>
    </target>
                                                                                
    <!-- JAR the bundle. -->
    <target name="jar" depends="compile">
        <!-- Copy embedded libs into output directory -->
        <copy todir="${output.dir}/org/mydomain/${bundle.name}">
            <fileset dir="${embeddedlib.dir}">
                <include name="*.jar"/>
            </fileset>
        </copy>

        <!-- JAR the output directory -->
        <jar manifest="${src.dir}/org/mydomain/${bundle.name}/manifest.mf"
            jarfile="${bundle.dir}/${bundle.name}.jar"
            basedir="${output.dir}">
            <include name="**"/>
        </jar>
    </target>
                                                                                
    <!-- Create the source distribution JAR file. -->
    <target name="dist">
        <!-- Create API doc directory. -->
        <mkdir dir="${apidoc.dir}"/>

        <!-- Generate API documentation. -->
        <javadoc sourcepath="${src.dir}"
                 packagenames="*"
                 destdir="${apidoc.dir}"
                 author="true"
                 windowtitle="${bundle.name} API Documentation"/>

        <!-- JAR the source and doc trees. -->
        <jar jarfile="${dist.dir}/${bundle.name}-src.jar" basedir="..">
            <include name="${bundle.name}/LICENSE.txt"/>
            <include name="${bundle.name}/build.xml"/>
            <include name="${bundle.name}/${embeddedlib.dir}/**"/>
            <include name="${bundle.name}/${lib.dir}/**"/>
            <include name="${bundle.name}/${doc.dir}/**"/>
            <include name="${bundle.name}/${src.dir}/**"/>
        </jar>
    </target>
                                                                                
    <!-- Clean up everything. -->
    <target name="clean">
        <delete dir="${output.dir}"/>
        <delete dir="${apidoc.dir}"/>
        <delete file="${bundle.dir}/${bundle.name}.jar"/>
        <delete file="${dist.dir}/${bundle.name}-src.jar"/>
    </target>
                                                                                
</project>

The above build.xml file is simple, but applicable to a wide variety of bundle projects. It works by first defining a set of properties that it will use, including the properties that will be overwritten by passed in values from Oscar's build process (e.g., bundle.dir and dist.dir). It next defines a file set for the compilation class path. The init target creates the output directory for the generated classes. The all target is listed as the default target in the very first line and will compile and JAR the bundle. The compile actually performs the compile, while the jar target actually JARs the bundle. By default, the bundle JAR is named "${bundle.name}.jar" (foo.jar in the example) and is placed in the "." directory; if the build is invoked by Oscar, then it will be placed in Oscar's bundle directory. The dist target creates a source archive JAR file that conforms to the above rules. By default, the source archive JAR file is named "${bundle.name}-src.jar" (foo-src.jar in the example) and is placed in the "." directory; if the build is invoked by Oscar, then it will be placed in Oscar's distribution directory. Lastly, the clean target deletes all generated output, including the bundle and source archive JAR files.

The above directory structure and build.xml file are representative of how all source archives in OBR are structured. While this exact structure is not required, the above example serves as a good template for those wishing to put their bundles in OBR or just wanting a reasonable structure for their own bundle projects.

Feedback

If you have comments or suggestions, feel free to contact me at heavy@ungoverned.org.

Richard S. Hall