Management of OSGi – let’s face it – is not very hard. The OSGi environment is clearly defined and that gives programmers many mechanisms to create administrative tools. The problem begins when we would like to use only one tool to manage few projects or artifacts of different types. I know this from personal experience because when I run Camel, ActiveMQ and CXF every from them provides own administration console. Every of them requires own security configuration, looks differently, have own dependencies and so on.

This stands a little bit in contradiction with the OSGi specification idea, which tries to unify management of different things, not only dependencies (Core) but aspects like configuration (ConfigAdmin), deployment (DeploymentAdmin), meta data (MetaType), preferences (PreferencesService), users (UserAdmin) or permissions (PermissionAdmin). Naturally, creation of standard for management tools is too hard to be closed in any specification, even so good like OSGi.

Felix WebConsole

As response for problems with lack of common tool, available from browser a project Felix WebConsole was created. This is a subproject of Felix’a. In its assumptions Felix WebConsole should let easily extend itself through the use of mechanisms known from the core OSGi – that is, services. It should also let change look and feel and localize the tool. All these assumptions was covered, but number of extensions for Felix WebConsole do not grow like mushrooms after the rain.
The question is, why? Now, in its simplicity Felix WebConsole make difficult creation of more complex extensions such as JMX. The problem is that our extension is only a servlet. From one hand it’s too much to put a link in list of bundles, on the other hand it is too little to make a few pages. If we’ll try we’ll begin to implement the second Struts framework based on servlets.
Project team do not make things easier because it puts on the “lightness” console. It is expected to deploy console on mobile devices too.

Karaf WebConsol as alternative

Apache Karaf WebConsole is brand new project, which was made few months ago. After dynamic incubation phase, which maybe was too short, it was moved to sub-project of Karaf. Similar as precursor it points to lightness but also points to collaboration with other web frameworks, in this case it is another ASF project – the Apache Wicket. Through its use we obtained far-reaching component model. It means that you may add link to menu and style it with fragment CSS (without fragment bundle). You may add new tab with content or simply put another widget to dashboard. All these things you may see right after logging into console.
All these extension won’t be possible without Wicket and Pax-Wicket. Thanks to huge amount of work made by Andreas Pieber on second project. Everything is stable and works as fast as Felix WebConsole.

Architecture of Karaf WebConsole

As I meintoned before, WebConsole uses Pax-Wicket and Wicket as presentation layer (we don’t count Java Script libraries). Most of extensions uses also blueprint to register services. There is no problem, similary like in case of Felix WebConsole, to use Spring DM or declarative services. Everything works with Pax-Web, but it should be possible to run it with any HttpService.
Whats’s more, an experimental branch of pax-wicket which I worked on previously named jsr330 let to use same components in a traditional container like is Tomcat. This means that the administrative tool went beyond the OSGi framework and will allow creation of multi-modular consoles, also in a traditional environment.

Extensions

New elements who are added to Karaf WebConsole are typically Wicket components or they are converted to them. Let see how to add new element to navigation:

package org.apache.karaf.webconsole.blueprint.internal.navigation;

import java.util.ArrayList;
import java.util.List;

import org.apache.karaf.webconsole.blueprint.internal.BlueprintPage;
import org.apache.karaf.webconsole.core.navigation.NavigationProvider;
import org.apache.wicket.Page;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.Link;

public class BlueprintNavigationProvider implements NavigationProvider {

    public List<Link<Page>> getItems(String componentId, String labelId) {
        List<Link<Page>> items = new ArrayList<Link<Page>>();

        Link<Page> link = new BookmarkablePageLink<Page>(componentId, BlueprintPage.class);
        link.add(new Label(labelId, "Blueprint"));
        items.add(link);

        return items;
    }

}


Interface org.apache.karaf.webconsole.core.navigation.NavigationProvider is universal supplier of navigation elements. In web application it’s mostly about links. Now, when we have implementation we need to submit it into registry to let use it. In this particular example we going to use blueprint, but it might be a standard activator or any other declarative way as well.

<?xml version="1.0" encoding="utf-8" ?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

    <service ref="provider" interface="org.apache.karaf.webconsole.core.navigation.NavigationProvider">
        <service-properties>
            <entry key="extends" value="osgi" />
        </service-properties>

    </service>

    <bean id="provider" class="org.apache.karaf.webconsole.blueprint.internal.navigation.BlueprintNavigationProvider" />

</blueprint>

Fragment above will cause addition of BlueprintPage as child of OSGi menu, because we set extends property. Result of execution you may see on attached picture.

Navigation is only example, remember – you have much more possibilities:

  • org.apache.karaf.webconsole.core.brand.BrandProvider – lets to change design (without fragment bundles)
  • org.apache.karaf.webconsole.core.navigation.ConsoleTabProvider – causes addition of new tab in navigation
  • org.apache.karaf.webconsole.core.navigation.SidebarProvider – adds new elements on left side
  • org.apache.karaf.webconsole.core.widget.WidgetProvider – lets to publish new panels with content
  • org.apache.karaf.webconsole.osgi.bundle.IActionProvider – adds specific link to bundle list
  • org.apache.karaf.webconsole.osgi.bundle.IColumnProvider – adds column to bundle list
  • org.apache.karaf.webconsole.osgi.bundle.IDecorationProvider – adds icon bundle list

Last two days I’ve spent hacking Swing code. I decided to run standalone producer application to show real interaction with broker. You may treat this Swing app like entry point for people to our middleware system. Users simply do “transfers” from this application and don’t know anything about technical details. I added text area to main window to show structure of message sent to broker.

Producing messages with JMS

Most of communication systems, whanever you will go have two different kinds of values, first – main and mainly used is body, second is typical metadata named headers or properties or parameters. JMS is not different, you can create different kinds of messages and set headers to them (in JMS world they’re named property, but I preffer header).
Let check messaging code we have:

// import declarations ...
public class JmsMessageSender implements MessageSender, InitializingBean {

