Bundle-to-bundle OSGi Network

0

源起

日前進行了一陣子的 hello spring-osgi 系列練習,學著如何將原先緊密結合的軟體分散為較為鬆散的網絡結合方式,對 OSGi 的觀察集中在這裡寫。

因為是一種包包(Bundle)之間的網路,下簡稱為 B2B,並非企業對企業的意思,反而比較像是 P2P 這類網路。

Anemic Bundle Model ?

做這類網絡需要平台支援,這裡用的是 OSGi,練習做著做著想到似乎越做越像某種貧血模式。之前大量採用空轉包 remix bundle 的方式來當這類網路的節點,這樣是否會形成某種 Anemic Bundle Model 現象,花費成本在這類不穩定關係網路平台(OSGi)上,卻只建構不需要這類不穩定關係平台的包(Bundle)。

在整合 spring 上,需要跟很多的既有 lib 連結,來使用這些既有 API 提供的服務,一開始為求方便與習慣,會反覆地作之前提到的 remix 轉包動作,慢慢地感覺到包包之間這種去中心化的連結方式和之前 jar 連結方式的不同,這樣做是否適合。

觀察增加的工作量有不少是調整包與包之間的輸出的服務(API)規格,之前的 Jar-to-jar(J2J) 是綁進來就可以用,屬於比較一種堅固的既有關係,類似人類的血緣關係,B2B 則是一種鬆散的關係,類似朋友之間的關係。

這種關係的不確定性會帶來彈性跟成本,就像交朋友一樣,可選擇,可拒絕,可接受但是也代表維護這種複雜不穩定關係的成本比較高。

朋友關係可不可以像親人關係一樣對待永遠沒有答案,只是採用某種連結特徵意味著某種特徵成本,舉 Anemic Domain Model 為例,採用 Domain Model 這類關係網路需要花費錢在 ORM 上面,但是卻在網路節點上大量採用不需要 ORM 的簡單物件,有點本末倒置。

http://en.wikipedia.org/wiki/Decentralization

http://en.wikipedia.org/wiki/Peer-to-peer

Spring-OSGi = BB2BB Network

Spring 提供另一種 B2B 網路是 Bean-to-bean,這類網路也是類似 J2J 網路,屬於既有堅硬關係的網路。利用 Spring-OSGi 將這個 B2B 包在另一個 B2B 之中形成簡稱的 BB2BB Network 如下,於是 beanHehe 可以連結到 beanHaha,通過堅固穩定關聯的 BeanNetwork 與鬆散不穩定關聯的 BundleNetwork。

BeanHaha-BeanNetwork-Bundle-BundelNetwork-Bundle-BeanNetwork-BeanHehe

這類網路有緊有密的網路關係類似人際網路,也代表人際網路的不特定性也會展現在這類網路, 物件導向利用抽象化映射現實環境以解決問題,這類關係是否可以用服務導向來映射,還要再觀察。

SOA (TODO)

Service-oriented architecture (SOA) http://en.wikipedia.org/wiki/Service-oriented_architecture

Hello Wicket and Spring-OSGi

0

源起

想要嘗試 wicket 開發網頁的方式,並套到 Spring-OSGi 的環境中。

hello wicket

在 web.xml 中可以看出整合的關係,上半部是 Spring 的部份喚起 ContextLoaderListener 並 採用 OsgiWebApplicationContext 當 ContextClass。

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<context-param>
  <param-name>contextClass</param-name>
  <param-value>
  org.springframework.osgi.context.support.OsgiWebApplicationContext
  </param-value>
</context-param>
<listener>
  <listener-class>
  org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>

下半部就是宣告 wicket 的使用,並用 SpringWebApplicationFactory 來注入 bean。

<servlet>
<servlet-name>HelloWorldApplication</servlet-name>
<servlet-class>
  wicket.protocol.http.WicketServlet
