Posts‎ > ‎

osgi-run Tutorial - run your Java/Kotlin/Frege code in OSGi

posted Jun 5, 2016, 10:20 AM by Renato Athaydes   [ updated Oct 22, 2017, 1:48 AM ]

What you'll learn in this tutorial:

Introduction


osgi-run is a Gradle plugin that makes using OSGi as easy as using Gradle.

To get started with this tutorial, you need to have Java and Gradle installed. It is assumed you have basic knowledge of both.

Some very basic OSGi knowledge is also assumed.


To make it easy for you to follow along, a GitHub project was created to back this tutorial.

Instead of creating the files described below, you can simply checkout the appropriate git branch and run and modify the code as you wish.

The git branch at each stage of the tutorial is shown in bold, blue font, as in the following example where master is the relevant branch:

git branch: master 

This is just an optional convenience. If you don't know git or prefer to create everything yourself, it's fine as well.

All boxes where the command starts with a $ sign are shell commands.

Note: osgi-run is constantly being updated with new features and bug fixes! So, everywhere you see  id "com.athaydes.osgi-run" version "1.5.0" , make sure to replace this version with the latest version shown below:




So, let's get started!

Run an OSGi container

Create a new Gradle project called osgi-run-tutorial from the command line:

$ mkdir osgi-run-tutorial

If you're going to use the tutorial GitHub project, clone the project instead:

$ git clone https://github.com/renatoathaydes/osgi-run-tutorial

Enter the new directory:

$ cd osgi-run-tutorial

Now, create a Gradle build file (called build.gradle) that applies the osgi-run plugin and adds the JCenter repository to fetch resources from:

git branch: master

plugins {
id "com.athaydes.osgi-run" version "1.5.0"
}

group 'osgi-run-tutorial'
version '1.0'

repositories {
jcenter()
}

From the command line, run the createOsgiRuntime Gradle task (which can be abbreviated to createOsgi, or even crO):

gradle createOsgi

This task, as you would expect, creates an OSGi environment that you can run.

Because you have not specified anything much in the build file, this environment will consist of the default osgi-run environment (as of version 1.5.0):

build/osgi

├── [        170]  bundle

│   ├── [      52560]  org.apache.felix.gogo.command-0.16.0.jar

│   ├── [     125027]  org.apache.felix.gogo.runtime-0.16.2.jar

│   └── [      54420]  org.apache.felix.gogo.shell-0.12.0.jar

├── [        102]  conf

│   └── [        157]  config.properties

├── [        342]  run.bat

├── [        347]  run.sh

└── [        102]  system-libs

    └── [     690862]  org.apache.felix.main-5.4.0.jar

3 directories, 7 files


