Sunday, May 25, 2014

Consuming arbitrary remote services with the OSGiELResolver (camunda BPM OSGi)

In my last blog post I promised to give a slightly more advanced example about how to use the new OSGiELResolver. And as I promised, here it is ;-)

Prerequisites


The setup is quite simple. We have three bundles:
  1. API
  2. Service Provider
  3. Service Consumer
You can find all the sources here. (feel free to suggest improvements, possible bugs, etc.)
As runtime I used two Apache Karaf instances on my computer (version 2.3.5; I had some problems with 3.0.1).
For remoting we'll use Apache CXF 1.4 (single bundle release).
And of course we'll need camunda BPM platform OSGi, which you'll have to build yourself.
Before I tell you more about the three bundles I'd like to point the book "Enterprise OSGi in Action" out. Without that great book I couldn't have provided this example. It's definitely worth reading.

So, enough advertisement, let's take a look at the bundles.

API bundle

 

The API bundle is really simple. It only contains one interface with a method. We'll need the bundle in both runtimes.

Provider bundle

 

Now we're getting a little bit more serious. The provider bundle contains the service implementation we want to use.
The context.xml contains the important parts for remoting:
<entry key="service.exported.interfaces" 
 value="de.blogspot.wrongtracks.osgielresolver.api.SomethingService"/><entry key="service.exported.configs"
       value="org.apache.cxf.ws"

<entry key="org.apache.cxf.ws.address"
       value="http://localhost:9001/somethingservice"/>


"service.exported.interfaces" should be obvious.
"service.exported.configs" tells Distributed OSGi to look for implementation specific properties.
Lastly "org.apache.cxf.ws.address" lets us define an alternative address. It is quite helpful if you don't want to type the fully qualified name of the class in your browser or other config files.

Consumer bundle

 

