Статьи

Построение CXF REST Сервис в OSGi для Караф

Я оставлю это экспертам, чтобы рассказать, насколько  хороша OSGi . Среди множества преимуществ я мог бы рассказать вам, почему мы выбрали OSGi для домашнего проекта — модульность, избегая ада JAR и динамических обновлений (эй, а почему бы и нет?)

Мы выбрали Apache Felix (реализация спецификации платформы OSGi) и Apache Karaf (как бы это выразиться — что-то вроде сервера приложений для приложений OSGi). Помимо работы в качестве контейнера OSGi, Karaf обладает множеством замечательных  функций  (каламбур). И нам нравится идея управления несколькими экземплярами Karaf, управляемыми через Zookeeper.

Хватит говорить, давайте посмотрим код. Это элементарное руководство по запуску базовой службы отдыха OSGi CXF-JAX на Karaf. Если задан параметр REST (имя), служба просто вернется  Hello, (name). Это оно !! Весь проект можно скачать  здесь

Так что, если запрос  http://localhost:8181/cxf/karafsimple/say/hello/arun, ответ будет Hello, arun

Обратите внимание, что этот проект не имеет моделей домена и поэтому имеет только два проекта — один для REST (Контроллер) и другой для фактической реализации сервиса. Структура проекта выглядит следующим образом:

Шаг 1 — Внедрение сервиса

В этом проекте есть только две «полезные» вещи —  HelloService интерфейс и  HelloServiceImpl класс.


HelloService
package me.rerun.karafcxf.service.impl;
public interface HelloService {
    public String sayHello(String name);
}

HelloServiceImpl
package me.rerun.karafcxf.service.impl;
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "Hello, "+name;
    }
}

Глупо, верно?

Шаг 2 — проект REST

Подобно проекту Service, в этом проекте также есть две примечательные вещи —  HelloRestService интерфейс и  HelloRestServiceImpl класс.

HelloRestService


HelloRestService
package me.rerun.karafcxf.rest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
//Maps for the `say` in the URL
@Path("say")
public interface HelloRestService {
    @GET
    @Path("hello/{name}") //Maps for the `hello/John` in the URL
    public String handleGet(@PathParam("name") String name);
}

HelloRestServiceImpl

The  HelloRestServiceImpl ничего не делает, кроме как вызывает то,  HelloService что вводится через  Blueprint Dependency Injection . Эй, инъекция выглядит очень знакомой Spring DI? В точку !! Spring DI находится под сильным влиянием Spring DI. На самом деле, оригинальная работа над проектом выполняется  весной .


HelloRestServiceImpl
package me.rerun.karafcxf.rest;
import me.rerun.karafcxf.service.impl.HelloService;
public class HelloRestServiceImpl implements HelloRestService{
    //Just like Spring.  Please add Getters/Setters. Blueprint annotations are still work in progress
    private HelloService helloService;
    public String handleGet(String name){
        return helloService.sayHello(name);
    }
    /*
        Constructor
     */
    public HelloRestServiceImpl(){
    }
    /*
        Getters and Setters
     */
    public HelloService getHelloService() {
        return helloService;
    }
    public void setHelloService(HelloService helloService) {
        this.helloService = helloService;
    }
}

Шаг 3 — Инъекции

XML (с любым именем) в папке OSGI-INF / blueprint будет выбран для сканирования DI.

serviceimpl.xml

Делает две вещи в одном теге:

  1. Регистрирует  HelloService в реестре сервисов для поиска
  2. Говорит, что его реализация HelloServiceImpl

ресурсы / OSGI-INF / план / serviceimpl.xml
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
    <service ref="helloServiceBean" interface="me.rerun.karafcxf.service.impl.HelloService">
        <bean class="me.rerun.karafcxf.service.impl.HelloServiceImpl"/>
    </service>
</blueprint>

На самом деле, вы можете сделать это в два отдельных шага. Подробнее об этом  здесь .


ресурсы / OSGI-INF / план / serviceimpl.xml
<service id="helloServiceBean" ref="helloServiceImpl"
    interface="me.rerun.karafcxf.service.impl.HelloService" />
<bean id="helloServiceImpl" class="me.rerun.karafcxf.service.impl.HelloServiceImpl" />

rest.xml


ресурсы / OSGI-INF / план / rest.xml
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
           xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws"
           xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
           xmlns:cxf="http://cxf.apache.org/blueprint/core"
           xsi:schemaLocation="
  http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
  http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd
  http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd
  http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd">
<!-- 1 -->
    <cxf:bus id="cxfBus1">
        <cxf:features>
            <cxf:logging/>
        </cxf:features>
    </cxf:bus>
<!-- 2 -->
    <jaxrs:server address="/karafsimple" id="someRestService">
        <jaxrs:serviceBeans>
            <ref component-id="restServiceImpl"/>
        </jaxrs:serviceBeans>
    </jaxrs:server>