As you can see, the default environment includes 3 bundles (the Apache Felix Gogo bundles, which enable a basic OSGi shell that allows you interact with the OSGi environment), a configuration file for the OSGi framework (don't worry about that as this file is created automatically by osgi-run based on the properties you set in the Gradle build file) and a single system lib, the OSGi framework implementation, which by default is Apache Felix.

The number between square brackets is the file size in bytes.
Notice that the whole OSGi environment with a few bundles takes only around 1.4MB.

The OSGi environment is ready to run, so choose the right run script for your OS (run.sh for Mac and Linux, run.bat for Windows) and run it:

$ chmod +x build/osgi/run.sh

$ build/osgi/run.sh

____________________________

Welcome to Apache Felix Gogo


g! 


From the Gogo shell, you can type lb to list the installed bundles, or help to see which commands are available. Type help <command> to see help for a particular command.

To shut down the OSGi environment, type stop 0 (which stops the bundle with ID 0, which is always the system bundle).

If you want to run Eclipse Equinox instead, add this at the end of your build file:

git branch: use-equinox

runOsgi {
configSettings = 'equinox'
programArgs = '-console'
}

This will instruct osgi-run to use Equinox instead of Felix and will start Equinox with the -console option, which means that instead of using the Gogo shell, we will use the Equinox built-in shell.

If you start the Equinox container and type ss, you should see this:

$ build/osgi/run.sh


osgi> ss


Framework is launched.


id State       Bundle

0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106


osgi> 


For the rest of this tutorial, it does not matter which container you use, as everything will work exactly the same with either, but the examples will use Felix. You can even try Knopflerfish, another OSGi container, if you want. See the Knopflerfish sample in the osgi-run tests if you are interested.


Create a Java OSGi bundle and run it

As you can see, running a OSGi container is pretty easy, but what you are probably interested in doing is writing some code and getting it to run in OSGi.

Before we do that, let's consider what options OSGi gives us to bootstrap our application (if you already know OSGi well, feel free to skip this small detour).


Bootstrapping an application in OSGi

A regular Java application is bootstrapped by the JVM invoking the main method of the class given as an argument to the java command, or when using the java -jar command, the Main-Class entry in the jar's manifest file.

In OSGi, the class that gets bootstrapped by the JVM is not the application class, but the framework class. For example, here's the main method if you use Felix.

An OSGi application is composed of one or more bundles. A bundle may not run any code at all at startup and simply export some packages for use by other bundles. Some bundles, conversely, may not export any package at all, but provide services to other bundles (which may, in turn, require services from other bundles to work). The closest OSGi has to a main method is the BundleActivator interface. It allows a bundle to run code when it gets started and when it gets stopped. Notice that OSGi bundles may be started/stopped at any time while the framework continues to run.

However, the preferred way to run code in OSGi is by using one of the services layer specifications - Declarative Services or Blueprint (if you're writing a web application, OSGi also has WABs - Web Application Bundles).

Blueprint is a full Dependency Injection solution inspired by Spring, while Declarative Services is a little simpler, allowing us to use a basic service-oriented architecture.

Both DS and Blueprint allow developers to use an XML descriptor or annotations to declare their services.

We will use DS annotations in this tutorial.


In this tutorial, we will use Declarative Services annotations to bootstrap our application.

To make use of Declarative Services, we need to add a few things to the Gradle build file (added lines are bold and have a comment-number at the end):


git branch: hello-world


plugins {
id "java"
id "com.athaydes.osgi-run" version "1.5.0"
id "org.dm.bundle" version "0.8.4" // 1
}

group 'osgi-run-tutorial'
version '1.0'

repositories {
jcenter()
}

dependencies {
compile "org.osgi:org.osgi.service.component.annotations:1.3.0" // 2
osgiRuntime 'org.apache.felix:org.apache.felix.scr:2.0.2'
// 3
}

runOsgi {
bundles += project // 4
}

bundle {
instruction '-dsannotations', '*' // 5
}


Explanation of the added lines (numbers in comments):

  1. apply the org.dm.bundle Gradle plugin to turn the java project into a bundle.
  2. add a dependency to the OSGi Declarative Services annotations jar (so we can annotate the Java code later).
  3. instruct osgi-run to deploy the Felix SCR (Service Component Runtime) bundle to the OSGi container.
  4. tell osgi-run to use all the default bundles (which include the gogo command-line interface for OSGi) plus this project as a bundle.
  5. instruct bnd (via the org.dm.bundle plugin) to use DS annotations.


Create a Java class at src/main/java/osgirun/tutorial/Hello.java:



package osgirun.tutorial;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;

@Component( immediate = true )
public class Hello {

@Activate
public void start() {
System.out.println( "Hello osgi-run" );
}

}


The @Component annotation identifies the Hello class as a OSGi service. The immediate argument tells the SCR that this service must be started immediately when the container starts up. The default is false, which means that the Hello class should only start when some other service required it.

When the service is started, the OSGi container calls the @Activate method.

If everything goes well, we should see the following output when we start the container again:


$ gradle clean createOsgi

$ chmod +x build/osgi/run.sh && build/osgi/run.sh

____________________________

Welcome to Apache Felix Gogo


g! Hello osgi-run


Congratulations! You've just written an OSGi service.

I hope to write a more in-depth DS tutorial in the future, but for now, let's continue by showing how you can use an existing OSGi bundle.


Use a third-party OSGi bundle


osgi-run makes using a third-party bundle as easy as adding a dependency in the build file.

Add the following line to the dependencies block in the Gradle build file:


git branch: use-third-party-bundle


compile "com.google.guava:guava:19.0"


Let's also change the Hello class to use something from Guava and make sure that it works:


package osgirun.tutorial;

import com.google.common.collect.ImmutableList;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;

@Component( immediate = true )
public class Hello {

@Activate
public void start() {
System.out.println( "Hello osgi-run " + ImmutableList.of( 'J', 'A', 'V', 'A' ) );
}

}


Re-compiling and running the container again, you should see that everything works as expected:

$ gradle clean createOsgi

$ chmod +x build/osgi/run.sh && build/osgi/run.sh

____________________________

Welcome to Apache Felix Gogo


g! Hello osgi-run [J, A, V, A]


Use a Java library which is not a OSGi bundle


One of the most common sources of frustration when you start using OSGi is to find out that not all Java libraries you might like are OSGi-ready. This means that if you try to use them in OSGi, you'll be faced with errors that you most likely have no previous experience with, and might not have enough understanding to solve (hence the frustration).

With osgi-run, the chances of that happening are much smaller because osgi-run will automatically wrap non-OSGi jars into OSGi bundles. By default, everything in the jar will be exported and available for use by other bundles, except code inside packages called internal. But you can customise how the wrapping should occur (for example, you can specify which packages should be exported, as you'll see in the next sections).

Let's suppose we want to use weather information in our application. The Yahoo Weather API is a great source of weather information... but to avoid having to consume a HTTP API directly, which can be cumbersome, let's use a Java library which does that for us: yahoo-weather-java-api.

Notice that this library is not a OSGi bundle.

To depend on this library, we add a normal Gradle dependency in the build file (you can replace the Guava dependency):

git branch: use-non-bundle


compile 'com.github.fedy2:yahoo-weather-java-api:2.0.0'


Change the Hello class to use the library:


package osgirun.tutorial;

import com.github.fedy2.weather.YahooWeatherService;
import com.github.fedy2.weather.data.Channel;
import com.github.fedy2.weather.data.unit.DegreeUnit;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;

@Component( immediate = true )
public class Hello {

@Activate
public void start() {
try {
YahooWeatherService service = new YahooWeatherService();
Channel channel = service.getForecastForLocation( "Stockholm, Sweden", DegreeUnit.CELSIUS )
.first( 1 ).get( 0 );

int temperature = channel.getItem().getCondition().getTemp();
String conditions = channel.getItem().getCondition().getText();

System.out.println( "Weather in Stockholm now: " +
temperature + "C, " + conditions );
} catch ( Exception e ) {
e.printStackTrace();
}
}

}


Re-compile and run the container again:


$ gradle clean createOsgi

$ chmod +x build/osgi/run.sh && build/osgi/run.sh

____________________________

Welcome to Apache Felix Gogo

g! Weather in Stockholm now: 15C, Partly Cloudy


It should work perfectly.

If you run the gradle command with the --info option:

$ gradle --info createOsgi


You will see that osgi-run quietly wrapped a couple of jars into bundles:

Wrapping non-bundle: yahoo-weather-java-api-2.0.0.jar

No instructions provided to wrap bundle yahoo-weather-java-api-2.0.0.jar, will use defaults

Wrapping non-bundle: animal-sniffer-annotations-1.9.jar

No instructions provided to wrap bundle animal-sniffer-annotations-1.9.jar, will use defaults


The first one is, as expected, the yahoo-weather-java-api jar. The second one, animal-sniffer-annotations, is a transitive dependency from the Felix SCR bundle.

You'll see how to remove the unnecessary animal-sniffer dependency and customise the wrapping of the yahoo-weather-java-api jar in the next section.


Analyse transitive dependencies and customise jars wrapping


You can see all your dependencies (and you should regularly check it to make sure no unintended libraries are included without your knowledge) by running the Gradle dependencies task (only relevant output shown):

$ gradle dependencies

:dependencies


------------------------------------------------------------

Root project

------------------------------------------------------------

...

compile - Compile classpath for source set 'main'.

+--- org.osgi:org.osgi.service.component.annotations:1.3.0

\--- com.github.fedy2:yahoo-weather-java-api:2.0.0

     +--- org.slf4j:slf4j-api:1.7.21

     \--- org.slf4j:slf4j-simple:1.7.21

          \--- org.slf4j:slf4j-api:1.7.21

...

osgiRuntime

\--- org.apache.felix:org.apache.felix.scr:2.0.2

     \--- org.codehaus.mojo:animal-sniffer-annotations:1.9



We can now see that by adding a dependency on yahoo-weather-java-api, we inadvertently imported the sfl4j-api and slf4j-simple jars.

Given the wide usage of the slf4j-api, you're likely to end up with more than one version of it in your dependency tree. That's fine and will work in OSGi as each bundle will get the correct version, unlike in a regular Java application, where whichever version gets loaded first wins (a non-deterministic process).

That's probably ok, but the animal-sniffer jar most likely shouldn't be in the OSGi runtime (it is a Maven Plugin, after all!).

To remove it, use Gradle as you would normally do:

git branch: dependencies-and-wrapping

osgiRuntime 'org.apache.felix:org.apache.felix.scr:2.0.2', {
transitive = false
}


Recompile, start the container, then type lb to list all installed bundles:

$ chmod +x build/osgi/run.sh && build/osgi/run.sh

____________________________

Welcome to Apache Felix Gogo


g! Weather in Stockholm now: 15C, Partly Cloudy

lb

START LEVEL 1

   ID|State      |Level|Name

    0|Active     |    0|System Bundle (5.4.0)|5.4.0

    1|Active     |    1|Apache Felix Gogo Command (0.16.0)|0.16.0

    2|Active     |    1|Apache Felix Gogo Runtime (0.16.2)|0.16.2

    3|Active     |    1|Apache Felix Gogo Shell (0.12.0)|0.12.0

    4|Active     |    1|Apache Felix Declarative Services (2.0.2)|2.0.2

    5|Active     |    1|osgi-run-tutorial (1.0.0)|1.0.0

    6|Active     |    1|slf4j-api (1.7.21)|1.7.21

    7|Resolved   |    1|slf4j-simple (1.7.21)|1.7.21

    8|Active     |    1|yahoo-weather-java-api (2.0.0)|2.0.0



Better, animal-sniffer is gone. But let's see what the OSGi metadata for the wrapped bundle looks like... we can see above that the ID of the yahoo-weather-java-api bundle is 8. To see its metadata, type headers 8:


g! headers 8

yahoo-weather-java-api (8)

--------------------------

Archiver-Version = Plexus Archiver

Bnd-LastModified = 1465142842856

Build-Jdk = 1.8.0_77

Built-By = fedy2

Bundle-ManifestVersion = 2

Bundle-Name = yahoo-weather-java-api

Bundle-SymbolicName = yahoo-weather-java-api

Bundle-Version = 2.0.0

Created-By = 1.8.0_60 (Oracle Corporation)

Export-Package = com.github.fedy2.weather;uses:="com.github.fedy2.weather.data,com.github.fedy2.weather.data.unit,javax.xml.bind",com.github.fedy2.weather.binding;uses:="com.github.fedy2.weather.data,javax.xml.bind",com.github.fedy2.weather.binding.adapter;uses:="com.github.fedy2.weather.data.unit,javax.xml.bind.annotation.adapters",com.github.fedy2.weather.data;uses:="com.github.fedy2.weather.data.unit,javax.xml.bind.annotation",com.github.fedy2.weather.data.unit

Import-Package = com.github.fedy2.weather.binding,com.github.fedy2.weather.binding.adapter,com.github.fedy2.weather.data,com.github.fedy2.weather.data.unit,javax.xml.bind,javax.xml.bind.annotation,javax.xml.bind.annotation.adapters,org.slf4j

Manifest-Version = 1.0

Originally-Created-By = Apache Maven 3.0.5

Require-Capability = osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.6))"

Tool = Bnd-3.2.0.201605172007


The Export-Package clause is usually what you need to worry about. Should this bundle be exporting com.github.fedy2.weather.binding. To be honest, I am not sure, but as we're not currently using that, let's suppose that this is an internal package we should not allow our application to use.

To make the generated bundle stop exporting this package, add the following block inside the runOsgi block in the Gradle build file (notice that, for documentation, we also add the Bundle-Description entry):



wrapInstructions {
// use regex to match file name of dependency
manifest( "yahoo-weather-java-api.*" ) {
// export everything except the binding package and sub-packages
instruction 'Export-Package', '!com.github.fedy2.weather.binding*', '*'
instruction 'Bundle-Description', 'The Yahoo Weather Java API is used to provide weather data.'
}
}


Compile and update a bundle without stopping the OSGi container


This time, instead of doing gradle clean, just keep the OSGi environment running and, from a different shell, run only the createOsgi task... 

$ gradle createOsgi


Nothing will happen in the OSGi container just yet... you need to tell the OSGi container to update the bundle we've modified.. from the previous steps, we know the ID of the yahoo-api bundle is 8, so type update 8 in the gogo command line (the ID number may be different for you!):


g! update 8


To confirm that the bundle was updated, run headers 8 again:


g! headers 8


yahoo-weather-java-api (8)

--------------------------

Archiver-Version = Plexus Archiver

Bnd-LastModified = 1465143665713

Build-Jdk = 1.8.0_77

Built-By = fedy2

Bundle-Description = The Yahoo Weather Java API is used to provide weather data.

Bundle-ManifestVersion = 2

Bundle-Name = yahoo-weather-java-api

Bundle-SymbolicName = yahoo-weather-java-api

Bundle-Version = 2.0.0

Created-By = 1.8.0_60 (Oracle Corporation)

Export-Package = com.github.fedy2.weather;uses:="com.github.fedy2.weather.data,com.github.fedy2.weather.data.unit,javax.xml.bind",com.github.fedy2.weather.data;uses:="com.github.fedy2.weather.data.unit,javax.xml.bind.annotation",com.github.fedy2.weather.data.unit

Import-Package = com.github.fedy2.weather.data.unit,javax.xml.bind,javax.xml.bind.annotation,javax.xml.bind.annotation.adapters,org.slf4j

Manifest-Version = 1.0

Originally-Created-By = Apache Maven 3.0.5

Private-Package = com.github.fedy2.weather.binding,com.github.fedy2.weather.binding.adapter

Require-Capability = osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.6))"

