xmarsh 0.1.3 release

0

新增 ssl 服務支援。

細節參閱

xmarsh@openfoundry

extremepattern.com/XMARSH

xmarsh 0.1.0 release

0

xmarsh 提供 eclipse 平台的附加工具 plugins,0.1.0 版提供建立憑證中心測試功能。

細節參閱

xmarsh@openfoundry

extremepattern.com/XMARSH

Show Me the OSGi Bundles

0

源起

想要從 web/http 介面看到 OSGi 平台的 bundle 狀態,進一步也可以做 start/stop/update 控制。

JMX ?

一開始想到 JMX 轉 Http 的實做,既然用 equinox 就考慮 Resource Monitoring 專案看看, 該案目前還在孕育階段,牽涉的包非常多,請參閱下面連結。

http://www.eclipse.org/equinox/incubator/monitoring/index.php

目前只有小小意圖,簡單的秀出狀態的話,要調整並安裝這些包似乎太麻煩。於是自己來個小包,用 spring-osgi/spring-mvc 支援,實做一個 controller ,支援注入 BundleContext 的 BundleContextAware 介面,然後簡單秀出來看。

為了重複使用方便,將之前練習的 haha.hello.jetty 改為 haha.osgi.jetty。

haha.osgi.webconsole

使用之前做的 haha.osgi.jetty.HttpService ,只要設定根目錄,會自行找到 WEN-INF/web.xml 並啟動需要的 controller,所以這裡不寫註冊 Servlet 的程式, 改回原來習慣的 web.xml 方式。

META-INF/MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: haha.osgi.webconsole
Bundle-SymbolicName: haha.osgi.webconsole
Bundle-Version: 1.0.0
Bundle-Localization: plugin
Import-Package: javax.servlet;version="2.4.0",
 javax.servlet.http;version="2.4.0",
 org.osgi.framework;version="1.3.0" 
Require-Bundle: haha.remix.spring,
 haha.osgi.jetty

META-INF/spring/webconsole-beans.xml

<osgi:reference id="httpService" 
  interface="haha.osgi.jetty.HttpService" />

<bean id="register" 
  class="haha.osgi.jetty.Register" init-method="init" 
  destroy-method="destroy">
  <property name="httpService" ref="httpService" />
  <property name="webappDir" value="webapp" />
  <property name="contextPath" value="/osgi" />
</bean>
webapp/WEB-INF/web.xml
<servlet>
<servlet-name>status</servlet-name>
<servlet-class>
  org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
  <param-name>contextClass</param-name>
  <param-value>
  org.springframework.osgi.context.support.OsgiWebApplicationContext
  </param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>status</servlet-name>
<url-pattern>/status</url-pattern>
</servlet-mapping>

webapp/WEB-INF/status-servlet.xml