	private ConnectionFactory connectionFactory;
	private Session session;
	private final String destination;
	private MessageProducer producer;

	public JmsMessageSender(String destination) {
		this.destination = destination;
	}

	public void sendMessage(String message, Map<String, Object> headers)
		throws Exception {

		if (session == null || producer == null) {
			afterPropertiesSet();
		}

		TextMessage jmsMsg = session.createTextMessage(message);
		for (String header : headers.keySet()) {
			jmsMsg.setObjectProperty(header, headers.get(header));
		}
		producer.send(jmsMsg);
	}

	public void afterPropertiesSet() throws Exception {
		Connection connection = connectionFactory.createConnection();
		session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		Queue queue = session.createQueue(destination);
		producer = session.createProducer(queue);
	}

	// not important
}

First of all we inject connectionFactory using Spring configuration file. In line 20 we create text message, because we sending messages with String as content. Line 22 sets headers for message and finally sends it in line 24 using message producer created in lines 28-31. If we’ll look closer these lines we’ll see standard initalization code automatically called during bean creation by Spring. In line 29 we create session without transaction support and create producer to send messages. Connection factory is configured in XML with properties file to extract informations like broker url username and password.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="view" class="org.code_house.mom.producer.ProducerWindow">
        <property name="title" value="Bank Agent APP" />
    </bean>

    <bean id="controller" class="org.code_house.mom.producer.ProducerController">
        <constructor-arg ref="view" />
        <property name="messageSender">
            <bean class="org.code_house.mom.producer.JmsMessageSender">
                <constructor-arg value="MOM.Incoming" />
                <property name="connectionFactory" ref="connectionFactory" />
            </bean>
        </property>
        <property name="mapper" ref="mapper" />
    </bean>

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="${url}" />
        <property name="userName" value="${user}" />
        <property name="password" value="${pass}" />
    </bean>

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:activemq.properties" />
    </bean>

    <bean id="mapper" class="org.codehaus.jackson.map.ObjectMapper" />

</beans>

I decided to use JSON over XML. Regarding last post about data structures it is not the best choice but it is very simple. So don’t treat it as reference implementation. I just wish to don’t use JAXB annotations. 🙂

Headers

Headers are common thing for routing. This is main reason why I allow to provide headers as parameter to sendMessage method. In many cases it is better to use headers or even extract headers from body of message in one endpoint to reduce number of content reads. Example how to use headers for routing we’ll see in next part about Apache Camel.

Just as a note types of headers supported by JMS spec:

  • Boolean
  • Byte
  • Short
  • Int
  • Long
  • Float
  • Double
  • String
  • Object

We can get or set headers using getTypeProperty(String name) or setTypeProperty(String name, Type value). Remember that headers of type “object” set by setObjectProperty must be serializable, otherwise they will be dropped before sending.

Producer application

As I said at begining of the post I spent two days on Swing hacks. 🙂 I injected two additional depentencies to this module – mig layout and better beans binding. First is responsible for frame layout, second for interactions between model and Swing controls. Whole application is simple form. All informations are displayed in status bar at bottom of the window, rest is taken by message area and combo boxes.

I have say, that binding framework is cool and reduced number of code lines I had to write without it. If you are interested in exact code structure, please go to producer module in mom-sample github repository. Because it is not in area of this post I will not write more about desktop implementation details.

For these who wish run sample (I belive you would do that) – after executing

mvn clean install

You can simply execute target/producer.jar by doulbe click. This is fat-jar with all dependencies needed by producer application.

XML Schema jest bodajże najlepszym sposobem walidacji dokumentów XML. Model zastosowany w przypadku tego meta-języka pozwala na tworzenie złożonych konstrukcji. W oparciu o niego można budować własne rozszerzenia czy też zagnieżdżać w sekcjach xsd:appinfo dodatkowe metadane. Dzisiaj jednak nie o tym, a o wzorcach projektowych. Sam się zdziwiłem gdy trafiłem na artykuł Introducing Design Patterns in XML Schemas. W życiu się nie zastanawiałem czy to co piszę w XSD ma coś wspólnego z wzorcami czy nie. Sun wyprzedził w tym momencie chyba wszystkich. 🙂

Wzorce, które zostały wymienione we wspomnianym artykule odnoszą się do powiązania definiowanych typów z definiowanymi elementami. Ciężko mi się zgodzić z tym, że wybór wzorca jest krytyczny przy projektowaniu schematu, ponieważ schemat zazwyczaj ma przeznaczenie już w chwili pisania i zazwyczaj nie możemy powiedzieć, zrobimy to wzorcem X, ponieważ sam nasuwa się wzorzec Y. Ale to tak tylko moim zdaniem.

Tabelka poniżej prezentuje zawartość przeniesioną ze strony Suna. Zawiera ona 4 najpopularniejsze wzorce. Dwa najczęściej spotykane w internecie to Venetian Blind oraz Garden of Eden ze względu na to, że są bardzo podatne na ponowne użycie.

Wzorzec Charakterystyka
Russian Doll, przykład Zawiera jeden element globalny, pozostałe są lokalne.
  • Jest tylko jeden poprawny element.
  • Może uprościć przestrzeń nazw poprzez zastosowanie atrybutu elementFormDefault dla elementu xsd:schema.
  • Nadaje się tylko dla pojedynczych schematów.
  • Pozwala na ponowne użycie tylko całej gałęzi, a nie każdego typu z osobna.
Salami Slice, przykład Wszystkie elementy są globalne, stąd każdy może być użyty w charakterze root node’a.
  • Wszystkie elementy można ponownie użyć.
  • Łatwe wiązanie schematów pomiędzy plikami.
  • Powoduje większą złożoność w przestrzeni nazw.
  • Trudny do określenia root.