Tool = Bnd-3.2.0.201605172007

 

As you can see, the Export-Package entry now has no binding packages (and there's a Private-Package entry containing that!) and the new Bundle-Description is visibile.

Because code reloading in OSGi is so great, let's change the Hello class one more time, just so we can see that we can re-compile and update our bundle while the application continues to run!

Let's allow the user to enter a location, instead of hard-coding one:

git branch: refresh-code

package osgirun.tutorial;

import com.github.fedy2.weather.YahooWeatherService;
import com.github.fedy2.weather.data.Channel;
import com.github.fedy2.weather.data.unit.DegreeUnit;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;

import java.util.Scanner;

@Component( immediate = true )
public class Hello {

@Activate
public void start() {
System.out.println( "Enter a location:" );
String location = new Scanner( System.in ).nextLine();

try {
YahooWeatherService service = new YahooWeatherService();
Channel channel = service.getForecastForLocation( location, DegreeUnit.CELSIUS )
.first( 1 ).get( 0 );

int temperature = channel.getItem().getCondition().getTemp();
String conditions = channel.getItem().getCondition().getText();

System.out.println( "Weather in " + location + " now: " +
temperature + "C, " + conditions );
} catch ( Exception e ) {
e.printStackTrace();
}
}

}


Again, make sure to not run the clean task! Just run gradle createOsgi one more time in the other shell, then update the osgi-run-tutorial bundle (whose ID, as you can see from the previous time we ran lb, is 5 on this run, but may be something else for you - check it again):


g! update 5


Enter a location:

Paris, France

Weather in Paris, France now: 17C, Cloudy



To try another location, just restart the bundle:


g! stop 5

g! start 5

Enter a location:

New York

Weather in New York now: 20C, Scattered Showers


Using Kotlin and OSGi


The next step in this tutorial is to show that you are not limited to Java when you want to use OSGi.

Let's see how to turn the Hello World Java code into Kotlin code, and then run it in OSGi.

The first thing to do is to replace the Java plugin with Kotlin's in the build file and add Kotlin as a dependency (notice that Kotlin has a special OSGi artifact, the standard jar does not work in OSGi).

Note: the buildscript block is required to declare the Kotlin plugin, instead of doing it in the newer plugins block, because Kotlin is not yet published on the Gradle Plugins repository.

git branch: use-kotlin

buildscript {
ext.kotlin_version = '1.0.2'

repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

plugins {
id "com.athaydes.osgi-run" version "1.5.0"
id "org.dm.bundle" version "0.8.4"
}

apply plugin: 'kotlin'

group 'osgi-run-tutorial'
version '1.0'

repositories {
jcenter()
}

dependencies {
compile "org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlin_version"
compile "org.osgi:org.osgi.service.component.annotations:1.3.0"
osgiRuntime 'org.apache.felix:org.apache.felix.scr:2.0.2'
}

runOsgi {
bundles += project
}

bundle {
instruction '-dsannotations', '*'
}


Here's the initial Hello class written in Kotlin (if you're using IntelliJ, let it do it for you automatically), with a small change to prove the Kotlin code is actually running:


package osgirun.tutorial

import org.osgi.service.component.annotations.Activate
import org.osgi.service.component.annotations.Component

@Component(immediate = true)
class Hello {

@Activate
fun start() {
println("Hello osgi-run ${listOf('k', 'o', 't', 'l', 'i', 'n')}")
}

}


Now we can run our nicer Kotlin code in OSGi:


$ gradle clean createOsgi

$ chmod +x build/osgi/run.sh && build/osgi/run.sh

____________________________

Welcome to Apache Felix Gogo


g! Hello osgi-run [k, o, t, l, i, n]


Using Frege for scripting in OSGi


In the last part of this tutorial, we'll see how to use a library which would usually be tricky to get working inside OSGi due to the fact that it uses its own classloader at runtime to load modules.

But this is not just any library: it's Frege, a Haskell for the JVM!

So, let's add a dependency on the Frege interpreter as a system lib, then use it with a ScriptEngine:


System libs

From what we've seen so far, you would imagine that to use the Frege ScriptEngine, all you would have to is to add Frege to the OSGi environment and then write some code to use it.

However, that will not work (if you have time, give it a go!) because:

    • ScriptEngines are looked up using Java's ServiceLoader mechanism, which does not normally work in OSGi.
    • Frege, internally, will load classes (or Frege modules) at runtime. Because each bundle in OSGi has its own classloader, the Frege interpreter will not be able to find the modules it needs, even the Prelude.
There are solutions to the first problem, but usually you would be stuck on the second one.

That's why osgi-run introduced system libs in the 1.5.0 version. They allow you to declare dependencies that should behave like a system library, ie. have access to the system classloader. This allows absolutely any library to work normally in OSGi. The only limitation is that classes present only in bundles will not be visible to the system libs unless you pass them to the system lib explicitly. But any package present in the system libs will be visible to any bundle that imports it.


git branch: use-frege

    systemLib "org.frege-lang:frege-interpreter-core:1.2"


Also change the bundles declaration to not deploy the default bundles (including gogo), only the project bundle, because this time we'll implement the CLI ourselves!


runOsgi {
bundles = [ project ]
}


Now, let's implement a simple REPL using the Hello class (still written in Kotlin):


package osgirun.tutorial

import org.osgi.service.component.annotations.Activate
import org.osgi.service.component.annotations.Component
import java.util.*
import javax.script.ScriptEngineManager

@Component(immediate = true)
class Hello {

@Activate
fun start() {
val prompt = "Frege > "
val engine = ScriptEngineManager().getEngineByName("frege") ?:
throw RuntimeException("Frege ScriptEngine was not found")

// never block the Component's @Activate method
Thread().run {
val scanner = Scanner(System.`in`)

val hasInput = {
print(prompt)
scanner.hasNextLine()
}

while (hasInput()) {
val line = scanner.nextLine()
if (line == "exit") {
break
} else {
println(engine.eval(line) ?: "")
}
}
}
}

}


Finally, you can start using a simple Frege REPL written in Kotlin, using a OSGi service!


$ gradle clean createOsgi

$ chmod +x build/osgi/run.sh && build/osgi/run.sh

Frege > 2 + 2

4

Frege > f x = x * x


Frege > f 5

25


And that's all.


You now know how to write OSGi applications that can benefit from the best of both worlds: OSGi modularity, dynamics and services, and at the same time use any Java resources you might need.


Discussion on Reddit.





Comments