- expose java static calls as Karaf shell native commands
- override OSGi Headers at deploy time
- override OSGi Headers after deploy time with OSGi Fragments
Expose java static calls as Karaf shell native commands
As part of my job as software engineer that has to collaborate with support guys and customers, I very often find myself in the need of extracting additional information from a system I don't have access to.
Usual approaches, valid in all kind of softwares, are usually extracting logs, invoking interactive commands to obtain specific outputs or in what is the most complex case deploy some PoC unit that is supposed to verify a specific behavior.
JBoss Fuse, adn Karaf, the platform it's based onto do alredy a great job in exposing all those data.
- extensive logs and integration with Log4j
- extensive list of jmx operation (you can eventually invoke over http with jolokia)
- a large list of shell commands
- you need to print values that are not logged or returned in the code
- you might need to short-circuit some logic to hit a specific execution branch of your code
- you want to inject a line of code that wasn't there at all
Byteman is still a very good option to, but Karaf has a facility we can use to run custom code.
Karaf, allows you to write code directly in its shell; and allows you to record these bits of code as macro you can re-invoke. This macro will look like a native Karaf shell command!
Let's see a real example I had to implement:
verify if the jvm running my JBoss Fuse instance was resolving a specific DNS as expected.
The standard JDK has a method you can invoke to resolve a dns name:
Since that command is simple enough, meaning it doesn't requires a complex or structured input, I thought I could turn it into an easy to reuse command:
# add all public static methods on a java class as commands to the namespace "my_context": # bundle 0 is because system libs are served by that bundle classloader addcommand my_context (($.context bundle 0) loadClass java.net.InetAddress)
That funky line is explained in this way:
addcommandis the karaf shell functionality that accepts new commands
my_contextis the namespace/prefix you will attach you command to. In my case, "dns" would have made a good namespace.
($.context bundle 0)invokes java code. In particular we are invoking the
$.contextinstances, that is a built-in instance exposed by Karaf shell to expose the OSGi framework, whose type is
org.apache.felix.framework.BundleContextImpl, and we are invoking its method called
bundlepassing it the argument
0representing the id of the OSGi classloader responsible to load the JDK classes. That call returns an instance of
org.apache.felix.framework.Felixthat we can use to load the specific class definition we need, that is
As the inline comment says, an invocation of
addcommand, exposes all the public static method on that class. So we are now allowed to invoke those methods, and in particular, the one that can resolve dns entries:
JBossFuse:karaf@root> my_context:getAllByName "www.google.com" www.google.com/220.127.116.11 www.google.com/18.104.22.168 www.google.com/22.214.171.124 www.google.com/126.96.36.199 www.google.com/188.8.131.52 www.google.com/2a00:1450:4002:804:0:0:0:1014
This functionality is described on Karaf documentation page.
Override OSGi Headers at deploy time
If you work with Karaf, you are working with OSGi, love it or hate it.
A typical step in each OSGi workflow is playing (or fighting) with OSGi headers.
If you are in total control of you project, this might be more or less easy, depending on the releationship between your deployment units. See Christian Posta post to have a glimpse of some less than obvious example.
Within those conditions, a very typical situation is the one when you have to use a bundle, yours or someone else's, and that bundle headers are not correct.
What you end up doing, very often is to re-package that bundles, so that you can alter the content of its
MANIFEST, to add the OSGi headers that you need.
Karaf has a facility in this regard, called the
You might alredy know it as a shortcut way to deploy a non-bundle jar on Karaf but it's actually more than just that.
What it really does, as the name suggest, is to wrap. But it can wrap both non-bundles and bundles!
Meaning that we can also use it to alter the metadata of an already packaged bundle we are about to install.
Let's give an example, again taken fron a real life experience.
Apache HttpClient is not totally OSGi friendly. We can install it on Karaf with the
wrap: protocol and export all its packages.
JBossFuse:karaf@root> install -s 'mvn:org.apache.httpcomponents/httpclient/4.2.5' Bundle ID: 257 JBossFuse:karaf@root> exports | grep -i 257 257 No active exported packages. This command only works on started bundles, use osgi:headers instead JBossFuse:karaf@root> install -s 'wrap:mvn:org.apache.httpcomponents/httpclient/\ 4.2.5$Export-Package=*; version=4.2.5' Bundle ID: 259 JBossFuse:karaf@root> exports | grep -i 259 259 org.apache.http.client.entity; version=4.2.5 259 org.apache.http.conn.scheme; version=4.2.5 259 org.apache.http.conn.params; version=4.2.5 259 org.apache.http.cookie.params; version=4.2.5 ...
And we can see that it works with plain bundles too:
JBossFuse:karaf@root> la -l | grep -i camel-core [ 142] [Active ] [ ] [ ] [ 50] mvn:org.apache.camel/camel-core/2.12.0.redhat-610379 JBossFuse:karaf@root> install -s 'wrap:mvn:org.apache.camel/camel-core/2.12.0.redhat-610379\ $overwrite=merge&Bundle-SymbolicName=paolo-s-hack&Export-Package=*; version=1.0.1' Bundle ID: 269 JBossFuse:karaf@root> headers 269 camel-core (269) ---------------- ... Bundle-Vendor = Red Hat, Inc. Bundle-Activator = org.apache.camel.impl.osgi.Activator Bundle-Name = camel-core Bundle-DocURL = http://redhat.com Bundle-Description = The Core Camel Java DSL based router Bundle-SymbolicName = paolo-s-hack Bundle-Version = 2.12.0.redhat-610379 Bundle-License = http://www.apache.org/licenses/LICENSE-2.0.txt Bundle-ManifestVersion = 2 ... Export-Package = org.apache.camel.fabric; uses:="org.apache.camel.util, org.apache.camel.model, org.apache.camel, org.apache.camel.processor, org.apache.camel.api.management, org.apache.camel.support, org.apache.camel.spi"; version=1.0.1, ...
Where you can see
Bundle-SymbolicName and the version of the exported packages are carrying the values I set.
Override OSGi Headers after deploy time with OSGi Fragments
Last trick is powerful, but it probably requires you to remove the original bundle if you don't want to risk having half of the classes exposed by one classloader and the remaining ones (those packages you might have added in the overridden
Export) in another one.
There is actually a better way to override OSGi headers, and it comes directly from an OSGi standard functionality: OSGi Fragments.
If you are not familiare with the concept, the definition taken directly from OSGi wiki is:
A Bundle fragment, or simply a fragment, is a bundle whose contents are made available to another bundle (the fragment host). Importantly, fragments share the classloader of their parent bundle.
That page gives also a further hint about what I will describe:
Sometimes, fragments are used to 'patch' existing bundles.
We can use this strategy to:
- inject .jars in the classpath of our target bundle
- alter headers of our target bundle
I have used the first case to fix a badly configured bundle that was looking for a an xml configuration descriptor that it didn't include, and that I have provided deploying a light Fragment Bundle that contained just that.
But the use case I want to show you here instead, is an improvement regarding the way to deploy Byteman on JBoss Fuse/Karaf.
If you remember my previous post, since Byteman classes needed to be available from every other deployed bundle and potentially need access to every class available, we had to add Byteman packages to the
org.osgi.framework.bootdelegation property, that instructs the OSGi Framework to expose the listed packages through the virtual system bundle (id = 0).
You can verify what is currently serving with
headers 0, I won't include the output here since it's a long list of jdk extension and framework classes.
If you add your packages,
org.jboss.byteman.rule,org.jboss.byteman.rule.exception in my case, even these packages will be listed in the output of that command.
The problem with this solution is that this is a boot time property. If you want to use Byteman to manipulate the bytecode of an already running instance, you have to restart it after you have edited this properties.
OSGi Fragments can help here, and avoid a preconfiguration at boot time.
We can build a custom empty bundle, with no real content, that attaches to the system bundle and extends the list of packages it serves.
<Export-Package> org.jboss.byteman.rule,org.jboss.byteman.rule.exception </Export-Package> <Fragment-Host> system.bundle; extension:=framework </Fragment-Host>
That's an excerpt of maven-bundle-plugin plugin configuration, see here for the full working Maven project, despite the project it's really just 30 lines of
JBossFuse:karaf@root> install -s mvn:test/byteman-fragment/1.0-SNAPSHOT
Once you have that configuration, you are ready to use Byteman, to, for example, inject a line in
java.lang.String default constructor.
# find your Fuse process id PROCESS_ID=$(ps aux | grep karaf | grep -v grep | cut -d ' ' -f2) # navigate to the folder where you have extracted Byteman cd /data/software/redhat/utils/byteman/byteman-download-184.108.40.206/ # export Byteman env variable: export BYTEMAN_HOME=$(pwd) cd bin/ # attach Byteman to Fabric8 process, no output expected unless you enable those verbose flags sh bminstall.sh -b -Dorg.jboss.byteman.transform.all $PROCESS_ID # add these flags if you have any kind of problem and what to see what's going on: -Dorg.jboss.byteman.debug -Dorg.jboss.byteman.verbose # install our Byteman custom rule, we are passing it directly inline with some bash trick sh bmsubmit.sh /dev/stdin <<OPTS # smoke test rule that uses also a custom output file RULE DNS StringSmokeTest CLASS java.lang.String METHOD <init>() AT ENTRY IF TRUE DO traceln(" works: " ); traceOpen("PAOLO", "/tmp/byteman.txt"); traceln("PAOLO", " works in files too " ); traceClose("PAOLO"); ENDRULE OPTS
Now, to verify that Byteman is working, we can just invoke
java.lang.String constructor in Karaf shell:
JBossFuse:karaf@root> new java.lang.String works:
And as per our rule, you will also see the content in
Inspiration for this third trick come from both the OSGi wiki and this interesting page from Spring guys.
If you have any comment or any other interesting workflow please leave a comment.