</servlet-class>
<init-param>
  <param-name>applicationFactoryClassName</param-name>
  <param-value>
    wicket.spring.SpringWebApplicationFactory
  </param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>HelloWorldApplication</servlet-name>
  <url-pattern>/hello/*</url-pattern>
</servlet-mapping>

applicationContext.xml 中 BundleViewer 是一個 BundleContextAware 介面的實做,利用 BundleContext 可以得到 Bundles 的資料。 HelloWorldApplication 則是 wicket.protocol.http.WebApplication 的實做。 實際上 BundleViewer 這個 bean 會被注到某些 Page 去,但是在這個 xml 檔案中 無法看到這些關聯,注入行為由 HelloWorldApplication 負責。

<bean id="bundleViewer" 
  class="haha.osgi.webconsole.wicket.BundleViewer"/>

<bean id="wicketApplication" 
  class="haha.osgi.webconsole.wicket.HelloWorldApplication" />

HelloWorldApplication 需要加個 init 來注入需要的 bean。

public class HelloWorldApplication extends WebApplication {

@Override
protected void init() {
  // THIS LINE IS IMPORTANT - IT INSTALLS THE COMPONENT INJECTOR THAT WILL
  // INJECT NEWLY CREATED COMPONENTS WITH THEIR SPRING DEPENDENCIES
  addComponentInstantiationListener(new SpringComponentInjector(this));
}

@Override
public Class getHomePage() {
  return HelloWorldPage.class;
}

HelloWorldPage 只要加一行,不能初始化,也不需要 setter。

@SpringBean
private BundleViewer bundleViewer;

觀察

  1. wicket 跟 Spring 的整合有許多方式,這是利用其中 wicket-spring-annot 提供的支援 來做的。
  2. wicket 還需要多練習。

Hello Small World

0

源起

在 Wikipedia 上看到一篇 Newman 的文章,講到網路的數學,主要談分析 social network 資料與建構數學模型。於是想要看看如何在 java 中玩一下。

分析這類網路往往想知道重點 (centrality measures) 所在,簡單的 (degree centrality)看連線多的。越多連線,越重要,越值得推薦。

問題在於連線的對象也應該納入考慮,連到大尾(或是比較喜歡)的,應該比較值得推薦,於是出現特徵向量評估方式 eigenvector centrality。文中還提到另外以路徑 Path 評估網路(重)點的方法。

觀察網路慢慢會發現一種特殊的現象,就是某種網路節點之間的聯繫遠比預期的還要緊密,路邊轉角賣吃的或是太空中的太空人可能是你朋友的朋友或是親戚。

提到這個現象,都會說到 Stanley Milgram 的寄信給陌生人實驗,找個朋友請他轉寄,看看經過幾手可以轉寄到該陌生人手上,觀察結果大都不超過六次,可以看出人際關係的網路遠比一般人認為的還要緊密。

http://en.wikipedia.org/wiki/Eigenvector_centrality

http://en.wikipedia.org/wiki/Social_network

http://en.wikipedia.org/wiki/Small-world_network

http://www.cs.cornell.edu/home/kleinber/

JUNG

Java Universal Network/Graph Framework 是個功能很多的工具,提供兩種世界真小的兩種模擬網路 KleinbergSmallWorldGenerator 跟 WattsBetaSmallWorldGenerator。

很方便的可以掛到 Swing 輸出。

WattsBetaSmallWorldGenerator gen = 
  new WattsBetaSmallWorldGenerator(20, 0.1 , 6);
Graph g = (Graph) gen.generateGraph();
JFrame jf = new JFrame();
VisualizationViewer vv = new VisualizationViewer(new SpringLayout(g),
  new PluggableRenderer());
jf.getContentPane().add(vv);        
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);

或是輸出 PNG 來看看。

VisualizationViewer vv = new VisualizationViewer(new SpringLayout(g),
  new PluggableRenderer(), new Dimension(600,600));
vv.setSize(600,600);
Container c = new Container();
BufferedImage bi = new BufferedImage(600,600,BufferedImage.TYPE_INT_RGB); 
Graphics2D gr = bi.createGraphics();
c.addNotify(); 
c.add(vv); 
c.setVisible(true); 
c.paintComponents(gr); 
ImageIO.write(bi, "png", new File("testdata/smallworld.png"));

結果出現在下面,應該可以再調。

觀察

  1. 畫出的網路很緊,要如何用等下次有機會再多看書。
  2. 數學的知識在畢業時沒帶走,都留給學校了。

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,這點副作用要考慮。

Older posts: 1 2