Venetian Blind, przykład Pochodna Russian Doll, zawiera jeden element globalny, pozostałe są lokalne
  • Zawiera tylko jeden element nadrzędny.
  • Pozwala na ponowne użycie wszystkich typów oraz elementu nadrzędnego.
  • Łatwa praca z wieloma plikami.
  • Ograniczona enkapsulacja poprzez ekspozycję wszystkich typów.
Garden of Eden, przykład Połączenie Venetian Blind oraz Salami Slice. Wiele elementów globalnych, wiele typów publicznych. Wiele kandydatów na root node.
  • Pozwala ponownie użyć elementy oraz typy.
  • Łatwa praca z wieloma plikami.
  • Zawiera wiele potencjalnych elementów nadrzędnych.
  • Ograniczona enkapsulacja.
  • Trudna do czytania i zrozumienia.
Źródło: Sun Developers Network

Przykłady

Russian Doll

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://schemas.sun.com/point/russiandoll"
    xmlns:tns="http://schemas.sun.com/point/russiandoll"
    elementFormDefault="qualified">

    <xsd:element name="Line">
	<xsd:complexType>
	    <xsd:sequence>
		<xsd:element name="PointA">
		    <xsd:complexType>
			<xsd:attribute name="x" type="xsd:integer"/>
			<xsd:attribute name="y" type="xsd:integer"/>
		    </xsd:complexType>
		</xsd:element>
		<xsd:element name="PointB">
		    <xsd:complexType>
			<xsd:attribute name="x" type="xsd:integer"/>
			<xsd:attribute name="y" type="xsd:integer"/>
		    </xsd:complexType>
		</xsd:element>
	    </xsd:sequence>
	</xsd:complexType>
    </xsd:element>
</xsd:schema>

Salami Slice

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://schemas.sun.com/point/salami"
    xmlns:tns="http://schemas.sun.com/point/salami"
    xmlns="http://schemas.sun.com/point/salami"
    elementFormDefault="qualified">

    <xsd:element name="PointA">
	<xsd:complexType>
	    <xsd:attribute name="x" type="xsd:integer"/>
	    <xsd:attribute name="y" type="xsd:integer"/>
    	</xsd:complexType>
    </xsd:element>

    <xsd:element name="PointB">
	<xsd:complexType>
	    <xsd:attribute name="x" type="xsd:integer"/>
	    <xsd:attribute name="y" type="xsd:integer"/>
	</xsd:complexType>
    </xsd:element>

    <xsd:element name="Line">
    	<xsd:complexType>
	    <xsd:sequence>
		<xsd:element ref="PointA"/>
		<xsd:element ref="PointB"/>
	    </xsd:sequence>
	</xsd:complexType>
    </xsd:element>
</xsd:schema>

Venetian Blind

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://schemas.sun.com/point/venetianblind"
    xmlns:tns="http://schemas.sun.com/point/venetianblind"
    xmlns="http://schemas.sun.com/point/venetianblind"
    elementFormDefault="qualified">

    <xsd:complexType name="PointType">
	<xsd:attribute name="x" type="xsd:integer"/>
	<xsd:attribute name="y" type="xsd:integer"/>
    </xsd:complexType>

    <xsd:element name="Line">
	<xsd:complexType>
	    <xsd:sequence>
		<xsd:element name="PointA" type="PointType"/>
		<xsd:element name="PointB" type="PointType"/>
	    </xsd:sequence>
	</xsd:complexType>
    </xsd:element>
</xsd:schema>

Garden of Eden

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://schemas.sun.com/point/gardenofeden"
    xmlns="http://schemas.sun.com/point/gardenofeden"
    elementFormDefault="qualified">

    <xsd:complexType name="PointType">
	<xsd:attribute name="x" type="xsd:integer"/>
	<xsd:attribute name="y" type="xsd:integer"/>
    </xsd:complexType>

    <xsd:complexType name="LineType">
	<xsd:sequence>
	    <xsd:element ref="PointA"/>
	    <xsd:element ref="PointB"/>
	</xsd:sequence>
    </xsd:complexType>

    <xsd:element name="PointA" type="PointType"/>

    <xsd:element name="PointB" type="PointType"/>

    <xsd:element name="Line" type="LineType"/>
</xsd:schema>

Data with middleware

The data structures are very important in middleware world. Because we often connect multiple systems we need to define an “domain model” for integration. The domain model means that objects we share between all systems are well known, well defined, well understand in multiple teams often provided by multiple vendors. Let see what does it means in practice.

Well known

If you talk with business guys you have to use same terms with programmers. You might be mediator but not the translator. As integration team member mediate correct shape of solution but don’t became a translator between business divisions and developers (unless you are business analitic). All people should use same terms to minimalise problems with number of definitions. That’s first step of multiple projects, not only integration. If you look for deeper knowledge of domain definitions check a “Domain Driven Design” book written by Eric Evans and published in 2003. Ok, maybe business is not always part of integration project, but you communicate systems used by people, isn’t?

Well defined

You have names and scopes for your classes and objects. Probably you have 90% of fields that will be defined in them. Remember to don’t put too much informations from one system to domain model. Domain model is not list of entities from one system. Make sure that you don’t share database identifier from one system to second. Identifier should be business, for example most of clients might be identified by personal document, most of companies can be identified by tax id, not by database sequence. For future reasons try to extract atomic values, like for databases. With atomic values you will have less text processing to extract specific data and less code to maintain.

Well understand

That’s case for distributed teams. Usually every team have own parts of software to do and every team plays different game. Someone else produces messages, someone consumes them, this is place when you’ll have small wars. Make sure that data structures you all have to share match common needs, that defined identifiers are fine for different systems too. I met multiple situations when system analytics from two companies spent lots of hours on phone defining field constraints. Don’t follow this path.

Whole integration project is about to fail in 95% cases – if you will not have data structures. Ok, we know that proper structure is key for integration. Second important point in data is representation. I don’t going to talk about REST at all, but rather about format of serialization. We have number of options here, just look table below

