上次的
OSGi&Wicket - To locate class and resource in bundle context. 中提到,如何讓Spring+Wicket 在Eclipse Virgo Server上運作,並通過修改WebApplication裡的設定如classResolver and ResourceStreamLocator , 使得Bundle 的wicket 元件能被使用。
這次要實作自訂的Wicket tag , 達成簡單的Plugin , 原始的參考來自於
Wicket Osgi Extension ,不過因為他是embedded apache felix ,所以我沒使用,而是透過source code去使用。
這邊的概念就是在Wicket HTML 頁面裡面定義一個Tag , 大概就像:
<wicket:extension-point id="test.extension.panel" />
這樣我們只要在一個Bundle 裡面定義好一個Extension class (extends Panel) , 然後再透過Bundle Activator 取得ExtensionService 來註冊這個Extension-Point id&class ,然後在Web Bundle裡碰到有這個Extension-point的tag會去看有沒有register的id & class, 有的話就取出來然後append 到html page裡。
其他的設定請參考
OSGi&Wicket - To locate class and resource in bundle context. , 這邊只例出Extension 相關的class實作 & 例子。
效果大概如下圖所示:
結果就會長這樣:
在原先的Bundle - OSGiWebApp2.Interface , 新增一個Class ExtensionPoints.java
裡面定義每個ExtensionPoints 的id
package com.osgiweb.apps2.OSGiWebApp2;
public final class ExtensionPoints {
private ExtensionPoints() {
}
public static final String HOMEPAGE_PANEL = "test.extension.panel";
public static final String HOMEPAGE_MENU = "test.extension.menu";
}
然後在Bundle - OSGiWebApp2.Web 裡面新增以下Class
ExtensionPointContainer.java , extends MarkupContainer, 主要是Convert <wicket:extension-point />
to <wicket:extension-point>...</wicket:extension-point>,
package com.osgi;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.parser.XmlTag;
public class ExtensionPointContainer extends MarkupContainer {
private static final long serialVersionUID = 1L;
public ExtensionPointContainer(String id) {
super(id);
System.out.println("ExtensionPointContainer start");
}
/**
* {@inheritDoc}
*
* @see org.apache.wicket.MarkupContainer#isTransparentResolver()
*/
@Override
public boolean isTransparentResolver() {
return true;
}
/**
* {@inheritDoc}
*
* @see org.apache.wicket.Component#onComponentTag(org.apache.wicket.markup.ComponentTag)
*/
@Override
protected void onComponentTag(final ComponentTag tag) {
// Convert <wicket:extension-point /> into
// <wicket:extension-point>...</wicket:extension-point>
if (tag.isOpenClose()) {
tag.setType(XmlTag.OPEN);
}
super.onComponentTag(tag);
}
}
OsgiExtensionPointResolver.java , 實作IComponentResolver , 然後在WebApplication 裡面還要做set的動作,這樣wicket 才會知道我們自訂的tag
package com.osgi;
import java.util.Collection;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupException;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.WicketTag;
import org.apache.wicket.markup.parser.filter.WicketTagIdentifier;
import org.apache.wicket.markup.resolver.IComponentResolver;
import org.osgi.framework.BundleContext;
import org.springframework.osgi.context.BundleContextAware;
import com.osgiweb.apps2.OSGiWebApp2.Interface.Extension;
import com.osgiweb.apps2.OSGiWebApp2.Interface.IWicketExtensionService;
public class OsgiExtensionPointResolver implements IComponentResolver {
private static final long serialVersionUID = 1L;
private BundleContext bundleCtx;
private static final String TAG_NAME = "extension-point";
static {
// register "wicket:extension-point"
WicketTagIdentifier.registerWellKnownTagName(TAG_NAME);
}
public OsgiExtensionPointResolver(BundleContext btx){
System.out.println("constructor for OsgiExtensionPointResolver");
this.bundleCtx = btx;
}
/**
* {@inheritDoc}
*
* @see org.apache.wicket.markup.resolver.IComponentResolver#resolve(org.apache.wicket.MarkupContainer,
* org.apache.wicket.markup.MarkupStream,
* org.apache.wicket.markup.ComponentTag)
*/
public boolean resolve(MarkupContainer container, MarkupStream markupStream, ComponentTag tag) {
if (tag instanceof WicketTag) {
WicketTag wtag = (WicketTag) tag;
System.out.println("wtag.getName() =" wtag.getName());
if (TAG_NAME.equalsIgnoreCase(wtag.getName())) {
String extensionPointId = wtag.getAttributes().getString("id");
if ((extensionPointId == null) || (extensionPointId.trim().length() == 0)) {
throw new MarkupException(
"Wrong format of <wicket:extension-point id='xxx'>: attribute 'id' is missing");
}
System.out.println("extensionPointId = " extensionPointId);
IWicketExtensionService s = (IWicketExtensionService)this.bundleCtx.getService(this.bundleCtx.getServiceReference(IWicketExtensionService.class.getName()));
System.out.println("IWicketExtensionService s = " s);
//extensions contain all extension-point id by class name,
//such as extensions =[class com.osgiweb.apps2.OSGiWebApp2.HomePages.SimpleMenuContribution]
Collection<Class<? extends Extension>> extensions = s.findExtensions(extensionPointId);
System.out.println("extensions =" extensions);
if (extensions != null && !extensions.isEmpty()) {
for (Class<? extends Extension> ext : extensions) {
try {
final String compId = "_extension_" extensionPointId "_"
container.getPage().getAutoIndex();
System.out.println("compId =" compId);
Extension comp = ext.getConstructor(String.class).newInstance(compId);
System.out.println("comp =" comp);
comp.setRenderBodyOnly(container.getApplication().getMarkupSettings().getStripWicketTags());
System.out.println("container ->" container.getApplication().getMarkupSettings().getStripWicketTags());
container.autoAdd(comp, markupStream);
markupStream.setCurrentIndex(markupStream.getCurrentIndex() - 1);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
markupStream.setCurrentIndex(markupStream.getCurrentIndex() 1);
} else {
ExtensionPointContainer comp = new ExtensionPointContainer(extensionPointId
"_" container.getPage().getAutoIndex());
comp.setRenderBodyOnly(container.getApplication().getMarkupSettings()
.getStripWicketTags());
container.autoAdd(comp, markupStream);
}
return true;
}
}
// We were not able to handle the tag
return false;
}
}
再來是WicketExtensionServiceImpl.java, 實作OSGiWebApp2.Interface下的IWicketExtensionService, 主要是透過WebApplication 發佈出來的osgi service 在每個bundle註冊Extension point
package com.service;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.osgiweb.apps2.OSGiWebApp2.Interface.Extension;
import com.osgiweb.apps2.OSGiWebApp2.Interface.IWicketExtensionService;
public class WicketExtensionServiceImpl implements IWicketExtensionService {
//Thread
private ConcurrentHashMap<String, Set<Class<? extends Extension>>> extensionsRegistry = new ConcurrentHashMap<String, Set<Class<? extends Extension>>>();
/**
* {@inheritDoc}
*
* @see net.javaforge.wicket.osgi.service.IWicketExtensionService#registerExtension(java.lang.String,
* java.lang.Class)
*/
public void registerExtension(String extensionPointId,
Class<? extends Extension> extension) {
if (!this.extensionsRegistry.containsKey(extensionPointId)) {
this.extensionsRegistry.put(extensionPointId,
new HashSet<Class<? extends Extension>>());
}
Set<Class<? extends Extension>> extensions = this.extensionsRegistry
.get(extensionPointId);
extensions.add(extension);
}
/**
* {@inheritDoc}
*
* @see net.javaforge.wicket.osgi.service.IWicketExtensionService#findExtensions(java.lang.String)
*/
public Collection<Class<? extends Extension>> findExtensions(
String extensionPointId) {
return this.extensionsRegistry.get(extensionPointId);
}
/**
* {@inheritDoc}
*
* @see net.javaforge.wicket.osgi.service.IWicketExtensionService#unregisterExtension(java.lang.Class)
*/
public void unregisterExtension(Class<? extends Extension> extension) {
Collection<Set<Class<? extends Extension>>> values = this.extensionsRegistry
.values();
for (Set<Class<? extends Extension>> subset : values) {
Iterator<Class<? extends Extension>> it = subset.iterator();
while (it.hasNext()) {
Class<? extends Extension> ext = it.next();
if (ext == extension)
it.remove();
}
}
}
}
然後在原本的home.html 做些修改,
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Wickety OSGi!</title>
</head>
<body>
<p wicket:id="message">replace me</p>
Wicket extendsion poinrt for test.extension.panel as below : <Br>
<wicket:extension-point id="test.extension.panel" />
</body>
</html>
然後是WicketApplication.java, 這邊要在init()時 getPageSetting, 然後addComponentResolver
package com.app;
import org.apache.wicket.Page;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
import org.osgi.framework.BundleContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.osgi.context.BundleContextAware;
import org.springframework.stereotype.Component;
import com.osgi.OsgiClassResolver;
import com.osgi.OsgiExtensionPointResolver;
import com.osgi.OsgiResourceStreamLocator;
import com.osgiweb.apps2.OSGiWebApp2.Interface.IWicketExtensionService;
import com.osgiweb.apps2.OSGiWebApp2.Interface.IWicketPageService;
import com.page.home;
import com.service.WicketExtensionServiceImpl;
import com.service.WicketPageServiceImpl;
@Component
public class WicketApplication extends WebApplication implements ApplicationContextAware,BundleContextAware{
private static final String DEFAULT_ENCODING = "UTF-8";
private BundleContext bundleCtx;
@Autowired
private ApplicationContext applicationContext;
@Override
public Class<? extends Page> getHomePage() {
return home.class;
}
protected void init() {
//
this.getApplicationSettings().setClassResolver(new OsgiClassResolver());
this.getResourceSettings().setResourceStreamLocator(new OsgiResourceStreamLocator());
this.getPageSettings().addComponentResolver(new OsgiExtensionPointResolver(this.bundleCtx));
super.init();
addComponentInstantiationListener(new SpringComponentInjector(this, applicationContext, true));
getMarkupSettings().setDefaultMarkupEncoding(DEFAULT_ENCODING);
registerPageService(this);
}
// @Override
// public String getConfigurationType() {
// return WebApplication.DEVELOPMENT;
// }
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
// public static WicketApplication get() {
// return (WicketApplication) WebApplication.get();
// }
@Override
public void setBundleContext(BundleContext bundleContext) {
this.bundleCtx = bundleContext;
System.out.println("bundle ctx = " bundleCtx );
}
public void registerPageService(WebApplication app){
System.out.println("register pageService");
System.out.println("class = " IWicketPageService.class.getName());
this.bundleCtx.registerService(IWicketPageService.class.getName(), new WicketPageServiceImpl(this), null);
System.out.println("register pageService finished");
this.bundleCtx.registerService(IWicketExtensionService.class.getName(),new WicketExtensionServiceImpl(), null);
}
}
OSGiWebApp2.web修改後的MANIFEST.mf 檔
Manifest-Version: 1.0
Import-Bundle: com.springsource.org.apache.taglibs.standard;version="[
1.1.2,1.3)"
Bundle-Version: 1.0.0
Tool: Bundlor 1.0.0.RELEASE
Bundle-Name: OSGiWebApp web
Import-Library: org.springframework.spring;version="[3.0,3.1)"
Bundle-ManifestVersion: 2
Bundle-SymbolicName: OSGiWebApp2.web
Web-ContextPath: OSGiWebApp
Import-Package: com.osgiweb.apps2.OSGiWebApp2.HomePages,
com.osgiweb.apps2.OSGiWebApp2.Interface,
javax.servlet.jsp.jstl.core;version="[1.1.2,1.2.0)",
org.apache.wicket;version="1.4.16",
org.apache.wicket.application;version="1.4.16",
org.apache.wicket.markup;version="1.4.16",
org.apache.wicket.markup.html;version="1.4.16",
org.apache.wicket.markup.html.link;version="1.4.16",
org.apache.wicket.markup.html.basic;version="1.4.16",
org.apache.wicket.markup.parser;version="1.4.16",
org.apache.wicket.markup.parser.filter;version="1.4.16",
org.apache.wicket.markup.resolver;version="1.4.16",
org.apache.wicket.protocol.http;version="1.4.16",
org.apache.wicket.settings;version="1.4.16",
org.apache.wicket.spring;version="1.4.16",
org.apache.wicket.spring.injection.annot;version="1.4.16",
org.apache.wicket.util.file;version="1.4.16",
org.apache.wicket.util.value;version="1.4.16",
org.apache.wicket.util.resource.locator;version="1.4.16",
org.eclipse.virgo.web.dm;version="[2.0.0,3.0.0)",
org.slf4j;version="1.6.1",
org.springframework.beans;version="[3.0.0,3.1.0)",
org.springframework.beans.factory.annotation;version="[3.0.0,3.1.0)",
org.springframework.context;version="[3.0.0,3.1.0)",
org.springframework.osgi.context,
org.springframework.stereotype;version="[3.0.0,3.1.0)",
org.springframework.web.servlet.mvc.annotation;version="[3.0.0,3.1.0)"
Export-Package: com.app;version="1.0.0";
uses:="org.apache.wicket.protocol.http,
org.osgi.framework,
org.springframework.beans,
org.springframework.beans.factory.annotation,
org.springframework.context,
org.springframework.osgi.context,
org.springframework.stereotype",
com.osgi;version="1.0.0";
uses:="org.apache.wicket,
org.apache.wicket.application,
org.apache.wicket.markup,
org.apache.wicket.markup.resolver,
org.osgi.framework,
org.springframework.osgi.context",
com.page;version="1.0.0";uses:="org.apache.wicket.markup.html",
com.service;version="1.0.0";
uses:="com.osgiweb.apps2.OSGiWebApp2.Interface,
org.apache.wicket.protocol.http,
org.apache.wicket.request.target.coding,
org.apache.wicket.util.lang,
org.springframework.beans,
org.springframework.context"
**Update 2011/08/03, 在Virgo 3.0.x RC上 新的設定如下,
原本的設定在3.0.xRc版本裡面會找不到class ServerOsgiBundleXmlWebApplicationContext,要在MANIFEST.mf檔裡面加入Import-Bundle的部份
加入以下package
org.eclipse.virgo.web.dm
完整如下,也許有辦法不用import-bundle ....
Manifest-Version: 1.0
Export-Package: com.app;version="1.0.0";
uses:="org.apache.wicket.protocol.http,
org.osgi.framework,
org.springframework.beans,
org.springframework.beans.factory.annotation,
org.springframework.context,
org.springframework.osgi.context,
org.springframework.stereotype",
com.osgi;version="1.0.0";
uses:="org.apache.wicket,
org.apache.wicket.application,
org.apache.wicket.markup,
org.apache.wicket.markup.resolver,
org.apache.wicket.util.file,
org.apache.wicket.util.resource,
org.apache.wicket.util.resource.locator,
org.osgi.framework,
org.springframework.osgi.context",
com.page;version="1.0.0";uses:="org.apache.wicket.markup.html",
com.service;version="1.0.0";
uses:="com.osgiweb.apps2.OSGiWebApp2.Interface,
org.apache.wicket.protocol.http,
org.apache.wicket.request.target.coding,
org.apache.wicket.util.lang,
org.springframework.beans,
org.springframework.context"
Import-Bundle: com.springsource.org.apache.taglibs.standard;version="[
1.1.2,1.3)"
Bundle-Version: 1.0.0
Tool: Bundlor 1.0.0.RELEASE
Bundle-Name: OSGiWebApp web
Import-Library: org.springframework.spring;version="[3.0,3.1)"
Import-Bundle: org.eclipse.virgo.web.dm
Bundle-ManifestVersion: 2
Bundle-SymbolicName: OSGiWebApp2.web
Web-ContextPath: OSGiWebApp
Bundle-ClassPath: .,WEB-INF/classes
Import-Package: com.app;
com.osgiweb.apps2.OSGiWebApp2.Interface,
org.apache.wicket,
org.apache.wicket.application,
org.apache.wicket.markup,
org.apache.wicket.markup.html,
org.apache.wicket.markup.html.basic,
org.apache.wicket.markup.parser,
org.apache.wicket.markup.parser.filter,
org.apache.wicket.markup.resolver,
org.apache.wicket.protocol.http,
org.apache.wicket.request.target.coding,
org.apache.wicket.settings,
org.apache.wicket.spring.injection.annot,
org.apache.wicket.util.file,
org.apache.wicket.util.lang,
org.apache.wicket.util.resource,
org.apache.wicket.util.resource.locator,
org.apache.wicket.util.string,
org.apache.wicket.util.value,
org.apache.wicket.spring,
org.osgi.framework,
org.springframework.beans,
org.springframework.beans.factory.annotation,
org.springframework.context,
org.springframework.osgi.context,
org.springframework.stereotype,
org.springframework.web.servlet.mvc.annotation
然後是Bundle - OSGiWebApp2.HomePages ,
先新增SimpleMenuContribution.java, SimpleMenuContribution.html
SimpleMenuContribution.java
package com.osgiweb.apps2.OSGiWebApp2.HomePages;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import com.osgiweb.apps2.OSGiWebApp2.Interface.Extension;
public class SimpleMenuContribution extends Extension {
private static final long serialVersionUID = 1L;
public SimpleMenuContribution(String id) {
super(id);
this.add(new BookmarkablePageLink<String>("page1", home2.class));
// this.add(new BookmarkablePageLink<String>("page2", MyPage2.class));
}
}
SimpleMenuContribution.html
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org" >
<wicket:panel>
<a href="#" wicket:id="page1" style="margin-left: 10px; margin-right: 10px;">page1</a>
</wicket:panel>
</html>
然後修改Activator.java, 新增IWicketExtensionService wicketPageService;的部份, 可以註冊extension point id,class,
package com.osgiweb.apps2.OSGiWebApp2.HomePages;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.osgi.context.BundleContextAware;
import com.osgiweb.apps2.OSGiWebApp2.ExtensionPoints;
import com.osgiweb.apps2.OSGiWebApp2.Interface.IWicketExtensionService;
import com.osgiweb.apps2.OSGiWebApp2.Interface.IWicketPageService;
public class Activator implements BundleContextAware {
Logger logg = LoggerFactory.getLogger(Activator.class);
private BundleContext bundleCtx;
IWicketPageService wicketService;
IWicketExtensionService wicketPageService;
public void start(){
// wicketService.mountBookmarkablePage( "home2", home2.class);
wicketPageService.registerExtension(ExtensionPoints.HOMEPAGE_PANEL, SimpleMenuContribution.class);
// logg.info("check logback object1 = {}, object2={}","MOMO-x","YOtsubaObjects");
}
public void stop(){
}
public void setWicketService(IWicketPageService wicketService) {
System.out.println("wicketService =" wicketService);
this.wicketService = wicketService;
}
public void setBundleContext(BundleContext bundleContext) {
System.out.println("bundleCtx =" bundleCtx);
this.bundleCtx = bundleContext;
}
public void setWicketPageService(IWicketExtensionService wicketPageService) {
this.wicketPageService = wicketPageService;
}
}
也別忘了修改bundle-context.xml,跟bundle-context-osgi.xml
bundle-context.xml
<?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">
<!-- regular spring configuration file defining the beans for this bundle.
The configuration of OSGi definitions is kept in a separate configuration
file so that this file can easily be used for integration testing outside
of an OSGi environment -->
<bean id="actavitor" class="com.osgiweb.apps2.OSGiWebApp2.HomePages.Activator"
init-method="start" destroy-method="stop">
<property name="wicketService" ref="iwicketService" />
<property name="wicketPageService" ref="iwicketPageService" />
</bean>
</beans>
bundle-context-osgi.xml
<?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">
<!-- definitions using elements of the osgi namespace can be included
in this file. There is no requirement to keep these definitions
in a separate file if you do not want to. The rationale for
keeping these definitions separate is to facilitate integration
testing of the bundle outside of an OSGi container -->
<osgi:reference id="iwicketService" interface="com.osgiweb.apps2.OSGiWebApp2.Interface.IWicketPageService"/>
<osgi:reference id="iwicketPageService" interface="com.osgiweb.apps2.OSGiWebApp2.Interface.IWicketExtensionService"/>
</beans>
然後一樣是改過的MANIFEST.mf檔
Manifest-Version: 1.0
Export-Package: com.osgiweb.apps2.OSGiWebApp2.HomePages;version="1.0.0";
uses:="com.osgiweb.apps2.OSGiWebApp2.Interface,
org.apache.wicket.markup.html,
org.osgi.framework,
org.springframework.osgi.context"
Unversioned-Imports: *
Built-By: momo
Tool: Bnd-1.15.0
Bundle-Name: Spring OSGi Bundle
Created-By: Apache Maven Bundle Plugin
Bundle-Version: 1.0.0
Build-Jdk: 1.6.0_24
Bnd-LastModified: 1304478929625
Bundle-ManifestVersion: 2
Import-Package: com.osgiweb.apps2.OSGiWebApp2.Interface,
org.apache.wicket,
org.apache.wicket.markup.html,
org.apache.wicket.markup.html.basic,
org.apache.wicket.markup.html.link,
org.osgi.framework,
org.slf4j,
org.springframework.osgi.context
Bundle-SymbolicName: com.osgiweb.apps2.OSGiWebApp2.HomePages
然後把這3個bundle deploy 到server上,
沒問題的話會看到以下的畫面
可以透過<wicket:extension-point id="test.extension.panel" /> 的方法來新增extension point的點, 然後每個xtension point在每個plugin bundle 裡面,這樣可以抽換每個bundle的plugin 。
Reference :
Wicket Osgi Extension