Let's take a look at the consumer. This bundle needs a little bit more information to work properly. To be able to consume remote services we need the OSGI-INF/remote-service/remote-services.xml. It doesn't have to be that name or that directory. You can specify the path inside the bundle with the "Remote-Service" header, which I set in the POM to:
      <Remote-Service>OSGI-INF/remote-service/*.xml</Remote-Service>
I won't walk you through the remote-services.xml. I'm sure you'll find better explanations somewhere else. (e.g. in Enterprise OSGi in Action ;-) )

After we configured this we can use the reference tag in the context.xml to find the service.
To make the service work with the OSGiELResolver we have to add two things. In the remote-services.xml the property "processExpression" has to be set and in the context.xml we have to use a filter.
As you may know the ELResolver uses the filter to search for classes. Searching only worked when both, attribute and filter, were set.

The provider Karaf

 

Like I said, I used Karaf as runtime. The "provider" Karaf needs three bundles:
  1. API
  2. Provider
  3. Apache CXF
Just drop them into the deploy directory. It worked best for me when I started them in the order API, CXF and provider. Then everything should work as expected.

The consumer Karaf

 

The "consumer" Karaf needs a little bit more bundles (and if you run it on the same machine you'll have to change three ports). You have to add:
  1. API
  2. Consumer
  3. Apache CXF
  4. camunda BPM platform OSGi and dependencies
Drop API, consumer and CXF jars into deploy (again, starting API, CXF and then consumer works best). Adding camunda BPM platform OSGi isn't very difficult because there is a feature.xml (assumed it is installed in your local Maven repository).
To install it type:
features:addurl mvn:org.camunda.bpm.extension.osgi/camunda-bpm-karaf-feature/1.0.0-SNAPSHOT/xml/features

and then:
features:install camunda-bpm-karaf-feature-minimal

This should resolve all you bundles. Now, If you start the consumer bundle you should see the log saying "Started process". Strangely the logger of the service implementation was quiet. But if you uncomment the exception you can see that the service was called.

So, as you can see, the new OSGiELResolver makes it possible to consume arbitrary remote services, which is quite an improvement. I hope my example is understandable and helps to see the possibilities.

Hint

 

When you encounter this exception:
java.lang.IllegalStateException: Invalid BundleContext
just start the CXF bundle again, then it should work.

Monday, May 19, 2014

camunda BPM OSGi: the new OSGiELResolver

Introduction


Some of you may know that I am the maintainer of the camunda BPM OSGi project.
Several weeks ago I started to implement a new ELResolver (EL = expression language) and because it's finished now I want to do some shameless self-advertising for my work ;-)

The problem


The "old" ELResolver had some limitations: It could only work with one kind of classes (those who implement the JavaDelegate interface) and you had to register the ELResolver as service listener.
Also, the implementation depends on Blueprint because it used the registered component id to find the classes.

The new OSGiELResolver


The new OSGiELResolver doesn't have those limitations. You can use it theoretically with every class and it doesn't depend on Blueprint. If you want to know more, please have a look at the updated README. I would be happy if you could give me some feedback or ideas for improvement.

So far for now. I'll try to put together a more advanced example, soon.

Please note: this change breaks the API because I moved some classes, so this version would be a new major version number, if it weren't for the snapshot ;-)

Saturday, May 10, 2014

First steps with Apache ACE

Introduction


"Apache ACE is a software distribution framework that allows you to centrally manage and distribute software components, configuration data and other artifacts to target systems." (from https://ace.apache.org/)
Well, that sounds good enough to try it out, at least for me.
I like the idea to centrally configure deployments with different version and have a way to automatically distribute those.

Starting ACE


Setting up Apache ACE was pretty easy. The Getting started guide contains all the necessary steps.
My MacBook was the ACE server and my Raspberry Pi a target.

Using the Web GUI is easy and straightforward (nice one guys ;-) ).
But that's for little children. I wanna find a way to automate everything with scripts.

The Client Shell API

Basically there are two ways to talk to the server remotely. One is the Client Shell API and the other way is via REST API. For now I'll stick with the Shell API

1st step: connecting to the server as shell client

Before we can write a script we have to connect to the server.
With some help from the iQSpot people (see here) I figured it out.
They suggest starting the client like this:

java -Dagent.discovery.serverurls="http://server:port"
     -Dorg.apache.ace.server="server:port"
     -Dorg.apache.ace.obr="server:port"
     -Dorg.osgi.service.http.port=-1
     -jar client.jar
 
Unfortunately, that didn't work for me (even after adding some missing backslashes).
The default is that you should be in the directory of client.jar. "-jar client.jar" wasn't the problem.
The startup searches for the client/conf directory, so when you see this exception:

java.lang.IllegalArgumentException: Bad arguments; either not an existing directory or an invalid interval.
    at org.apache.ace.configurator.Configurator.<init>(Configurator.java:89)
    at org.apache.ace.configurator.Activator.init(Activator.java:33)
    at org.apache.felix.dm.DependencyActivatorBase.start(DependencyActivatorBase.java:76)
    at org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:645)
    at org.apache.felix.framework.Felix.activateBundle(Felix.java:2146)
    at org.apache.felix.framework.Felix.startBundle(Felix.java:2064)
    at org.apache.felix.framework.Felix.setActiveStartLevel(Felix.java:1291)
    at org.apache.felix.framework.FrameworkStartLevelImpl.run(FrameworkStartLevelImpl.java:304)
    at java.lang.Thread.run(Thread.java:722)

You're probably starting the client from a different directory.
To get rid of that exception we have to set a property:
-Dorg.apache.ace.configurator.CONFIG_DIR=
All in all the command to start the client looks like this:

java -Dagent.discovery.serverurls="http://server:port"\
     -Dorg.apache.ace.server="server:port"\
     -Dorg.apache.ace.obr="server:port"\
     -Dorg.osgi.service.http.port=-1\
     -Dorg.apache.ace.configurator.CONFIG_DIR="apache-ace-2.0.1-bin/client/conf"\
     -jar apache-ace-2.0.1-bin/client/client.jar


Now we can start the client.
But to pass a script to the shell we need two more arguments. Thanks again to the iQSpot people. They already pointed out those arguments:
  • -Dgosh.args="–-args"
  • -Dace.gogo.script.delay=delay
  • -Dace.gogo.script=/path/to/script.gogo
What do those three do?
Everything you'll pass as "gosh.args" will be executed immidiately. If pass "--help" for example and start the client you'll see the help output.
The delay is helpful when you want to give your client some time to synchronize with the server.
"ace.gogo.script" should be obvious ;-)
We end up with the following command:

java -Dagent.discovery.serverurls="http://server:port"\
     -Dorg.apache.ace.server="server:port"\
     -Dorg.apache.ace.obr="server:port"\

     -Dorg.osgi.service.http.port=-1\
     -Dorg.apache.ace.configurator.CONFIG_DIR="apache-ace-2.0.1-bin/client/conf"\
     -Dace.gogo.script.delay="3000"\
     -Dace.gogo.script="script.foo"\

     -jar apache-ace-2.0.1-bin/client/client.jar

Now we have to find out, what we should put into "script.foo".
 

Shell commands


Every (basic) command is described here.
The steps are quite simple: cw, ca, cf, ca2f, cd, cf2d
If you don't like or get the abbreviations (it took me a while) there is also a nice picture in the REST API documentation:
What the picture is missing is cw or "create workspace". When using the Shell API you need a workspace, which you can commit later.

The script

 

So, what should skript.foo do? Let's assume we have to upload some generated artifacts from our CI server 
The steps are
  1. create workspace
  2. add the new Jars as artifacts from certain directory
  3. create a new feature
  4. add artifacts to feature
  5. create a new distribution
  6. add new feature and existing ones to distribution
  7. add feature to existing target 
I have to admit that it took me quite a while to figure everything out because I'm not very experienced with Apache Felix GoGo.
Creating the workspace is easy: w = (cw)
Now we can call the workspace with $w. Adding the Jars was more difficult. Let's assume the directory is ./toAdd. Then the command looks like this: 

each ([(ls toAdd)]) {$w ca (($it toURL) toString) false}

You "toAdd" can be changed to any path and you could use some wildcards, like ls toAdd/*.jar
I guess if you're used to GoGo the command won't be a surprise. If you're not used to it, I would like to explain the different parts to you:
each takes a list and a function. ls toAdd returns a File array. That's why we need the brackets. They convert the array into a list. After that comes the function, indicated by the braces.
$w ca is the method to create an arfifact. $it is the iterator over the list that is provided by each.
Then we call the methods toURL and toString because reflection makes it possible ;-)

Third step: add all artifacts to feature

each ($w la "(Bundle-SymbolicName=org.camunda.*)") {symbolicName=($it getAttribute "Bundle-SymbolicName"); $w ca2f "(Bundle-SymbolicName="$symbolicName")" "(name=test-feature)"}

Again, we use a for-each-loop.
$w la lists all the bundles that match the passed pattern. (Here, I want to add all camunda bundles, no advertisement ;-))
Then I save the symbolic name in a variable, so it's easier for me later to reference it.
org.apache.ace.client.repository.RepositoryObject has a getAttribute method, which we use here.
Also, please note the semicolon.
We use the symbolic name as part of the first argument for ca2f (create artifact2feature).
The String contains three parts
  • "(Bundle-SymbolicName="
  •  $symbolicName
  • ")"
I don't know why, but we don't ne a "+" for string concatenation. The second argument is the name of the feature. I just assume it to stay the same: "test-feature"
Creating a distribution and a feature2distribution are nothing special.

All in all we end up with the following:

w = (ace:cw)
$w cf test-feature
$w cd test-distro

each ([(ls toAdd)]) {$w ca (($it toURL) toString) false}

each ($w la "(Bundle-SymbolicName=org.camunda.*)") {symbolicName=($it getAttribute "Bundle-SymbolicName"); $w ca2f "(Bundle-SymbolicName="$symbolicName")" "(name=test-feature)"}

$w cf2d "(name=test-feature)" "(name=test-distro)"

$w commit


That should do the trick so far.
Stay tuned for my next steps with ACE ;-)

Tuesday, May 6, 2014

Glassfish 4, Commons Mail and "UnsupportedDataTypeException: no object DCH for MIME type multipart/mixed"

I know there are a bazillion posts/threads/etc. about the exception mentioned in the title and now there are a bazillion + one ;-)
Unfortunately I couldn't find the solution I want to present to you anywhere else.

First some context:
My class extends an Activiti class and uses Apache Commons Mail to send an email.
The email contains some text and has a file (txt/pdf/docs) attached.
Everything runs inside a Glassfish 4 and the Jars are deployed as OSGi bundles.

When calling email.send() the server threw the feared UnsupportedDataTypeException:

Caused by: javax.activation.UnsupportedDataTypeException: no object DCH for MIME type multipart/mixed;
    boundary="----=_Part_0_397989068.1398665205325"
    at javax.activation.ObjectDataContentHandler.writeTo(DataHandler.java:891)
    at javax.activation.DataHandler.writeTo(DataHandler.java:317)
    at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1574)
    at javax.mail.internet.MimeMessage.writeTo(MimeMessage.java:1840)
    at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1119)
    ... 120 more


Like I mentioned the Internet is full of solutions but none of them worked for me.
Because the Glassfish showed me that all of my bundles were correctly linked and resolved the problem had to be another place.

My colleague then told me I should try to change the TCCL. After some try-and-error it worked (I tried the one from commons.mail, the one from javax.activation and one I forgot ;-)).

The solution was to import javax.mail in my bundle and change the TCCL to the javax.mail classloader:

Thread.currentThread().setContextClassLoader(javax.mail.Message.class.getClassLoader());
email.send()

I am not sure why only the javax.mail classloader works. For me it is some arcane dependency/classloading/visibility problem.
Nevertheless, I hope this post helps some Glassfish/OSGi users.

Finally, I would like to thank my colleague @spost1970 for helping me find a solution.

 

Copyright @ 2013 Wrong tracks of a developer.

Designed by Templateiy