Serialization Format Pros Cons
Java Object Serialization
  • Very easy to maintain
  • Easy to use with JMS / ActiveMQ – ObjectMessages
  • Built in Java
  • Available only for systems written in Java
  • Both sides must use same classes and versions (if we use serialization id)
  • As every binary format – it is hard to process
XML (with XML Schema)
  • Easy to read and write, both for computer and people (I don’t belive YAML)
  • Easy to use with JMS / ActiveMQ – TextMessages
  • Very easy validation, with XML Schema
  • Well supported in many languages (including validation with XML Schema), DOM api provides complex operations
  • Requires additional libraries, some languages don’t have any binding from XML to Objects and vice versa
  • Synchronisation between Java and XML Schema (code generation)
  • Without proper XML Schema might be too lax
  • Not the best for big portions of data because overflow
JSON
  • Easy to read and write, both for computer and people, even easier than XML
  • Easy to use with JMS / ActiveMQ – TextMessages
  • Smaller than XML, easier to parse, even without any API
  • Supported in number of languages
  • Lack of well supported official structure control tool (like XML Schema)
  • Lack of official API for handling JSON
  • Requires additional libraries, some languages don’t have any binding from JSON to Objects and vice versa

There is number of different wire formats not listed here – for example Protobuf, EDI which match this scenario too. I don’t count them (even if I should) because first is not well supported in many languages, second is rather legacy format not well for new systems.

Structures used in MOM

Like in most cases, also with middleware we have some data structures. Because we going to process simple cash transactions we’ll have following classes:

  • org.code_house.mom.domain.Transaction
  • org.code_house.mom.domain.Client
  • org.code_house.mom.domain.Money
  • org.code_house.mom.domain.Money.Currency

Transaction

Every transaction will have unique identifier generated by UUID. Transaction will point to some Client and have assigned Money object.

Client

In our case client is very simple POJO which contains fields ID and name. For middleware purposes we going to use only ID. Name might be usefull for user interface operations.

Money

Because we going to transfer some money to client account from producer we have to use proper data structure for a “cash”. Few years ago Martin Fowler in his book “Patterns of Enterprise Application Architecture” defined a Money pattern which is suitable to handle money values. We have to remember that client account might be handled in different currency and we cannot send only amount information. We need a currency too.

That’s all for data structures. It was more about philosophy than about code, but don’t worry – in next part we going to write more. All needed classes you’ll find in git repository. You may download zip archive|tar archive if you don’t have git client.

At end of the May I had great time in United Kingdom providing consulting. After this I started thinking about sharing an idea of middleware application based on ActiveMQ and Camel features. I’ve spent few days to create sufficient example.

What middleware is?

Middleware is a general term, about some software which is some kind of proxy between other systems. What for? – you could ask. Generally because communication from point to point is not the best to build bigger applications, and some components in middle of communication allows us to inject new logic without changing source systems. Could you imagine situation where you have a number of system producing messages and an number of consumers of these messages written in different languages? That’s typical case where middleware is going to be usefull.
There is number of middleware tools from various categories, in this post we’ll learn how to use ActiveMQ to build message oriented middleware. What does the MOM means? Generally that we have asynchronous communication without direct method invocations. Producer don’t know anything about consumer and vice versa. If you are interested in MOM – pick up “ActiveMQ in Action” book (written by commiters of ActiveMQ) from Manning Publications Co. where this term is described in greater detail.

Read the rest of this entry »

Pod koniec maja świetnie spędziłem czas w Wielkiej Brytanii dostarczając consulting. Tuż po nim zacząłem myśleć o podzieleniu się ideą aplikacji middleware zbudowanej na ActiveMQ z funkcjami Camela. Spędziłem kilka dni tworząc wystarczający przykład.

Czym jest middleware?

Middleware jest ogólnym terminem traktującym o oprogramowaniu które jest pewnego rodzaju pośrednikiem między systemami. Po cóż? – możesz zapytać. Generalnie ponieważ komunikacja pomiędzy punktami nie jest najlepsza do budowania większych aplikacji, co więcej niektóre komponenty pomiędzy komunikującymi pozwalają wstrzykiwać nową logikę bez zmieniania systemów komunikujących się. Czy możesz wyobrazić sobie sytuację gdzie wiele systemów tworzy komunikaty i wielu konsumentów tych komunikatów stworzonych w różnych językach? To jest typowy scenariusz gdzie middleware będzie użyteczne.
Istnieje wiele narzędzi middleware i kilka ich kategorii, w tym wpisie dowiemy się jak używać ActiveMQ do budowania message oriented middleware. Co oznacza termin MOM? To, że mamy asynchroniczną komunikację bez bezpośrednich wywołań metod. Producent nic nie wie o konsumencie i na odwrót. Jeśli jesteś zainteresowany MOM-ami – zajrzyj do książki “ActiveMQ in Action” (napisanej przez commiterów ActiveMQ) z wydawnictwa Manning, w której termin ten jest opisany szerzej.
Read the rest of this entry »

I work with Apache Karaf almost every day. There is a lot of commands provided by default and most of them are a bit anonymous. In this post I would like introduce these commands.
Read the rest of this entry »

W tym wpisie zostanie omówiony proces OSGi-fikacji artefaktów, który przechodziłem gdy uruchamiałem prostą usługę na ServiceMix, która miała śledzić zewnętrzny RSS i pobierać z niego wpisy. Postanowiłem skorzystać z camel-rss. Przykłady które były do niego załączone są wystarczające by stworzyć odpowiedniego konsumenta…
Read the rest of this entry »

Enterprise OSGi

23 Mar
2010

Do opublikowania tego postu zachęcił mnie Jacek Laskowski swym postem pod tytułem W piątek 4Developers ze mną z Enterprise OSGi i in.

Bardzo się cieszę że na 4Developers (na którym niestety mnie nie będzie) temat Enterprise OSGi będzie poruszony, ponieważ jak się zdaje jest to nieuchronny kierunek rozwoju Javy. Pod wpływem słów Jacka zacząłem się zastanawiać nad długofalowymi efektami jakie OSGi ma wnieść do developmentu.