<bean
  class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
  <value>/*=statusController</value>
</property>
</bean>
<bean id="statusController" 
  class="haha.osgi.webconsole.StatusController">
</bean>

haha.osgi.webconsole.StatusController

public class StatusController extends AbstractController 
  implements BundleContextAware {

  @Override
  protected ModelAndView handleRequestInternal(HttpServletRequest req,
    HttpServletResponse res) throws Exception {

  res.setContentType("text/plain; charset=UTF-8");
  PrintWriter out = res.getWriter();
  Bundle[] bundles = bundleContext.getBundles();
  for (Bundle bundle : bundles) {
    int state = bundle.getState();
    String status = "ACTIVE";
    switch (state) {
      case Bundle.INSTALLED:
        status = "INSTALLED";
        break;
    [SKIP]

結果

http://127.0.0.1/osgi/status

Bundle 0 / system.bundle / ACTIVE
Bundle 1 / haha.remix.jetty / ACTIVE
Bundle 2 / haha.remix.spring / ACTIVE
Bundle 5 / org.apache.commons.logging / ACTIVE
Bundle 6 / org.eclipse.equinox.common / ACTIVE
Bundle 7 / org.eclipse.osgi.services / ACTIVE
Bundle 14 / haha.osgi.webconsole / ACTIVE
Bundle 16 / org.eclipse.equinox.log / ACTIVE
Bundle 18 / haha.osgi.jetty / ACTIVE

觀察

  1. 簡單使用 BundleContext 秀狀態,可以進一步做控制的實做
  2. 這類應用要設定三個 XML 似乎過於複雜,沒用到 spring-mvc 的功能,只是簡單 controller 其實可以用 servlet 代替。
  3. 轉換成 JSTL view 發生找不到 tld 的問題,變成要自帶 WEB-INF/c.tld 並改 uri 的做法,採用 eclipse buddy 機制也許也有用。主要是找不到放在別的包中的 META-INF 目錄下的 c.tld 等檔案。

Spring-driven or OSGi-standard HttpService

0

源起

原來的 OSGi HtppService 是個相當簡潔的介面,不提供太多細部的設定考量,也就是說整個 HttpService 的某些特質取決於底層的實做預設值,並不能在註冊時加以調整。

舉 welcome file 的例子來說,不同實做,出現的行為是 404/500 都不確定,太粗的介面提供小型裝置的容易實現性,但是對習慣開發 server-side 的開發者而言,實在不方便。

equinox 用 jetty 當 servlet 2.4 實做,jetty 5 是個成熟的實做,許多企業級應用都可找到,equinox 採用 ProxyServlet 轉接方式,來轉出符合 OSGi HttpService 的介面。

這裡不去修改這個 ProxyServlet 機制,而是動手另外實做類似 org.eclipse.equinox.http.jetty 的 Spring-driven HttpService 功能,同時轉用 jetty 6.1 來測試,這裡用 haha.remix.jetty 來包一大包,另外用 haha.hello.jetty 來啟動 jetty server,用 haha.hello.httpdoc 來註冊 http://localhost/。

haha.remix.jetty

不需啟動,基本上是個 lib 為主的 bundle。

META-IMF/MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Jetty Plug-in
Bundle-SymbolicName: haha.remix.jetty
Bundle-Version: 6.1.0
Bundle-Localization: plugin
Import-Package: org.osgi.framework;version="1.3.0" 
Bundle-ClassPath: .,
 lib/jetty-6.1.0pre2.jar,
 lib/jetty-util-6.1.0pre2.jar,
 lib/servlet-api-2.5-6.1.0pre2.jar
Export-Package: javax.servlet;version="2.5.0",
 javax.servlet.http;version="2.5.0",
 javax.servlet.resources;version="2.5.0",
 org.mortbay.component,
 org.mortbay.io,
 org.mortbay.io.bio,....[SKIP]

haha.hello.jetty

META-IMF/MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: haha.hello.jetty
Bundle-SymbolicName: haha.hello.jetty
Bundle-Version: 1.0.0
Bundle-Localization: plugin
Import-Package: org.osgi.framework;version="1.3.0" 
Require-Bundle: haha.remix.spring,
 haha.remix.jetty
Bundle-ClassPath: .
Export-Package: haha.hello.jetty

將 jetty 用 osgi-service 註冊到 OSGi 環境去。

MEAT-INF/spring/jetty-beans.xml

<bean id="httpServiceImpl" 
  class="haha.hello.jetty.HttpServiceImpl" 
 init-method="init" destroy-method="destroy">
  <property name="port" value="80" />
  <property name="debugEnable" value="true" />
</bean>
<osgi:service ref="httpServiceImpl" interface="haha.hello.jetty.HttpService"/>

haha.hello.jetty.HttpService

public interface HttpService {
  void addWebApp(URL fileURL, String contextPath);
}

haha.hello.httpdoc

這是比較麻煩的地方,主要是轉換檔案位置的問題,這裡用到 org.eclipse.equinox.common 的支援, 使用 org.eclipse.core.runtime.FileLocator 來轉檔案的位置給 haha.hello.jetty 知道。

META-IMF/MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: haha.hello.httpdoc
Bundle-SymbolicName: haha.hello.httpdoc
Bundle-Version: 1.0.0
Bundle-Localization: plugin
Import-Package: javax.servlet;version="2.4.0",
 javax.servlet.http;version="2.4.0",
 org.osgi.framework;version="1.3.0",
 org.osgi.service.component;version="1.0.0",
 org.osgi.service.log;version="1.3.0",
 org.springframework.osgi.context
Require-Bundle: org.eclipse.equinox.common,
 haha.hello.jetty

將目錄所在用 osgi-reference 註冊到 jetty 環境去。

MEAT-INF/spring/httpdoc-beans.xml

<osgi:reference id="httpService" 
  interface="haha.hello.jetty.HttpService" />

<bean id="register" 
  class="haha.hello.httpdoc.Register" init-method="init" 
  destroy-method="destroy">
  <property name="httpService" ref="httpService" />
</bean>

haha.hello.httpdoc.Register 須實做 BundleContextAware 讓 Spring-OSGi 注入 BundleContext 來用。

public class Register implements BundleContextAware{

 private HttpService httpService;
 private BundleContext bundleContext;

 public void init() throws IOException{
  Bundle bundle = bundleContext.getBundle();
  URL entry = bundle.getEntry("htdoc");
  URL dir = FileLocator.toFileURL(entry);
  httpService.addWebApp(dir, "/");
 }

結果可以在 http://localhost/ 看到 haha.hello.httpdoc bundle 中的 htdoc 目錄。

Refactoring

為了減少重複寫這些註冊 Register 以及依賴 org.eclipse.equinox.common 與 org.springframework.osgi.context.BundleContextAware,將這個責任移到 haha.hello.jetty 去,客端只要負責實體化並注入 HttpService 即可,如下面情形。

<bean id="register" 
    class="haha.hello.jetty.Register" init-method="init" destroy-method="destroy">
    <property name="httpService" ref="httpService" />
    <property name="webappDir" value="htdoc" />
    <property name="contextPath" value="/project" />
</bean>    

觀察

  1. 簡單測試目錄註冊方式與整個 Spring-driven HttpService 的可用性,如果 htdoc 包成 jar 的話,還需要再進一步修改與考量。
  2. spring-driven jetty 也可以支援 osgi-standard http service interface,不過這裡沒有實做。
  3. 目前只有實做簡單的註冊,接下來可以考慮測看看 jsp/servlet/spring-mvc 等機制。
  4. 目前註冊端需要依賴 org.eclipse.equinox.common 與 org.springframework.osgi.context.BundleContextAware [參照 Refactoring 一節,移該責任到 jetty 啟動包去]
  5. 捨棄 OSGi 的 HttpService 代表已偏離標準的約定,這樣一來就需考量與其他 HttpService 實做相容議題。
  6. 大量採用 remix 的做法會形成大者恆大的現象,幾個巨大 bundle 會讓更新下載時間拉長, 只是部份更新卻需要下載數 MB,這點副作用要考慮。

Hello Xfire Web Services and OSGi

0

源起

除了直接接到 spring MVC 來展現網頁之外,對於外部的程式也需要連結,這裡測試 XFire 掛 Spring 的模式來做。

XFire

Chapter 17.5. Web Services

XFire Spring support

XFire JSR 181 Annotations

做法

xfire 有支援 spring ,他將一些預設定義放在 classpath:org/codehaus/xfire/spring/xfire.xml, 不過它是 spring 1.x 版本,即使設定 eclipse buddy 機制取得這個檔,也需要修改,索性直接將這個 檔放到需要的包中。

主要實做下面幾包 bundle 來用。

  1. haha.remix.xfire
  2. haha.hello.ws.userdao
  3. haha.hello.ws.client

haha.remix.xfire

這是個大轉包,沒有加程式,只是改 MANIFEST.MF 來協助。

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: haha.remix.xfire
Bundle-SymbolicName: haha.remix.xfire
Bundle-Version: 1.0.0
Bundle-Activator: haha.remix.xfire.Activator
Bundle-Localization: plugin
Import-Package: javax.servlet;version="2.4.0",
 javax.servlet.http;version="2.4.0",
 org.apache.commons.logging,
 org.osgi.framework;version="1.3.0" 
Bundle-ClassPath: lib/wstx-asl-3.1.0.jar,
 lib/stax-api-1.0.1.jar,
 lib/wsdl4j-1.5.2.jar,
 lib/xfire-all-1.2.2.jar,
 lib/xfire-jsr181-api-1.0-M1.jar,
 .,
 lib/activation-1.1.jar,
 lib/jdom-1.0.jar,
 lib/XmlSchema-1.1.jar
Export-Package: com.ctc.wstx.stax,
 javax.jws,
 javax.xml.stream,
 org.apache.ws.commons.schema,
 org.codehaus.stax2,
........[skip]
Require-Bundle: org.apache.commons.httpclient;resolution:=optional,
 org.apache.commons.codec;resolution:=optional,
 haha.remix.spring;resolution:=optional

這裡需注意 require bundle 都是選擇性的,那是因為有些特定需求要到客端包要求的時候,才會需要, 也不一定會有這些客端包,所以一直掛著讓他一定要這個附屬包也說不過去,所以用選擇性掛上。

haha.hello.ws.userdao

META-INF/spring/bean.xml

<osgi:reference id="httpService" 
  interface="org.osgi.service.http.HttpService"/>

<bean name="servletRegister" 
   class="haha.hello.ws.userdao.ServletRegister" init-method="init">
      <property name="httpService" ref="httpService"/>
</bean>

haha.hello.ws.userdao.ServletRegister 負責啟動 WEB-INF/xx.xml 與註冊服務到 osgi:reference 的 org.osgi.service.http.HttpService

System.setProperty("javax.xml.stream.XMLInputFactory",
  "com.ctc.wstx.stax.WstxInputFactory");
System.setProperty("javax.xml.stream.XMLOutputFactory",
  "com.ctc.wstx.stax.WstxOutputFactory");
System.setProperty("javax.xml.stream.XMLEventFactory",
  "com.ctc.wstx.stax.WstxEventFactory");

DispatcherServlet servlet = new DispatcherServlet();
servlet.setNamespace("hello-servlet");

OsgiWebApplicationContext owac = new OsgiWebApplicationContext();
servlet.setContextClass(owac.getClass());
httpService.registerServlet("/services", servlet, null ,null);

WEB-INF/hello-servlet.xml

<import resource="xfire.xml"/>
<osgi:reference id="userDao" 
     interface="haha.hello.jpa.userdao.UserDao"/>

<bean name="helloImpl" class="haha.hello.ws.userdao.HelloImpl">     
   <property name="userDao" ref="userDao"/>
</bean>

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
   <property name="mappings">
            <value>/hello=wsController</value>
   </property>
</bean>

<bean id="wsController" 
      class="org.codehaus.xfire.spring.remoting.XFireExporter">
        <property name="serviceFactory">
            <ref bean="xfire.annotationServiceFactory"/>
        </property>
        <property name="xfire">
            <ref bean="xfire"/>
        </property>
        <property name="serviceBean">
            <ref bean="helloImpl"/>
        </property>
</bean>

WEB-INF/xfire.xml 取自 xfire classpath:org/codehaus/xfire/spring/xfire.xml 修改一下 singleton 設定而已。

這一包主要將 haha.hello.ws.userdao.Hello 放到 WebServic 去。

haha.hello.ws.userdao.Hello

package haha.hello.ws.userdao;

import javax.jws.WebMethod;
import javax.jws.WebResult;
import javax.jws.WebService;

@WebService(name = "HelloService",  targetNamespace = "http://www.openuri.org/2004/04/HelloWorld")
public interface Hello {
  @WebMethod
  @WebResult
  String getName();
}

haha.hello.ws.userdao.HelloImpl 會注入 osgi:reference 的 haha.hello.jpa.userdao.UserDao 來用。

package haha.hello.ws.userdao;

import haha.hello.jpa.userdao.UserDao;
import javax.jws.WebService;

@WebService(endpointInterface = "haha.hello.ws.userdao.Hello")
public class HelloImpl implements Hello {

 private UserDao userDao;

 private String name = "foo";

 public String getName() {
   return name + userDao.loadUsers().size();
 }
....[SKIP].....

MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: haha.hello.ws.userdao
Bundle-SymbolicName: haha.hello.ws.userdao
Bundle-Version: 1.0.0
Bundle-Activator: haha.hello.ws.userdao.Activator
Bundle-Localization: plugin
Import-Package: com.ctc.wstx.stax,
 javax.jws,
 javax.servlet;version="2.4.0",
 javax.servlet.http;version="2.4.0",
 org.osgi.framework;version="1.3.0",
 org.osgi.service.http;version="1.2.0",
 org.springframework.osgi.context.support
Require-Bundle: haha.remix.spring,
 org.eclipse.equinox.http.jetty,
 haha.hello.jpa.userdao,
 haha.remix.xfire
Export-Package: haha.hello.ws.userdao

啟動後可以直接開瀏覽器到 http://127.0.0.1/services/hello?wsdl 觀察 WSDL

haha.hello.ws.client

陽春型客戶端,只是取用 getName 輸出看看而已,沒有用到 WSDL2JAVA 來轉成 java。

package haha.hello.ws.client;

import java.net.URL;
import org.codehaus.xfire.client.Client;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {

 public void start(BundleContext context) throws Exception {
    Client client = new Client(
            new URL("http://127.0.0.1/services/hello?wsdl"));
        Object[] results = client.invoke("getName",null);
        System.out.println(results[0]);
 }
...[SKIP]....

MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: haha.hello.ws.client
Bundle-SymbolicName: haha.hello.ws.client
Bundle-Version: 1.0.0
Bundle-Activator: haha.hello.ws.client.Activator
Bundle-Localization: plugin
Import-Package: org.apache.commons.logging,
 org.codehaus.xfire.client,
 org.osgi.framework;version="1.3.0" 
Bundle-ClassPath: .
Require-Bundle: org.apache.commons.httpclient,
 org.apache.commons.codec,
 haha.hello.ws.userdao,
 haha.remix.xfire

觀察

  1. 越深入企業層面的應用,越發覺既有程式套件之間的相依性很高,之前用一個 classpath 可以找到, 現在轉 OSGi 變成要考慮匯出匯入問題,變的更為複雜,有時現有套件無法滿足,只好直接轉 remix 大包 來實現或是用 eclipse buddy 機制。
  2. 如果使用 import-package 方式,似乎無法保證某些服務會先啟動,於是將 ws.client 中設定 Require-Bundle 來讓某些服務先滿足。

Older posts: 1 2 3 4