Feb
Feb
Jan
xmarsh 提供 eclipse 平台的附加工具 plugins,0.1.0 版提供建立憑證中心測試功能。
細節參閱
Nov
源起
想要從 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
觀察
- 簡單使用 BundleContext 秀狀態,可以進一步做控制的實做
- 這類應用要設定三個 XML 似乎過於複雜,沒用到 spring-mvc 的功能,只是簡單 controller 其實可以用 servlet 代替。
- 轉換成 JSTL view 發生找不到 tld 的問題,變成要自帶 WEB-INF/c.tld 並改 uri 的做法,採用 eclipse buddy 機制也許也有用。主要是找不到放在別的包中的 META-INF 目錄下的 c.tld 等檔案。
Nov
源起
原來的 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>
觀察
- 簡單測試目錄註冊方式與整個 Spring-driven HttpService 的可用性,如果 htdoc 包成 jar 的話,還需要再進一步修改與考量。
- spring-driven jetty 也可以支援 osgi-standard http service interface,不過這裡沒有實做。
- 目前只有實做簡單的註冊,接下來可以考慮測看看 jsp/servlet/spring-mvc 等機制。
- 目前註冊端需要依賴 org.eclipse.equinox.common 與 org.springframework.osgi.context.BundleContextAware [參照 Refactoring 一節,移該責任到 jetty 啟動包去]
- 捨棄 OSGi 的 HttpService 代表已偏離標準的約定,這樣一來就需考量與其他 HttpService 實做相容議題。
- 大量採用 remix 的做法會形成大者恆大的現象,幾個巨大 bundle 會讓更新下載時間拉長, 只是部份更新卻需要下載數 MB,這點副作用要考慮。
Nov
源起
除了直接接到 spring MVC 來展現網頁之外,對於外部的程式也需要連結,這裡測試 XFire 掛 Spring 的模式來做。
做法
xfire 有支援 spring ,他將一些預設定義放在 classpath:org/codehaus/xfire/spring/xfire.xml, 不過它是 spring 1.x 版本,即使設定 eclipse buddy 機制取得這個檔,也需要修改,索性直接將這個 檔放到需要的包中。
主要實做下面幾包 bundle 來用。
- haha.remix.xfire
- haha.hello.ws.userdao
- 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
觀察
- 越深入企業層面的應用,越發覺既有程式套件之間的相依性很高,之前用一個 classpath 可以找到, 現在轉 OSGi 變成要考慮匯出匯入問題,變的更為複雜,有時現有套件無法滿足,只好直接轉 remix 大包 來實現或是用 eclipse buddy 機制。
- 如果使用 import-package 方式,似乎無法保證某些服務會先啟動,於是將 ws.client 中設定 Require-Bundle 來讓某些服務先滿足。