Hałas który obecnie jest wokół OSGi w przybiera konkretne kształty w postaci projektów takich jak Aries czy Gemini. Obydwa projekty skupiają się nad ostatnimi draftami OSGi R4 V4.2 i mają na celu udostępnienie technologii takich jak JNDI, JPA i JMX wewnątrz kontenerów OSGi. Zacznijmy jednak od początku..

OSGi a jarhell

JAR-hell occurs when software is deployed into a runtime environment which is unsuitable, but nothing other than full integration testing would detect this. Having multiple software packages dependent upon the same piece of software, with unpredictable incompatibilities, is pure hell. Ensuring the compatibility of a variety of dependent packages is duanting, doing it amongst the variety supported by a hierarchy of complex class loaders, is inhuman.

Źródło Apache Depot

Czyli w skrócie – piekło zaczyna się robić gdy pojawiają się niekompatybilności między bibliotekami w poprawnym środowisku. Co więcej owe niekompatybilności można wykryć dopiero po dogłębnych testach we wszystkich środowiskach w których ma działać aplikacja.

W przypadku OSGi wszystkie zależności są przewidywalne, co więcej nie uda się nam uruchomić paczki bez jej zależności – stąd teoretycznie nigdy nie powinniśmy widzieć ClassNotFoundException. Nie uda nam się również uruchomić naszego bundle jeśli powstanie konflikt w używanych zależnościach. Przykład z życia wzięty – mamy bundle zależące od camel-activemq oraz activemq-core. Pierwszy z nich pozwala na import spring-jms w wersji < 4.0, natomiast drugi w wersji < 2.6. Jeśli do tego mamy dwie wersje bundle spring-jms: 2.5.6 oraz 3.0.0 to mamy klapę. Naszej paczki nie da się wystartować ponieważ otrzymamy "Packages usage conflict". Zostaliśmy ochronieni przed JAR Hellem kosztem zablokowania kodu nawet jeśli zależność była opcjonalna.

Rozwiązanie zagadki packages usage.

Problem wydaje się trywialny – teoretycznie to są dwie różne wersje Springa, nie da się zaprzeczyć że 2.5.6 != 3.0.0. W praktyce jednak zmiany w spring-jms były tak niewielkie że można bez problemu uruchomić activemq-core z nową wersją. W takim wypadku jesteśmy zmuszeni do czekania na nową wersję ActiveMQ, która będzie pozwalała na korzystanie ze Springa 3.0 bądź samodzielnie zmodyfikować manifesty. Obydwa rozwiązania są równie złe – jedno to czekanie, drugie to tworzenie nowej dystrybucji ActiveMQ.

Co w takim wypadku możemy zrobić? Możemy użyć serwisów OSGi, które pozwalają na oddzielenie implementacji od interfejsu, dzięki czemu możemy połączyć dwie wersje bibliotek za fasadą w postaci ServiceReference. Tutaj jednak może pojawić się inny problem – mianowicie część bibliotek które lubią dostęp do ClassLoaderów może skutecznie protestować – na przykład Hibernate czy Open JPA. Dla przykładu diagram obrazujący kolejny z życia wzięty przypadek:

Diagram bundli

W tym przypadku usiłowałem stworzyć działającą usługę która zapisywała by przychodzące komunikaty w bazie danych. Może parę słów o tym, który bundle co robi:

  • datasource otwiera połączenie do bazy danych, tworzy ConnectionFactory dla JMS a także EntityManagera.
  • binding bramka do przyjmowania komunikatów – w tym przypadku był to web service.
  • engine definicja routingu z użyciem Camela.
  • persistence użycie EntityManagera do zapisywania komunikatów
  • domain POJO, klasy domenowe

Całość komunikacji odbywała się z JMS w trybie request-reply dzięki Apache Camel. Po bardzo długich “walkach” poniższą strukturę udało się uruchomić pod OSGi. Ostatecznie całość działa z Hibernate w układzie takim jak poniżej.

Działająca struktura

Z diagramu wyrzuciłem paczki które nie są istotne takie jak driver JDBC czy Commons ConnectionPool. Jedyny mankament na jaki trafiłem wiąże się z DAO Service, mianowicie bundle który go eksportuje poprzez Spring-DM musi zadeklarować widoczność wszystkich swoich klas dla paczki która będzie korzystać z usługi co jak by nie patrzeć jest drobnym wypaczeniem fasady jaką ma być ServiceReference. Niestety po 2 tygodniach poświęconych na uruchomienie JPA w OSGi nie siliłem się na elegancję.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:osgi="http://www.springframework.org/schema/osgi"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/osgi
        http://www.springframework.org/schema/osgi/spring-osgi.xsd
    ">

    <osgi:reference id="dataSource" interface="javax.sql.DataSource" />

    <osgi:service id="exchangeDAOExporter" ref="exchangeDAO"
        context-class-loader="service-provider"
        interface="org.code_house.dataaccess.ExchangeDAO" />
</beans>

Kolejne metadane

Problem jaki powstaje z OSGi to metadane. Do tej pory – jeśli zarządzałem zależnościami do bibliotek robiłem to przez dependencyManagement Mavena, dzięki któremu rozwiązywałem wszystkie konflikty. OSGi jednak nie wiąże się z Mavenem ponieważ są to dwa różne obszary o zgoła innym funkcjonowaniu – OSGi to runtime, Maven to build time. Dopóki nie zapanuje harmonia pomiędzy tymi dwoma uruchamianie czegokolwiek w OSGi będzie katorgą. Należy do tego dodać jeszcze jeden element związany z OSGi – mianowicie OSGi Bundle Repository (OBR), a OBR nijak się ma do repozytoriów Mavena przez co rozbieżności tylko się nasilają.
Aby temu zapobiegać najpopularniejsze istniejące repozytoria SpringSource Enterprise Bundle Repository oraz ServiceMix 4 Bundles repository – publikują artefakty w repozytoriach Mavena. Problem w tym, że część artefaktów jest powielona. Tak jak kiedyś były 3 wersje Java Persistence API tak teraz dochodzą kolejne dwie – z manifestami OSGi. Czy ktoś wspominał o piekle?
Należy również dodać że nie każdy JAR który ląduje w OSGi jest traktowany tak samo – oprócz standardowych bundli są również fragmenty, które są świetnym rozwiązaniem, jednakże początkowo potrafią przysporzyć wielu problemów. Cały trik sprowadza się do tego, że fragmenty mają wspólny class loader z paczką do której są przypięte.