<!-- 3 -->
    <!-- Implementation of the rest service -->
    <bean id="restServiceImpl" class="me.rerun.karafcxf.rest.HelloRestServiceImpl">
        <property name="helloService" ref="helloServiceBean"/>  <!--Points to the reference below -->
    </bean>
<!-- 4 -->
    <!-- This has to point to the service registered through serviceimpl.xml in the service.impl project -->
    <reference id="helloServiceBean"
               interface="me.rerun.karafcxf.service.impl.HelloService">
    </reference>
</blueprint>

1)  cxf-bus это конфигурация шины для CXF. Это как менеджер для всех сервисов CXF. Насколько я знаю, наиболее распространенное использование — это настройка пользовательских  перехватчиков  (для аудита, манипулирования запросами / ответами, манипулирования заголовками и т. Д.)

2) the jaxrs:server initiates a server which would start listening to the URLs that we mapped for. Of course, we would want to map the handlers for the URLs and those go under the serviceBeans.

3) The third note in the XML is the just the restServiceImpl configuration and the injection of the helloService as a property inside the HelloRestServiceImpl

4) The reference tag will lookup the service registry for a bean with the same id.

Step 4 — KAR — Karaf Archive (Optional but easier this way)

Technically, you could just start throwing the bundles generated through the Maven into to the deploy directory of karaf. However, as the project goes big and your dependencies are becoming many, it is advisable to use the .kar archive. Picture .kar as your .war or .ear bundle. A .kar archive, internally, looks something like this :

Building .kar

Building the .kar is composed of two steps :

Step 1

feature.xml

Similar to your web descriptor or application descriptor, we have the features descriptor in Karaf to represent the entire repository that we are bundling together to compose our application.

In our case, we don’t have any external .jar dependencies except for the cxf and the http service for which we are using the in-built features available inside karaf.

feature.xml
<?xml version="1.0" encoding="UTF-8"?>
<features xmlns="http://karaf.apache.org/xmlns/features/v1.0.0"
          name="${project.artifactId}-${project.version}">
    <feature name="karafcxf" description="karaf cxf hello" version="1.0-SNAPSHOT" resolver="(obr)">
        <details>${project.description}</details>
        <feature>http</feature>
        <feature>cxf</feature>
        <feature>this-project-dependants</feature>
        <bundle>mvn:karafcxf/karafcxf.service.impl/1.0-SNAPSHOT</bundle>
        <bundle>mvn:karafcxf/karafcxf.rest/1.0-SNAPSHOT</bundle>
    </feature>
    <feature name="this-project-dependants">
        <!-- Dependent bundles if you have any. This project doesn't have one
        <bundle>mvn:org.apache.commons/commons-lang3/3.1</bundle>
        <bundle>mvn:org.codehaus.jackson/jackson-core-asl/1.9.5</bundle>
        <bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.5</bundle>
        <bundle>mvn:org.codehaus.jackson/jackson-jaxrs/1.9.5</bundle>
        <bundle>mvn:com.google.guava/guava/14.0.1</bundle>
        <bundle>wrap:mvn:org.apache.httpcomponents/httpcore/4.2.4</bundle>
        <bundle>wrap:mvn:org.apache.httpcomponents/httpmime/4.2.5</bundle>
        <bundle>wrap:mvn:org.noggit/noggit/0.5</bundle>
        <bundle>wrap:mvn:org.apache.solr/solr-solrj/4.4.0</bundle>
        -->
    </feature>
</features>

Step 2

Maven plugin configuration to create .kar, which indicates where your feature.xml is located (Note that this file is located inside your karaf project)

pom.xml
 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.karaf.tooling</groupId>
                <artifactId>features-maven-plugin</artifactId>
                <version>2.3.2</version>
                <executions>
                    <execution>
                        <id>create-kar</id>
                        <goals>
                            <goal>create-kar</goal>
                        </goals>
                        <configuration>
                            <featuresFile>${project.basedir}/src/main/resources/feature.xml</featuresFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Wraps

Notice the wrap protocol in front of the mvn protocol in few bundles as in

<bundle>wrap:mvn:org.apache.httpcomponents/httpmime/4.2.5</bundle>

Not all Jars are OSGi ready but they would obviously be used as a dependency in our project. In those cases, Karaf notices the wrapprotocol and bundles the JAR into an OSGi bundle. In case of doubt, just drop off the wrap and Karaf would complain that the jar is not OSGi compatible. (Alternatively, you could open up each of the dependant jars and check their manifest files)