Korzyści z Enterprise OSGi

Vendor Lock Po całych tych wywodach na temat problemów z OSGi pora na to co ma ono nam dać – przenośność. Podobnie jak Java EE 6 z profilami tak OSGi ma zapewnić większą przenośność klocków pomiędzy środowiskami. Wyobrażacie sobie, że można przenieść aplikację z kontenera servletów na serwer aplikacyjny bez modyfikacji? Albo usługę z szyny integracyjnej na kontener servletów? Niedorzeczne, ale z OSGi możliwe do wykonania. Wystarczy zainstalować wszystkie wymagane bundle i całość będzie działać.

Oczywiście naiwna była by wiara w to, że tak będzie. Każda specyfikacja która powstaje dla Javy ma standaryzować i ujednolicać środowiska. W praktyce jednak każda z nich staje się punktem wyjściowym do rozwoju nowych produktów. Każdy dostawca oferuje zgodność ze specyfikacją plus coś. Nie twierdzę, że to złe, ponieważ polaryzacja rynku oprogramowania jest tak samo potrzebna jak wolny rynek, należy się jednak wystrzegać monopolistów a w przypadku oprogramowania również vendor locków.

Patrzę na OSGi z nadzieją ponieważ w moim mniemaniu jest to przedłużenie idei jaką niosła specyfikacja Java Business Integration. Propozycja JBI 2.0 spotkała się z krytyką ze strony IBM oraz BEA Systems zasłaniających się tym, że istnieje SCA. Problem w tym, że obydwie specyfikacje traktują o innych warstwach integracji – SCA gwarantuje przenośność usług, podczas gdy JBI miało zapewnić przenośność komponentów i komunikacji między nimi. SCA i JBI mogą a nawet powinny iść w parze. Teoria JBI mówiła o możliwości uruchomienia servicemix-cxf-bc (zgodnego z JBI) na Open ESB, w praktyce okazywało się to jednak bardzo trudne. Dzięki OSGi/Enterprise OSGi stanie się to łatwiejsze.

W ciągu kilku najbliższych lat trend OSGi dzięki zainteresowaniu wielkich korporacji będzie rósł w siłę. Trzymam kciuki by Enterprise OSGI przyniosło więcej korzyści niż tylko zbawienie od JAR Hella.

Dnia 23 lutego w ramach Warszawa JUG miałem przyjemność wraz z Tomkiem Nurkiewiczem prezentować narzędzia integracyjne z otwartym kodem źródłowym. Tomek przedstawił Mule ESB, podczas gdy ja zająłem się Apache ServiceMix i Apache Camel. Ze względu na objętość przykładu ten wpis będzie jedynie wprowadzeniem do konsoli.

Niestety podczas prezentacji nie udało mi się uruchomić przykładu na “szynie” – ponieważ uniemożliwiły to zależności do bibliotek których nie miałem zapisanych lokalnie. Drugim mym przeciwnikiem był czas – nie było wielu chętnych by słuchać po 2h tłumaczeń dlaczego się nie udało 🙂 Na problem z zależnościami stworzyłem rozwiązanie i zgłosiłem je do Karaf-a (FELIX 2141). W przyszłej wersji – 1.6 – wszyscy będą mogli skorzystać z polecenia features:info -t które wyświetli całe drzewko zależności potrzebnych do zainstalowania nowych funkcjonalności.

Przygotowanie środowiska

Do uruchomienia przykładów potrzebować będziemy dwóch paczek – pierwsza to Apache ServiceMix, druga to Apache Maven. Ze swojej strony polecam pobranie FUSE ESB 4.2, produktu który jest oparty o ServiceMix. Zgodnie z moimi zapowiedziami od tej wersji FUSE Source wprowadza pełne wsparcie produkcyjne dla ServiceMix 4. Jeśli nie masz zainstalowanego Mavena i nigdy tego nie robiłeś zajrzyj na wiki Code-House: Wprowadzenie do Maven 2

Po pobraniu odpowiedniej wersji należy ją rozpakować do wybranego folderu. Szynę uruchamiamy skryptem servicemix.bat. Jeśli wszystko przebiegło poprawnie naszym oczom powinien ukazać się obrazek jak poniżej:

E:toolsprogressfuse-esb4.2.0-bbin>servicemix.bat
 ____                  _          __  __ _