013-08-28 01:38:48,669 | WARN  | raf-2.3.2/deploy | KarArtifactInstaller             | eployer.kar.KarArtifactInstaller  192 | 24 - org.apache.karaf.deployer.kar - 2.3.2 | Unable to install Kar feature xx-xxx-xxxxxx/0.0.0
org.osgi.framework.BundleException: Jar is not a bundle, no Bundle-SymbolicName mvn:org.apache.httpcomponents/httpcore/4.2.4
  at org.apache.karaf.features.internal.FeaturesServiceImpl.installBundleIfNeeded(FeaturesServiceImpl.java:836)[26:org.apache.karaf.features.core:2.3.2]
  at org.apache.karaf.features.internal.FeaturesServiceImpl.doInstallFeature(FeaturesServiceImpl.java:618)[26:org.apache.karaf.features.core:2.3.2]
  at org.apache.karaf.features.internal.FeaturesServiceImpl.installFeatures(FeaturesServiceImpl.java:414)[26:org.apache.karaf.features.core:2.3.2]
  at org.apache.karaf.features.internal.FeaturesServiceImpl.installFeature(FeaturesServiceImpl.java:402)[26:org.apache.karaf.features.core:2.3.2]
  at Proxy508d2419_d21e_4a93_b7fb_26e28d2f03a6.installFeature(Unknown Source)[:]
  at org.apache.karaf.deployer.kar.KarArtifactInstaller.installFeatures(KarArtifactInstaller.java:189)[24:org.apache.karaf.deployer.kar:2.3.2]
  at org.apache.karaf.deployer.kar.KarArtifactInstaller.install(KarArtifactInstaller.java:134)[24:org.apache.karaf.deployer.kar:2.3.2]
  at org.apache.karaf.deployer.kar.KarArtifactInstaller.update(KarArtifactInstaller.java:348)[24:org.apache.karaf.deployer.kar:2.3.2]
  at org.apache.felix.fileinstall.internal.DirectoryWatcher.update(DirectoryWatcher.java:1103)[6:org.apache.felix.fileinstall:3.2.6]
  at org.apache.felix.fileinstall.internal.DirectoryWatcher.update(DirectoryWatcher.java:898)[6:org.apache.felix.fileinstall:3.2.6]
  at org.apache.felix.fileinstall.internal.DirectoryWatcher.process(DirectoryWatcher.java:482)[6:org.apache.felix.fileinstall:3.2.6]
  at org.apache.felix.fileinstall.internal.DirectoryWatcher.run(DirectoryWatcher.java:291)[6:org.apache.felix.fileinstall:3.2.6]

Changing Log Levels

For all our development environment, we would want to increase our log level to get more feedback from Karaf. This could be achieved by modifying the org.ops4j.pax.logging.cfg file located in your <karaf-installation-directory>/etc

Step 5 — Other Maven configuration

Not technically a step because we would have covered this from the beginning anyway. And nothing fancy here.

Parent pom.xml

  1. Has the rest of the sub-modules configured.
  2. The various library dependencies configured
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>karafcxf</groupId>
    <artifactId>karafcxf</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <cxf.version>2.7.5</cxf.version>
    </properties>
    <modules>
        <module>karafcxf.rest</module>
        <module>karafcxf.service.impl</module>
        <module>karafcxf.kar</module>
    </modules>
    <dependencies>
      …
      …

Rest and ServiceImpl pom.xml

The other poms.xmls aren’t interesting

They just affiliate themselves to the parent pom with the parent tag as in

pom.xml
<build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>2.3.7</version>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                        <Bundle-Version>${project.version}</Bundle-Version>
                        <Bundle-Activator>com.nutraspace.coreservices.search.rest.Activator</Bundle-Activator>
                        <Export-Package>com.nutraspace.coreservices.search.rest*;version=${project.version}</Export-Package>
                        <Import-Package>*</Import-Package>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>

Step 6 — Bring up Karaf

My Karaf installation is located at : /Users/Gabriel/apps/apache-karaf-2.3.2

Start Karaf :

<installation-dir>/bin/./karaf

Installing CXF and HTTP services

features:chooseurl cxf 2.7.5

features:install http cxf

Checking whether your bundle is installed and your service running

osgi:list

dxf:list-endpoints

Stop Karaf

Ctrl +D (Please note that Ctrl +C closes the connection abruptly instead of stopping Karaf)

In case of accidental Ctrl+C and if Karaf isn’t starting properly, do a

rm -rf <installation-dir>/data/cache/*

URL Mapping

Like I mentioned earlier, the target request URL will be something like http://localhost:8181/cxf/karafsimple/say/hello/arun and the response would be Hello, arun

1) The HelloRestService interface has all the JAX RS annotations for the URL mapping. Well, technically, the interfaces just maps for say/hello/(name)

2) The karafsimple in the URL is derived from the the JAX RS address in blueprint.xml (about that later in Step 3 — rest.xml).

3) The cxf is a default if you deploy a CXF service on Karaf which obviously you could change.