/ ___|  ___ _ ____   _(_) ___ ___|  /  (_)_  __
___  / _  '__  / / |/ __/ _  |/| |  / /
 ___) |  __/ |    V /| | (_|  __/ |  | | |>  <
|____/ ___|_|    _/ |_|______|_|  |_|_/_/_

  Apache ServiceMix (4.2.0-fuse-01-00)

Hit '' for a list of available commands
and '[cmd] --help' for help on a specific command.
karaf@root>

Od tej chwili mamy do dyspozycji konsolę administracyjną. Aby zwiększyć jej użyteczność wykonujemy następujące polecenia:

karaf@root> osgi:install wrap:mvn:http://download.java.net/maven/2!net.java.dev.jna/jna/3.1.0
Bundle ID: 134

Spowoduje to pobranie z repozytorium Mavena (http://download.java.net/maven/2) biblioteki JNA w wersji 3.1.0. Nie musimy oczywiście określać za każdym razem adresu repozytoriów, ale o tym nieco później. Prefix wrap: spowoduje wygenerowanie manifestu OSGi na podstawie zawartości pobranej biblioteki. Jest on konieczny ponieważ JNA nie dostarcza potrzebnych danych do uruchomienia w środowisku OSGi.

karaf@root> osgi:install mvn:http://jansi.fusesource.org/repo/release!org.fusesource.jansi/jansi/1.2
Bundle ID: 135

Druga biblioteka wykorzystuje JNA i umożliwia kolorowanie tekstu w konsoli windowsowej zgodnie z ANSI, czyli tak jak w standardowych terminalach Unix-a. Po zainstalowaniu tych dwóch rzeczy pora zaprząc je do pracy. Poniżej filtrujemy listę zainstalowanych rzeczy po słowie Console.

karaf@root> list|grep Console
[  10] [Active     ] [Created     ] [       ] [   30] Apache Felix Karaf :: Shell Console (1.4.0.fuse-01-00)
karaf@root> refresh 10

Numer 10 oznacza id paczki OSGi, tekst Active to stan paczki a tekst Created informuje o stanie kontekstu blueprint, czwarty bloczek to stan kontekstu Springa, numer 30 to start level paczki a w ostatnim nawiasie mamy wersję. Sporo informacji jak na jedną linijkę, nieprawdaż? Blueprint to standaryzowanie tego czym jest Spring i Spring DM, także można korzystać zamiennie bądź z jednego bądź z drugiego rozwiązania. Ciekawe porównanie funkcjonalności obu rozwiązań – Spring DM oraz Blueprint opublikował kilka dni temu Guillaume Nodet na swoim blogu we wpisie Spring-DM, Aries Blueprint and custom namespaces. Apache Camel od wersji 2.3 będzie wspierał Blueprint (CAMEL-2022).


 ____                  _          __  __ _
/ ___|  ___ _ ____   _(_) ___ ___|  /  (_)_  __
___  / _  '__  / / |/ __/ _  |/| |  / /
 ___) |  __/ |    V /| | (_|  __/ |  | | |>  <
|____/ ___|_|    _/ |_|______|_|  |_|_/_/_

  Apache ServiceMix (4.2.0-fuse-01-00)

Hit '<tab>' for a list of available commands
and '[cmd] --help' for help on a specific command.
karaf@root>

Teraz wykonanie poleceń z filtrem grep spowoduje podświetlenie szukanej frazy:

karaf@root> list |grep Console
[  10] [Active     ] [Created     ] [       ] [   30] Apache Felix Karaf :: Shell Console (1.4.0.fuse-01-00)

Takie małe udogodnienie przy przeglądaniu dłuższych rezultatów jest nieocenione.

Podstawowe polecenia konsoli

Polecenia w Apache Karaf są podzielone na kilka grup, które ułatwiają zarządzanie. Pierwszą grupą jest OSGi.

Polecenia OSGi

Polecenie Przeznaczenie
list Wyświetlenie listy zainstalowanych paczek
ls [bundle id] Wyświetlenie usług eksportowanych przez paczkę.
ls -u [bundle id] Wyświetlenie usług używanych przez paczkę.
headers [bundle id] Wyświetlenie manifestu paczki.
start [bundle id] Uruchomienie paczki o danym ID.
stop [bundle id] Zatrzymanie paczki o danym ID.
restart [bundle id] Zatrzymanie i wystartowanie paczki o danym ID.
update [bundle id] Aktualizacja paczki.
refresh [bundle id] Odświeżenie importów paczki a także przeładowanie kontekstu Spring-DM.
install [url] Zainstalowanie nowej paczki.
uninstall [bundle id] Odinstalowanie paczki.
shutdown Zatrzymanie kontenera.
bundle-level [bundle id] [startLevel] Ustawienie start levelu dla paczki.
start-level

Kiedy znamy już listę poleceń nie pozostaje nic innego jak je wypróbować. 🙂 Checkout przykładowego kodu z SVN pozwoli wykonać nam kilka ćwiczeń. Po wykonaniu polecenia mvn clean install w repozytorium Mavena są JARy które, naturalnie, chcemy zainstalować. Rezultat jaki powinniśmy zobaczyć w konsoli to:

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] Money transfer ServiceMix example ..................... SUCCESS [2.028s]
[INFO] Money transfer :: API ................................. SUCCESS [3.182s]
[INFO] Money transfer :: POI bundle .......................... SUCCESS [7.504s]
[INFO] Money transfer :: Internal ............................ SUCCESS [0.093s]
[INFO] Money transfer :: Internal :: CSV ..................... SUCCESS [4.399s]
[INFO] Money transfer :: Internal :: XLS ..................... SUCCESS [2.823s]
[INFO] Money transfer :: Internal :: Mail .................... SUCCESS [2.309s]
[INFO] Money transfer :: Internal :: Splitter ................ SUCCESS [0.936s]
[INFO] Money transfer :: Internal :: Routes .................. SUCCESS [1:21.241s]
[INFO] Money transfer :: External ............................ SUCCESS [0.016s]
[INFO] Money transfer :: External :: Customer ................ SUCCESS [3.182s]
[INFO] Money transfer :: External :: Bank .................... SUCCESS [2.059s]
[INFO] Money transfer :: External :: Validator ............... SUCCESS [2.918s]
[INFO] Money transfer :: Features ............................ SUCCESS [0.171s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1 minute 54 seconds
[INFO] Finished at: Mon Mar 22 10:47:27 CET 2010
[INFO] Final Memory: 127M/341M
[INFO] ------------------------------------------------------------------------

Może teraz kilka słów o modułach które są widoczne w Mavenie.

Moduł Ścieżka
Maven ID
Przeznaczenie
Money transfer :: API api
org.code-house.samples/api
API systemów zewnętrznych oraz POJO wykorzystywane do komunikacji.
Money transfer :: POI bundle poi
org.code-house.samples/poi
Bundle zawierający bibliotekę Apache POI oraz wszystkie jej zależności z Manifestem OSGi pozwalającym na jej uruchomienie na szynie.
Money transfer :: Internal :: CSV internal/csv
org.code-house.samples.internal/csv
Paczka zawierająca implementację procesora przetwarzającego pliki CSV na POJO MoneyTransfer.
Money transfer :: Internal :: XLS internal/xls
org.code-house.samples.internal/xls
Paczka zawierająca implementację procesora przetwarzającego pliki XLS na POJO MoneyTransfer.
Money transfer :: Internal :: Mail internal/mail
org.code-house.samples.internal/mail
Paczka z kodem dzielącym przychodzący mail z załącznikami na pojedyncze wiadomości które można przetworzyć jako XLS bądź CSV.
Money transfer :: Internal :: Splitter internal/splitter
org.code-house.samples.internal/splitter
Paczka z kodem odpowiedzialnym za rozdzielanie listy obiektów MoneyTransfer na listę wiadomości.
Money transfer :: Internal :: Routes internal/routes
org.code-house.samples.internal/routes
Główna paczka z definicjami routingu
Money transfer :: External :: Customer external/customer
org.code-house.samples.external/customer
Implementacja WebService odpowiedzialnego za pobieranie danych klienta na podstawie numeru rachunku bankowego.
Money transfer :: External :: Bank external/bank
org.code-house.samples.external/bank
Implementacja usługi zwracającej informację o nazwie banku na podstawie numeru rachunku.
Money transfer :: External :: Validator external/validator
org.code-house.samples.external/validator
Usługa weryfikująca czy MoneyTransfer jest poprawny.
Money transfer :: Features features
org.code-house.samples/features
Moduł zawierający opcjonalny deskryptor do uruchomienia modułów.

Jak widać dwa główne obszary projektu są skupione w katalogach internal oraz external. Ten pierwszy zawiera implementacje ściśle związaną z usługami natomiast drugi to “zatyczki” emulujące działanie systemów zewnętrznych. Drobna nota – kolumna Maven ID nie zawiera informacji o wersji – w każdym module jest to 1.0.0.SNAPSHOT.

Aby zainstalować któryś bundle przechodzimy do konsoli ServiceMix’a i wykonujemy takie polecenia:

karaf@root> install -s mvn:org.code-house.samples/api/1.0.0.SNAPSHOT
Bundle ID: 210
karaf@root> features:install camel-jetty
karaf@root> install -s mvn:org.code-house.samples.external/customer/1.0.0.SNAPSHOT
Bundle ID: 211

Po wykonaniu tych poleceń powinien być uruchomiony Web Service którego WSDL znajduje się pod adresem http://localhost:9001/CustomerWs?wsdl. Przełącznik -s w przypadku polecenia install powoduje że po zainstalowaniu bundle zostanie wystartowany. Polecenie features-install camel-jetty jest potrzebne nie ze względu na to że kolejna paczka korzysta z Camela, dzięki jego wykonaniu zostanie zainstalowane Jetty, z którego korzysta CXF.

Kolejne paczki instalujemy analogicznie:

karaf@root> features:install camel-activemq
karaf@root> install -s mvn:org.code-house.samples.external/validator/1.0.0.SNAPSHOT
Bundle ID: 215
karaf@root> install -s mvn:org.code-house.samples.external/bank/1.0.0.SNAPSHOT
Bundle ID: 217

Rozszerzenie camel-activemq jest rozszerzeniem modułu camel-jms które pozwala na nieco wydajniejszą pracę z ActiveMQ.

Integracja ServiceMix z repozytoriami Mavena

Maven jako narzędzie do budowania korzysta z określonego schematu składowania bibliotek które następnie są automatycznie pobierane. Karaf, który jak wspomniałem podczas prezentacji, wyłonił się z projektu ServiceMix Kernel korzysta z biblioteki Pax URL. Dzięki temu z marszu mamy dostęp do standardowych repozytoriów Mavena, co jednak gdy mamy swoje repozytorium, które zawiera tylko nasze artefakty?
Otwieramy plik etc/org.ops4j.pax.url.mvn.cfg i dodajemy w nim co trzeba. Moja standardowa konfiguracja wygląda następująco:

org.ops4j.pax.url.mvn.settings=E:/tools/maven-2.2.1/conf/settings.xml
org.ops4j.pax.url.mvn.localRepository=E:/repository
org.ops4j.pax.url.mvn.defaultRepositories=file:${karaf.home}/${karaf.default.repository}@snapshots
org.ops4j.pax.url.mvn.repositories= 
    http://repo1.maven.org/maven2, 
    http://repo.fusesource.com/maven2, 
    http://repo.fusesource.com/maven2-snapshot@snapshots@noreleases, 
    http://repository.apache.org/content/groups/snapshots-group@snapshots@noreleases, 
    http://repository.ops4j.org/maven2, 
    http://svn.apache.org/repos/asf/servicemix/m2-repo, 
    http://repository.springsource.com/maven/bundles/release, 
    http://repository.springsource.com/maven/bundles/external, 
    http://repository.code-house.org/content/groups/release, 
    http://repository.code-house.org/content/groups/snapshot@snapshots@noreleases, 
    http://jansi.fusesource.org/repo/release

Dzięki temu PAX w pierwszej kolejności będzie skanował katalog E:/repository zamiast standardowego ~/.m2/repository. Jeśli któreś z repozytoriów wymaga autoryzacji adres powinien wyglądać następująco:

http://user:pass@jansi.fusesource.org/repo/release

Podsumowanie

Mam nadzieję że wpis ten przybliży chociaż w niewielkim stopniu ServiceMix 4 oraz Karafa. W przyszłym wpisie, którego daty publikacji nie sposób przewidzieć zostaną dokładniej omówione polecenia z grupy features. Póki co życzę miłej zabawy z konsolą. 🙂 W razie pytań, niejasności i problemów – proszę o komentarze.

top