2011年5月26日 星期四

OSGI Tutorial (4) - Spring DM ,The Resource Abstraction

OSGi每個Bundle 都有各自的ClassLoader, SpringDM會在每一個Bundle啟動時創建ApplicationContext,如果對Spring 有點概念的應該都會知道在Spring裡面是如何獲取Resource, 而在Spring DM裡面的文件有提到:
Spring resource prefixes such as file: and http: are also supportedm , Resources loaded using 
such prefixes may come from any location, they are not restricted to being defined within the 
resource-loading bundle or its attached fragments. 

OSGi platforms may define their own unique prefixes for accessing bundle contents. 
For example, Equinox defines the bundleresource: and bundlentry: prefixes. These platform 
specific prefixes may also be used with Spring OSGi, at the cost, of course, of tying yourself 
to a particular OSGi implementation.
原本的file: , http: 也是可以使用的, 並不被限制於自身的bundle位置, osgi 的平台每個實作的不一樣,所以預設的prefix 也許會不一樣,Equinox 就是bundleresource: and bundlentry: ,如果你對Spring 的Resource 不太了解的話,建議可以先看這份官方的Doc Link-> Spring 3.x - 4. Resources

一樣先建立二個Bundle , 2個Bundle 的結構如下
OSGIApp.Sample.OSGiSample

OSGIApp.Sample.OSGiSample2

然後在OSGIApp.Sample.OSGiSample 裡面的Activator 裡面取Resource , code如下
package com.my;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.osgi.context.BundleContextAware;

public class Activator implements ApplicationContextAware,BundleContextAware {
 
 ApplicationContext application;
 Bundle bundle;
 Bundle bundle2;
 BundleContext btx;
  
 public void start() throws BundleException, IOException{
  
  //default setting,If no prefix is specified, the bundle space (osgibundle:) will be used.
  Resource res = application.getResource("/TxtFile/ResoureText.txt");
  System.out.println("Resource(Default osgibundle) = "   res);
  System.out.println("Resource(Default osgibundle) = "   res.getURI());
  System.out.println("Resource(Default osgibundle) = "   res.getURL());
  File f = res.getFile();
  System.out.println("File(osgibundle) getAbsolutePath = "  f.getAbsolutePath() );
  System.out.println("File(osgibundle) getPath = "  f.getPath() );
  System.out.println();
  
  //osgibundlejar prefix , Searches only the bundle jar. Provides low-level access.
  //using the same semantics as the Bundle.getEntry method. Delegates internally to the Bundle.findEntries .
  Resource res2 = application.getResource("osgibundlejar:/TxtFile/ResoureText.txt");
  System.out.println("Resource2(osgibundlejar) = "   res2);
  System.out.println("Resource2(osgibundlejar) = "   res2.getURI());
  System.out.println("Resource2(osgibundlejar) = "   res2.getURL());
  try{ 
   File f2 = res2.getFile(); // can't use it.
//   File f2 = new File(res2.getURI().toString()); //it's work.
   System.out.println("File(osgibundlejar) getAbsolutePath = "  f2.getAbsolutePath() );
   System.out.println("File(osgibundlejar) getPath = "  f2.getPath() );

  }catch(FileNotFoundException ex){
   System.out.println(ex);
  }
  System.out.println();
  
  //classpath:(the same as classpath*:) prefix , Searches the bundle classloader (the bundle, all imported packages and required bundles). 
  Resource res3 = application.getResource("classpath:/TxtFile/ResoureText.txt");
  System.out.println("Resource3(classpath) = "   res3);
  System.out.println("Resource3(classpath) = "   res3.getURI());
  System.out.println("Resource3(classpath) = "   res3.getURL());
  File f3 = res3.getFile();
  System.out.println("File(classpath) getAbsolutePath = "  f3.getAbsolutePath() );
  System.out.println("File(classpath) getPath = "  f3.getPath() );
  System.out.println();
  
  //To use osgi framework that get resource from the osgi bundle. 
  bundle = btx.getBundle();  
  System.out.println("Bundle GetResoruce = "    bundle.getResource("/TxtFile/ResoureText.txt"));
  System.out.println("bundle getEntry  = "  bundle.getEntry("/TxtFile/ResoureText.txt"));
  File f4 = new File(bundle.getResource("/TxtFile/ResoureText.txt").toString());
  System.out.println("File(classpath) getAbsolutePath = "  f4.getAbsolutePath() );
  System.out.println("File(classpath) getPath = "  f4.getPath() );
  System.out.println();
  
  System.out.println("bundl2 = " bundle2);
  System.out.println("get uri = " bundle2.getResource("/XmlFolder/Test.xml"));
  System.out.println("Bundle2 GetResoruce = "    bundle2.getResource("/XmlFolder/Test.xml"));
  System.out.println("bundle2 getEntry  = "  bundle2.getEntry("/XmlFolder/Test.xml"));
  File f5 = new File(bundle2.getResource("/XmlFolder/Test.xml").toString());
  System.out.println("File(classpath) getAbsolutePath = "  f5.getAbsolutePath() );
  System.out.println("File(classpath) getPath = "  f5.getPath() );
  System.out.println();
  //  System.out.println("bundle  = " bundle);
  
  //update sampe2 bundle 
//  bundle.update();
 }
 
 public void stop() throws BundleException{
  //stop sample2 , 
//  bundle.stop();
 }
// 
// public void setBundle(Bundle bundle) {
//  System.out.println("bnudle set");
//  this.bundle = bundle;
// }
//
 
 
 public void setBundleContext(BundleContext bundleContext) {
  System.out.println("set bundle context ");
  this.btx= bundleContext;
  
 }

 public void setBundle2(Bundle bundle2) {
  this.bundle2 = bundle2;
 }

 public void setApplicationContext(ApplicationContext arg0)
   throws BeansException {  
  this.application = arg0;
  
 }

}


然後這邊是完整的Log清單
set bundle context  
Resource(Default osgibundle) = OSGi resource[/TxtFile/ResoureText.txt|bnd.id=60|bnd.sym=OSGIApp.Sample.OSGiSample] 
Resource(Default osgibundle) = bundleentry://60.fwk1834141/TxtFile/ResoureText.txt 
Resource(Default osgibundle) = bundleentry://60.fwk1834141/TxtFile/ResoureText.txt 
File(osgibundle) getAbsolutePath = C:\Eclipse-Virgo-WebServer\work\org.eclipse.virgo.kernel.deployer_2.1.0.RELEASE\staging\global\bundle\OSGIApp.Sample.OSGiSample\1.0.0\OSGiSample.jar\TxtFile\ResoureText.txt 
File(osgibundle) getPath = C:\Eclipse-Virgo-WebServer\work\org.eclipse.virgo.kernel.deployer_2.1.0.RELEASE\staging\global\bundle\OSGIApp.Sample.OSGiSample\1.0.0\OSGiSample.jar\TxtFile\ResoureText.txt 
 
Resource2(osgibundlejar) = OSGi resource[osgibundlejar:/TxtFile/ResoureText.txt|bnd.id=60|bnd.sym=OSGIApp.Sample.OSGiSample] 
Resource2(osgibundlejar) = bundleentry://60.fwk1834141/TxtFile/ResoureText.txt 
Resource2(osgibundlejar) = bundleentry://60.fwk1834141/TxtFile/ResoureText.txt 
java.io.FileNotFoundException: OSGi resource[osgibundlejar:/TxtFile/ResoureText.txt|bnd.id=60|bnd.sym=OSGIApp.Sample.OSGiSample] cannot be resolved to absolute file path 
 
Resource3(classpath) = OSGi resource[classpath:/TxtFile/ResoureText.txt|bnd.id=60|bnd.sym=OSGIApp.Sample.OSGiSample] 
Resource3(classpath) = file:/C:/Eclipse-Virgo-WebServer/work/osgi/configuration/org.eclipse.osgi/bundles/36/data/store/org.eclipse.osgi/bundles/60/1/bundlefile/TxtFile/ResoureText.txt 
Resource3(classpath) = file:/C:/Eclipse-Virgo-WebServer/work/osgi/configuration/org.eclipse.osgi/bundles/36/data/store/org.eclipse.osgi/bundles/60/1/bundlefile/TxtFile/ResoureText.txt 
File(classpath) getAbsolutePath = C:\Eclipse-Virgo-WebServer\work\osgi\configuration\org.eclipse.osgi\bundles\36\data\store\org.eclipse.osgi\bundles\60\1\bundlefile\TxtFile\ResoureText.txt 
File(classpath) getPath = C:\Eclipse-Virgo-WebServer\work\osgi\configuration\org.eclipse.osgi\bundles\36\data\store\org.eclipse.osgi\bundles\60\1\bundlefile\TxtFile\ResoureText.txt 
 
Bundle GetResoruce = file:/C:/Eclipse-Virgo-WebServer/work/osgi/configuration/org.eclipse.osgi/bundles/36/data/store/org.eclipse.osgi/bundles/60/1/bundlefile/TxtFile/ResoureText.txt 
bundle getEntry  = bundleentry://60.fwk1834141/TxtFile/ResoureText.txt 
File(classpath) getAbsolutePath = C:\Eclipse-Virgo-WebServer\file:\C:\Eclipse-Virgo-WebServer\work\osgi\configuration\org.eclipse.osgi\bundles\36\data\store\org.eclipse.osgi\bundles\60\1\bundlefile\TxtFile\ResoureText.txt 
File(classpath) getPath = file:\C:\Eclipse-Virgo-WebServer\work\osgi\configuration\org.eclipse.osgi\bundles\36\data\store\org.eclipse.osgi\bundles\60\1\bundlefile\TxtFile\ResoureText.txt 
 
bundl2 = OSGIApp.Sample.OSGiSample2_1.0.0 [57] 
get uri = file:/C:/Eclipse-Virgo-WebServer/work/osgi/configuration/org.eclipse.osgi/bundles/36/data/store/org.eclipse.osgi/bundles/57/2/bundlefile/XmlFolder/Test.xml 
Bundle2 GetResoruce = file:/C:/Eclipse-Virgo-WebServer/work/osgi/configuration/org.eclipse.osgi/bundles/36/data/store/org.eclipse.osgi/bundles/57/2/bundlefile/XmlFolder/Test.xml 
bundle2 getEntry  = bundleentry://57.fwk1834141/XmlFolder/Test.xml 
File(classpath) getAbsolutePath = C:\Eclipse-Virgo-WebServer\file:\C:\Eclipse-Virgo-WebServer\work\osgi\configuration\org.eclipse.osgi\bundles\36\data\store\org.eclipse.osgi\bundles\57\2\bundlefile\XmlFolder\Test.xml 
File(classpath) getPath = file:\C:\Eclipse-Virgo-WebServer\work\osgi\configuration\org.eclipse.osgi\bundles\36\data\store\org.eclipse.osgi\bundles\57\2\bundlefile\XmlFolder\Test.xml                                                  

這邊要注意一下預設的prefix 是osgibundle: , 須要注意的是osgibundlejar: 這邊只提供low-lever access, 如果直接Resource.getFile() 會出現FileNotFoundException, 需使用getURI() 來取得檔案, 可以注意到預設的osgibundle 取出的檔案PATH跟其他的有不一樣,在Eclipse Virgo裡面是去jar檔的位置尋找, osgibundlejar則是跟osgi預設的行為很象, Equinox 裡面是設定bundleresource: and bundlentry: prefixes。

Reference:
Spring dm文件裡面有介紹, 相關的prefix

Table 6.1. OSGi resource search strategies

OSGi Search StrategyPrefixExplanation
Class Spaceclasspath:Searches the bundle classloader (the bundle, all imported packages and required bundles). Forces the bundle to be resolved.
This method has similar semantics to
Bundle#getResource(String)
Class Spaceclasspath*:Searches the bundle classloader (the bundle and all imported packages and required bundles). Forces the bundle to be resolved.
This method has similar semantics to
Bundle#getResources(String)
JAR File (or JarSpace)osgibundlejar:Searches only the bundle jar. Provides low-level access without requiring the bundle to be resolved.
Bundle Spaceosgibundle:Searches the bundle jar and its attached fragments (if there are any). Does not create a class loader or force the bundle to be resolved.


Chapter 6. Bundles and Application Contexts : 6.4. The Resource Abstraction
阅读 springdm in action 笔记--Spring DM extenders Link

2011年5月24日 星期二

Design Pattern - Adapte (Object)

Adapter Pattern 有分為 Object Adapter 與 Class Adapter , 這邊主要是針對Object Adapter做筆計,
當我們在開發軟體時,系統的資料和行為都正確,但是介面不符時,就可以考慮使用Adapter Pattern, 又或著你想銜接二個不同的Interface 時, 可以考慮這模式。

下面是Adapter Pattern的class diagram:

Target - 是Client 要呼叫的Interface , 可以是具體的class , abstract class or interface
Adapter - 包裝一個Adaptee物件, 實作 or Extends Target , 把原始的介面轉換成目標介面
Adaptee - 需要轉換的class

簡單的Code 實作:
Target.java
package adapter;

public interface Target {
 public void request();
}

Adapter .java
package adapter;

public class Adaptee {
 public void SpecialRequest(){
  System.out.println("Other Request");
 }
}

Adaptee .java
/**
 * 
 */
package adapter;

/**
 * @author momo
 *
 */
public class Adapter implements Target {

 private Adaptee adaptee = new Adaptee();
 /* (non-Javadoc)
  * @see adapter.Target#request()
  */
 @Override
 public void request() {
  adaptee.SpecialRequest();
 }

}


adapterTest.java
package adapter;

public class adapaterTest {
 /**
  * @param args
  */
 public static void main(String[] args) {
  
  Target target = new Adapter();
  target.request();

 }

}

另一個例子可以在 Here 找到, 主要是透過實作同一個介面來將2個Class(LegacyLinem,LegacyRectangle)做處理



Reference :

Wikipedia Adapter Pattern : Link

2011年5月20日 星期五

OSGi&Wicket - using customized wicket tag for extension-point on the osgi web server.

上次的 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

OSGI Tutorial (3) - how to get BundleContext in spring DM and to get other bundle.

首先建立2個Bunlde
OSGIApp.Sample.OSGiSample
OSGIApp.Sample.OSGiSample2

OSGIApp.Sample.OSGiSample2 比較單純, 只包含了一隻class, 如下圖

然後是MANIFEST.mf檔,
Manifest-Version: 1.0
Export-Package: com.sample.OSGiSample2;version="1.0.0"
Unversioned-Imports: *
Build-Jdk: 1.6.0_24
Built-By: momo
Bundle-Version: 1.0.0
Tool: Bnd-1.15.0
Bnd-LastModified: 1305872257329
Bundle-Name: Spring OSGi Bundle
Bundle-ManifestVersion: 2
Created-By: Apache Maven Bundle Plugin
Bundle-SymbolicName: OSGIApp.Sample.OSGiSample2

之後把他deploy 到Virgo Server上,
接下來是OSGIApp.Sample.OSGiSample bundle, 如下

這邊要測試的是在bundle start & stop 時去控制
OSGIApp.Sample.OSGiSample2 bundle的行為,
首先是Activator.java
package com.my;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.osgi.context.BundleContextAware;

public class Activator implements BundleContextAware {
 
 
 Bundle bundle;
 
 BundleContext btx;
  
 public void start() throws BundleException{
  
  System.out.println("bundle  = " bundle);
  
  //update sampe2 bundle 
  bundle.update();
 }
 
 public void stop() throws BundleException{
  //stop sample2 , 
  bundle.stop();
 }
 
 public void setBundle(Bundle bundle) {
  System.out.println("bnudle set");
  this.bundle = bundle;
 }

 public void setBundleContext(BundleContext bundleContext) {
  System.out.println("set bundle context ");
  this.btx= bundleContext;
  
 }
}


在OSGi 的BuneleContext裡提供一個方法
btx.getBundle(70); //get bundle by bundle ID
這邊會從OSGi Container裡面去找Bundle id = 70的bundle,
Spring DM 裡面可以設定如下

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"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.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 -->
         <context:annotation-config />
       <osgi:bundle id="rbundle" symbolic-name="OSGIApp.Sample.OSGiSample2" />
       <!-- This is the same as with the results of Bundle update();
       <osgi:bundle id="rbundle" symbolic-name="OSGIApp.Sample.OSGiSample2" action="update"/>  
        -->
</beans>

然後是bundle-context.xml, 相關的bean 設定
<?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:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.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 -->
       <context:annotation-config />
       
       <bean id="activator" class="com.my.Activator" init-method="start"
     destroy-method="stop">
     <property name="bundle" ref="rbundle"></property>
     </bean>
       
</beans>

然後別忘了重新設定MANIFEST.mf檔, Spring bundlor plugin 可以自動generation template.mf to MANIFEST.mf, 會自動check 需要class , 不過有時候會有一些class的問題,可能需手動解決。
在template.mf 檔或project 上按右鍵, 然後選Spring tools > Run Generation MANIFEST.mf file

完成後的MANIFEST如下
Manifest-Version: 1.0
Export-Package: com.my;version="1.0.0";uses:="org.osgi.framework,org.s
 pringframework.beans.factory.annotation,org.springframework.osgi.cont
 ext"
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: 1305707690281
Bundle-ManifestVersion: 2
Import-Package: org.osgi.framework,
 org.springframework.beans.factory.annotation,
 org.springframework.osgi.context
Bundle-SymbolicName: OSGIApp.Sample.OSGiSample


結果如下, OSGIApp.Sample.OSGiSample2, OSGIApp.Sample.OSGiSample deploye 上去時, start() 調用後會先把OSGIApp.Sample.OSGiSample2給update(stop > start), 最後看到Refresh 是個人在測試時hot deploy 的效果。

2個bundle 的狀態, 然後把OSGIApp.Sample.OSGiSample 給stop

OSGIApp.Sample.OSGiSample2也會一起Stop,

2個Bundle 都是RESOLVED狀態


Reference :
Spring Dynamic Modules Reference Guide

2011年5月19日 星期四

Java - Singletone Pattern &Double-checked locking

今天看到一篇文章在比較ConcurrentHashMap and HashMap with synchronized 的效能, 另一篇文章又在說明ConcurrentHashMap 的內容實作,ConcurrentHashMap 裡面使用了一些技巧(JMM) , 看到JMM又跑去看了一下,後來又看到了Double-checked locking, 以往實作Singleton時不會考慮到的一些問題。

wiki裡面的例子, 基本的singleton 實作, 在單thread下
class Foo {
    private Helper helper = null;
    public synchronized Helper getHelper() {
        if (helper == null)
            helper = new Helper();
        return helper;
    }
 
    // other functions and members...
}

但是這個在multi-thread下是不行的, 所以會有一種解決方式:
// Correct multithreaded version
class Foo { 
  private Helper helper = null;
  public synchronized Helper getHelper() {
    if (helper == null) 
        helper = new Helper();
    return helper;
    }
  // other functions and members...
  }

但是這種寫法效率上又不是很好,所以會有另一種的改進方法
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}

但是文章中又提到,上面這種看起來是work的,但是實際上是行不通的,因為new Helper()在很多平台和優化編譯器上是錯誤的。原因在於:helper = new Helper()這行代碼在不同編譯器上的行為是無法預知的。

看看另一個例子,
public  class  LazySingleton {  
    private  int  someField;  
      
    private  static  LazySingleton instance;  
      
    private  LazySingleton() {  
        this .someField = new  Random().nextInt( 200 )  1 ;      
    }  
      
    public  static  LazySingleton getInstance() {  
        if  (instance == null ) {                               
            synchronized (LazySingleton. class ) {           
                if  (instance == null ) {                    
                    instance = new  LazySingleton();        
                }  
            }  
        }  
        return  instance;                                  
    }  
      
    public  int  getSomeField() {  
        return  this .someField;                             
    }  
}  
這個例子下, 儘管得到了LazySingleton的正確的引用,但是卻有可能訪問到其成員變量的不正確值,具體來說LazySingleton.getInstance().getSomeField()有可能返回someField的默認值0。如果程序行為正確的話,這應當是不可能發生的事,因為在構造函數里設置的someField的值不可能為0。也說明這種情況理論上有可能發生。
DCL的分析也告訴我們一條經驗原則, 對引用(包括對象引用和數組引用)的非同步訪問,即使得到該引用的最新值,卻並不能保證也能得到其成員變量(對數組而言就是每個數組元素)的最新值。LazySingleton是一個不變class,它只有get方法而沒有set方法,即使對於不可變對象,它也必須被安全的發布,才能被安全地共享。 所謂“安全的共享”就是說不需要同步也不會遇到數據競爭的問題。 在Java5或以後,將someField聲明成final的,即使它不被安全的發布,也能被安全地共享,而在Java1.4或以前則必須被安全地發布。

這種設計上的問題要如何解決?
在JDK 1.5裡面,我們可以這樣做

// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
    private volatile Helper helper = null;
    public Helper getHelper() {
        Helper result = helper;
        if (result == null) {
            synchronized(this) {
                result = helper;
                if (result == null)
                    helper = result = new Helper();
            }
        }
        return result;
    }
 
    // other functions and members...
}
將object 設為volatile , 注意volatile只能保證多線程的內存可見性,不能保證多線程的執行有序性。
以下的a=a+count包含很多動作, 多個線程的執行是無序的,因為沒有任何機制來保證多個線程的執行有序性和原子性。 volatile存在的意義是,任何線程對a的修改,都會馬上被其他線程讀取到

public  class  VolatileTest{  
      public  volatile  int  a;  
      public  void  add( int  count){  
           a=a+count;  
           //a++;
      }  
    }  
在effective中提到的,簡單實現的方法
public  class  Singleton {  
      
      private  Singleton() {}  
      
      // Lazy initialization holder class idiom for static fields  
      private  static  class  InstanceHolder {  
       private  static  final  Singleton instance = new  Singleton();  
      }  
      
      public  static  Singleton getSingleton() {   
        return  InstanceHolder.instance;   
      }  
    }  

或著實作threadlocal
class Foo {
  /** If perThreadInstance.get() returns a non-null value, this thread
  has done synchronization needed to see initialization
  of helper */
         private final ThreadLocal perThreadInstance = new ThreadLocal();
         private Helper helper = null;
         public Helper getHelper() {
             if (perThreadInstance.get() == null) createHelper();
             return helper;
         }
         private final void createHelper() {
             synchronized(this) {
                 if (helper == null)
                     helper = new Helper();
             }
      // Any non-null value would do as the argument here
             perThreadInstance.set(perThreadInstance);
         }
 }

wiki 上的另一個例子,設成靜態的
// Correct lazy initialization in Java 
@ThreadSafe
class Foo {
    private static class HelperHolder {
       public static Helper helper = new Helper();
    }
    public static Helper getHelper() {
        return HelperHolder.helper;
    }
}

一樣是使用volatile 跟 final , (JDK 1.5 對volatile & final 有加強一些語意,針對JMM)
public class FinalWrapper<T>
{
    public final T value;
    public FinalWrapper(T value) { this.value = value; }
}
 
public class Foo
{
   private FinalWrapper<Helper> helperWrapper = null;
   public Helper getHelper()
   {
      FinalWrapper<Helper> wrapper = helperWrapper;
      if (wrapper == null)
      {
          synchronized(this)
          {
              if (helperWrapper ==null)
                  helperWrapper = new FinalWrapper<Helper>( new Helper() );
              wrapper = helperWrapper;
          }
      }
      return wrapper.value;
   }
}



More infomation :
Performance comparision between ConcurrentHashMap and synchronized HashMap in Terracotta

ConcurrentHashMap

Wikipedia : Double-checked locking

The "Double-Checked Locking is Broken" Declaration

Jeremy Manson Blog:Double Checked Locking

用happen-before規則重新審視DCL

2011年5月18日 星期三

OSGI Tutorial (2) - How to create Spring-DM bundle Project in Eclipse(Maven project)

Eclipse Virgo 的文件裡面的
Creating an Application with Virgo 使用的Example 是greenpage, greenpage有簡單的WebApp , 建議可以抓下來follow 官方文件做一次練習,可以更容易了解。GreenPage 可以在這邊下載:
greenpages-2.3.0.RELEASE.zip

本篇文章主要是使用Eclipse Create Spring DM Bundle Project, 下列相關的文件建議可以先看一下, 必須先了解一下Maven 怎麼使用,還有一些Spring Framework相關的知識。
Maven Getting Started Guide : Link
Spring Dynamic Modules Reference Guide : Link
Spring Framework Reference Documentation 3.x : Link



建立一個Maven Project ,

Next


在Catelog 選All , 然後filter 輸入osgi , 選擇spring-osgi-bundle-archtype ,

輸入GroupID, Artifact ID, 還有Version 跟default pacakge, 然後按下finish

建立出來的Project 大概長這樣,可以先將ProjectRoot的META-INF folder砍掉,我們不會用到, log4j.porperties也砍掉。

然後在project 上按右鍵,選擇Run as > Maven build... ,

在Goals 下輸入 org.apache.felix:maven-bundle-plugin:manifest , 利用apache felix 的maven bundle plugin 去建立MANIFEST.mf檔,

然後f5重整project,在target/classes/META-INF/下能找到建出來的MANIFEST.MF檔

把它移到src/main/resource/META-INF/下

然後run maven clear

打開MANIFEST.mf檔,copy所有內容,然後再打開project root下的template.mf檔, 把MANIFEST.mf檔的內容copy到template.mf裡, 如下圖

然後在Project 上點滑鼠右鍵, Spring Tools > add OSGi Bundle Project Nature , 新增Spring OSGi Nature,

建出來的Maven Project 習慣上我都會把POM.xml 做些修改, 可依個人需求修改,

在com.my 下建立一個class, 叫Activator,

package com.my;

public class Activator {
 
 public void start(){
  System.out.println("Hello OSGI");
 }
 
 public void stop(){
  System.out.println("good bye OSGi");
 }
}

然後設定bundle-context.xml ,定義bean , 然後設定init & destory 的method
<?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="activator" class="com.my.Activator" init-method="start"
     destroy-method="stop"></bean>
       
</beans>

然後打開Package Explorer , 滑鼠左鍵把整個project 拖到Virg Web Server上,


然後Server Cnosole 能看到install bundle的訊息

telnet localhost 2401 , 輸入 ss, 能看到bundle 已安裝

然後可以到C:\Eclipse-Virgo-WebServer\serviceability\logs\log.log 查看log檔,
(記確認conf下的org.eclipse.virgo.medic.properties 裡面log.wrapSysOut=true )


在OSGI console 輸入stop 73, 把bundle stop ,

查看log,可以看到good bye osgi 的message

OSGI Tutorial (1) - development environment(update for Eclipse 3.7)

在OSGi 的開發上, Container 的選擇有很多, Apache Felix , Knopflerfish , Eclipse Equinox ...等,這邊主要是針對使用Spring DM 開發OSGi app時的環境設定, 主要是使用Eclipse Virgo Server , Virgo 是從Spring dm Server轉過來的, 內部的OSGi container 是Eclipse Equinox , Web Server是Tomcat,

首先列出需要的東西:

Eclipse 3.6 Environment
JDK 1.5 or higher: download here
Eclipse Virgo Web Server : download here
Apache Maven 2.2.1 : download here
Eclipse Helios (3.6.2) : download here


Eclipse 3.7 Environment
JDK 1.5 or higher: download here
Eclipse Virgo Web Server : download here
Apache Maven 2.2.1 : download here 





Eclipse 3.6 Env



1: 安裝JDK 1.5 or higher, 下載JDK 1.5或以上的版本,安裝好確認。

2: 安裝Apache Maven 2.2.1 , 解壓縮到C:\apache-maven-2.2.1 (自訂) , 設定環境變數,在PATH中加入C:\apache-maven-2.2.1\bin, 開始>執行>cmd >打入mvn -version 查看是否設定成功, 會列出Java version,maven version...等資訊

3: 安裝Eclipse Helios (3.6.2) , 然後下面是需要安裝的Eclipse Plugin ,
SpringSource Tool Suite(STS) eclipse plugin
(UpdateSite: http://dist.springsource.com/release/TOOLS/update/e3.6 ),
最新版能在 here 找到

(如果本身Eclipse 有安裝Spring IDE, 但是沒有dm Server Tools的部份,可以試著單純安裝dm Server Tools的部份,如果不行的話請移除所有Spring IDE plugin再重新安裝。)

在Eclipse 裡面的 Help > Install New Software -> 新增STS 的UpdateSIte Location , 會看到以下的List,
安裝的時候記的不要全選,全選安裝有時會發生一些error, 像是not recognizing dependency ...之類的, 建議從Spring IDE Core/Extendsions/Integrations 安裝,再安裝其他的, 本人在安裝的時候發生錯誤是一個一個慢慢安裝(汗), 2.5.x安裝時有發生過, 2.6.x不確定,總之就是如此。

4: 安裝The Maven Integration for Eclipse (m2eclipse, Eclipse m2e)
(m2eclipse Core Update Site: http://m2eclipse.sonatype.org/sites/m2e )

然後設定Maven 的Install , 預設embedded 是3.0 , 換成我們安裝的2.2.1 , 如下圖:

4:安裝Eclipse Virgo Server , 下載virgo-web-server-2.1.1.RELEASE.zip , 解壓縮到C:\Eclipse-Virgo-WebServer(自訂), 在Eclipse 的windows > Perferences 設定Server ,如下圖:

然後把Server view 打開, windows > Show View -> server, 新增Eclipse Virgo Server , 如下圖:

在Server 上點2下滑鼠右鍵,可以看到下面的畫面, 有一些設定跟tools,
OverView 可以設定Runtime Environment, port ...等,

Repository 的部份, 左邊的Search 可以尋找需要的Library , 如jdom, 會從 SpringSource Enterprise Bundle Repository 尋找library, 但是版本不一定是最新的, 而且Spring因為正轉向Eclipse Virgo Project, 在還沒完全轉移前有說明local 的index 不會更新, 有需要還是去上面的網站Search, 會有比較新的library 。右邊是目前Eclipse Virgo Server 已安裝的Library 。

5: 打開org.eclipse.virgo.kernel.userregion.properties , 在C:\Eclipse-Virgo-WebServer\config 裡面, 修改下面的註解部份,將註解拿掉 ,這樣在Server 啟動後可以透過telnet localhost 2401 連到osgi consol

Eclipse Virgo 2.1.x version
#osgi console support
#osgi.console=2401
server start 後, 在cmd輸入telnet localhost 2401後打上ss , 可以列出目前有安裝的bundle

Notes: Update 2011/08/02 , for 3.0.x RC , 原本的console 改成Apache gogo,在使用上比較方便,
打開osgi.console.ssh.properties,enabled設成true , 使用putty 通過ssh連線,defaul acct/passwd 為equinox/equinox , 第一次登入會要設定新帳號,密碼, 新的console 如下,差別在於可以用tab 快速切換command



Eclipse 3.7 Enviroment


首先安裝Eclipse 3.7
Eclipse 3.7 : download here

然後安裝所需要的Eclipse Pluging, 下面是需要的plugin

SpringSource Tool Suite for Eclipse Indigo(3.7)
Virgo IDE Tooling (原Spring bundlor)
M2Eclipse

首先安裝STS for eclipse 3.7 , 點選Help > Eclipse Marketplace > type "STS" and search, 可以看到下面畫面


然後是安裝M2Eclipse ,
Maven Integration for Eclipse Update Site - http://m2eclipse.sonatype.org/sites/m2e

這邊我不使用Eclipse MarketPlace裡面的m2eclipse, 因為原本的m2eclipse project 從 sonatype project 轉換到 Eclipse project 使用上會有一些小問題,像是Maven init 時會download and update local index ,有時會出一些問題(download的檔案是gz,會fail), 所以請使用help > install new software > type M2eclise updatesite , 如下圖



再來是安裝Virgo IDE Tooling , 可以參考Eclipse Virgo wiki page 的說明 Virgo IDE Tooling page
一樣help > install new software > 輸入update site : http://download.eclipse.org/virgo/milestone/IDE 如下圖


Spring bundlor 已經轉移到Eclipse Virgo tools, 所以在一些eclipse project 的設定會有些不同, 如下面的installed facet , 跟project natures 的不同

原Spring source :
org.eclipse.wst.common.project.facet.core.xml
<installed facet="com.springsource.server.bundle" version="1.0"/>

.project
<nature>com.springsource.server.ide.facet.core.bundlenature</nature>

Eclipse Virgo tools
org.eclipse.wst.common.project.facet.core.xml
<installed facet="org.eclipse.virgo.server.bundle" version="1.0"/>

.project
<nature>org.eclipse.virgo.ide.facet.core.bundlenature</nature> 

不過只影響Eclipse Project的部份, 如果有用舊版的Spring bundlor 轉換到新版本的話要注意

到這邊就環境的部份就設定ok

Eclipse Virgo Server 的設定有很多, 可以參考官方文件
Eclipse virgo server documentation : HTML Doc LINK







Reference :
Eclipse Virgo Documentation : Link
Spring Dynamic Modules Reference Guide : Link
Maven Getting Started Guide : Link
SpringSource Tool Suite : Link
Eclipse Virgo wiki Page - Virgo/Tooling : Link

2011年5月17日 星期二

Design Pattern - Builder Pattern

Builder pattern, 可以將一個物件內部的"表象"跟物件產生的過程分開來,在建造的過程中產生不同的物件,
簡單的說,如果要把建立複雜組件與運用組件方式分離,就可以使用Builder Pattern.

Builder pattern 的Class diagram :
From wiki:
Builder
Abstract interface for creating objects (product).
Concrete Builder
Provides implementation for Builder. It is an object able to construct other objects. Constructs and assembles parts to build the objects.
Director
The Director class is responsible for managing the correct sequence of object creation. It receives a Concrete Builder as a parameter and executes the necessary operations on it.
Product
The final object that will be created by the Director using Builder.


wiki的例子是用Pizza , Pizza 就是一個Product , 會有一個抽像的PizzaBuilder 用來建立不同的pizza部份, 然後實作HawaiianPizzaBuilder and SpicyPizzaBuilder (ConcreteBuilder),再透過Director - Cook去把完整的pizza 實作出來。

如果我有一個Product , 每個Product 裡面的零件都不一樣, 也能套上Builder Pattern,首先建立一個Product 類別如下:
/** "Product" */
package builder;

import java.util.ArrayList;
import java.util.List;

public class Product {
 List<String> parts = new ArrayList<String>();
 
 public void addPart(String part){
  parts.add(part);
 }
 
 public void showProd(){
  for (int i = 0; i < parts.size(); i  ) {
   System.out.println("Prod infor =  "  parts.get(i));
  }
 }
 
}


再來建立一個Abstract Builder , 用來建立每個Product 的零件
/** "Abstract Builder" */
package builder;

public abstract class PartBuilder {
 protected Product prod;
 
 public Product getProduct(){
  return this.prod;
 }
 
 public void createNewProd(){
  this.prod = new Product();
 }
 
 public abstract void buildeAddPart();
}


然後再建立一個Concrete Builder
/** "ConcreteBuilder" */
package builder;

public class ProdABuilder extends PartBuilder {

 @Override
 public void buildeAddPart() {
  prod.addPart("PART1");  
 }

}

然後是Director的部份,負責把產品建立出來
/** "Director" */
package builder;

public class Director {
 private PartBuilder partbuilder;
 public void setPartbuilder(PartBuilder p){
  this.partbuilder = p;
 }
 
 public Product getProd(){
  return partbuilder.getProduct();
 }
 
 public void createProduct(){
  partbuilder.createNewProd();
  partbuilder.buildeAddPart();
 }
 
}

最後就能使用
package builder;

public class builderpattern {

 /**
  * @param args
  */
 public static void main(String[] args) {
  Director director = new Director();
  PartBuilder prodaBuilder = new ProdABuilder();
    
  director.setPartbuilder(prodaBuilder);
  director.createProduct();
    
  Product prod = director.getProd();
  prod.showProd();

 }

}




在Java Bean 裡面, 一般的Bean 長成這樣,
package builder;

public class UserBeanBuilder {
 private long userId;
 private String userName;
 private String userDesc;
 
 public long getUserId() {
  return userId;
 }
 
 public void setUserId(long userId) {
  this.userId = userId;
 }
 public String getUserName() {
  return userName;
 }
 public void setUserName(String userName) {
  this.userName = userName;
 }
 public String getUserDesc() {
  return userDesc;
 }
 public void setUserDesc(String userDesc) {
  this.userDesc = userDesc;
 }
 
 
 
 

}


但是如果有很多屬性,而我們又必需讓bean建立時有一些初始值, Constructor 就會寫很多個, Builder Pattern 在Effective Java也有提到
Item2 Consider using a Builder when faced with many constructor parameters


經過修改之後Bean 會長這樣
package builder;

public class UserBeanBuilder {
 private long userId;
 private String userName;
 private String userDesc;
 
 public long getUserId() {
  return userId;
 }

 public String getUserName() {
  return userName;
 }

 public String getUserDesc() {
  return userDesc;
 }

 public static class Builder{
  private long userId;
  private String userName;
  private String userDesc;
  
  public Builder(long userId,String userName){
   this.userId = userId;
   this.userName = userName;
  }
  
  public Builder userDesc(String desc) {
   this.userDesc = desc;
   return this;
  }
  
  public UserBeanBuilder build(){
   return new UserBeanBuilder(this);
  }
 }
 
 private UserBeanBuilder(Builder builder){
  this.userId = builder.userId;
  this.userName = builder.userName;
  this.userDesc = builder.userDesc;
 }
}


調用
package builder;

public class UserCreator {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  UserBeanBuilder user = new UserBeanBuilder.Builder(100, "MOMO").userDesc("CHECK THIS").build();
  
  System.out.println(user.getUserId());
  System.out.println(user.getUserName());
  System.out.println(user.getUserDesc());
  
 }

}



利用在一個 class 中內置的 public static class Builder ,並將原先的 constructor 封裝在該 class 中,我們可以免除以下問題:
1:Ugly constructor. 例如:new NutritionFacts(int servingSize, int servings,int calories, int fat, int 15 more optional params!);
2:Constructor telescoping. 如果是上例的 constructor ,我們在使用的時候一定得參看原先的Cnstructor 說明,才能知道各個參數的意義。如:NutritionFacts locoCola =new NutritionFacts(240, 8, 0, 0, 30, 28); ,無法了解這串數字是什麼
3:Too many setters required when creating objects.


Reference :
Wikipedia - Builder pattern

Book Review: Effective Java 2nd Edition

2011年5月16日 星期一

Spring + Hibernate , Session & Transaction (2)

上一篇文章隨便講了一下Spring + Hibernate , Session & transaction的問題, 原本對於session 的exception相關如下:
org.hibernate.SessionException: Session was already closed
   org.hibernate.impl.SessionImpl.close(SessionImpl.java:275)
   sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
原本針對Spring package 去trace debug log , 以為是Project 設計上的問題,造成Session 物件reference的錯誤,後來把hibernate package debug log也打開後才發現根本的問題....@$@!%@!%,在網路上查找了一些問題,有些是Lazy load的問題,有些是可以透過opensessioninviewfilter , 或著OpenSessionInViewInterceptor 來解決的問題,但是在trace log 之後發現了一個很根本的問題,也很...蠢的問題。就照字面上的意思, Session 已經被關閉了,當Spring 在HibernateTransactionManager doCommit之後 最後在HibernateTransactionManager 執行doCleanupAfterCompletion , 時會去關閉session ,在這邊產生了Exception , 主要是因為Spring 裡面的ApplicationContext 的設定問題
<bean id="sessionFactory"
  class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
  destroy-method="destroy">
  <property name="dataSource">
   <ref bean="myDatasource" />
  </property>
  <property name="annotatedClasses">
   <list>
      ...........
      ...........
   </list>
  </property>
  <property name="hibernateProperties">
   <props>
    <prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
    <prop key="hibernate.show_sql">false</prop>
    <prop key="hibernate.transaction.auto_close_session">false</prop>
   </props>
  </property>
 </bean>

如果把hibernate.transaction.auto_close_session 設為true , 在Transaction 提交的時候會把session close, 兇手就在JDBCTransaction 裡面的closeIfRequired, 然後調用 transactionContext.managedClose() 這邊,下面是Source code

org.hibernate.transaction.JDBCTransaction
package org.hibernate.transaction;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.transaction.Status;
import javax.transaction.Synchronization;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.hibernate.HibernateException;
import org.hibernate.Transaction;
import org.hibernate.TransactionException;
import org.hibernate.jdbc.JDBCContext;

/**
 * Implements a basic transaction strategy for JDBC connections.This is the
 * default <tt>Transaction</tt> implementation used if none is explicitly
 * specified.
 * @author Anton van Straaten, Gavin King
 */
public class JDBCTransaction implements Transaction {

 private static final Log log = LogFactory.getLog(JDBCTransaction.class);

 private final JDBCContext jdbcContext;
 private final TransactionFactory.Context transactionContext;

 private boolean toggleAutoCommit;
 private boolean begun;
 private boolean rolledBack;
 private boolean committed;
 private boolean commitFailed;
 private List synchronizations;
 private boolean callback;
 private int timeout = -1;

 public JDBCTransaction(JDBCContext jdbcContext, TransactionFactory.Context transactionContext) {
  this.jdbcContext = jdbcContext;
  this.transactionContext = transactionContext;
 }

 public void begin() throws HibernateException {
  if (begun) {
   return;
  }
  if (commitFailed) {
   throw new TransactionException("cannot re-start transaction after failed commit");
  }

  log.debug("begin");

  try {
   toggleAutoCommit = jdbcContext.connection().getAutoCommit();
   if ( log.isDebugEnabled() ) {
    log.debug("current autocommit status: "   toggleAutoCommit);
   }
   if (toggleAutoCommit) {
    log.debug("disabling autocommit");
    jdbcContext.connection().setAutoCommit(false);
   }
  }
  catch (SQLException e) {
   log.error("JDBC begin failed", e);
   throw new TransactionException("JDBC begin failed: ", e);
  }

  callback = jdbcContext.registerCallbackIfNecessary();

  begun = true;
  committed = false;
  rolledBack = false;

  if ( timeout>0 ) {
   jdbcContext.getConnectionManager()
     .getBatcher()
     .setTransactionTimeout(timeout);
  }

  jdbcContext.afterTransactionBegin(this);
 }

 private void closeIfRequired() throws HibernateException {
  if ( callback && transactionContext.shouldAutoClose() && !transactionContext.isClosed() ) {
   try {
    transactionContext.managedClose();
   }
   catch (HibernateException he) {
    log.error("Could not close session", he);
    //swallow, the transaction was finished
   }
  }
 }

 public void commit() throws HibernateException {
  if (!begun) {
   throw new TransactionException("Transaction not successfully started");
  }

  log.debug("commit");

  if ( !transactionContext.isFlushModeNever() && callback ) {
   transactionContext.managedFlush(); //if an exception occurs during flush, user must call rollback()
  }

  notifyLocalSynchsBeforeTransactionCompletion();
  if ( callback ) {
   jdbcContext.beforeTransactionCompletion( this );
  }

  try {
   commitAndResetAutoCommit();
   log.debug("committed JDBC Connection");
   committed = true;
   if ( callback ) {
    jdbcContext.afterTransactionCompletion( true, this );
   }
   notifyLocalSynchsAfterTransactionCompletion( Status.STATUS_COMMITTED );
  }
  catch (SQLException e) {
   log.error("JDBC commit failed", e);
   commitFailed = true;
   if ( callback ) {
    jdbcContext.afterTransactionCompletion( false, this );
   }
   notifyLocalSynchsAfterTransactionCompletion( Status.STATUS_UNKNOWN );
   throw new TransactionException("JDBC commit failed", e);
  }
  finally {
   closeIfRequired();
  }
 }

 private void commitAndResetAutoCommit() throws SQLException {
  try {
   jdbcContext.connection().commit();
  }
  finally {
   toggleAutoCommit();
  }
 }

 public void rollback() throws HibernateException {

  if (!begun && !commitFailed) {
   throw new TransactionException("Transaction not successfully started");
  }

  log.debug("rollback");

  if (!commitFailed) {

   /*notifyLocalSynchsBeforeTransactionCompletion();
   if ( callback ) {
    jdbcContext.notifyLocalSynchsBeforeTransactionCompletion( this );
   }*/

   try {
    rollbackAndResetAutoCommit();
    log.debug("rolled back JDBC Connection");
    rolledBack = true;
    notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_ROLLEDBACK);
   }
   catch (SQLException e) {
    log.error("JDBC rollback failed", e);
    notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_UNKNOWN);
    throw new TransactionException("JDBC rollback failed", e);
   }
   finally {
    if ( callback ) {
     jdbcContext.afterTransactionCompletion( false, this );
    }
    closeIfRequired();
   }
  }
 }

 private void rollbackAndResetAutoCommit() throws SQLException {
  try {
   jdbcContext.connection().rollback();
  }
  finally {
   toggleAutoCommit();
  }
 }

 private void toggleAutoCommit() {
  try {
   if (toggleAutoCommit) {
    log.debug("re-enabling autocommit");
    jdbcContext.connection().setAutoCommit( true );
   }
  }
  catch (Exception sqle) {
   log.error("Could not toggle autocommit", sqle);
   //swallow it (the transaction _was_ successful or successfully rolled back)
  }
 }

 public boolean wasRolledBack() {
  return rolledBack;
 }

 public boolean wasCommitted() {
  return committed;
 }

 public boolean isActive() {
  return begun && ! ( rolledBack || committed | commitFailed );
 }

 public void registerSynchronization(Synchronization sync) throws HibernateException {
  if (sync==null) throw new NullPointerException("null Synchronization");
  if (synchronizations==null) {
   synchronizations = new ArrayList();
  }
  synchronizations.add(sync);
 }

 private void notifyLocalSynchsBeforeTransactionCompletion() {
  if (synchronizations!=null) {
   for ( int i=0; i<synchronizations.size(); i   ) {
    Synchronization sync = (Synchronization) synchronizations.get(i);
    try {
     sync.beforeCompletion();
    }
    catch (Throwable t) {
     log.error("exception calling user Synchronization", t);
    }
   }
  }
 }

 private void notifyLocalSynchsAfterTransactionCompletion(int status) {
  begun = false;
  if (synchronizations!=null) {
   for ( int i=0; i<synchronizations.size(); i   ) {
    Synchronization sync = (Synchronization) synchronizations.get(i);
    try {
     sync.afterCompletion(status);
    }
    catch (Throwable t) {
     log.error("exception calling user Synchronization", t);
    }
   }
  }
 }

 public void setTimeout(int seconds) {
  timeout = seconds;
 }
}



下面是完整的Transaction complete log

2011/05/16 14:33:12 [DEBUG] TransactionAspectSupport.java/commitTransactionAfterReturning:319, Completing transaction for [com.gfactor.emaildiscovery.service.NewCaseService.getAllCaseInfoByUser]
2011/05/16 14:33:12 [DEBUG] AbstractPlatformTransactionManager.java/triggerBeforeCommit:903, Triggering beforeCommit synchronization
2011/05/16 14:33:12 [DEBUG] AbstractPlatformTransactionManager.java/triggerBeforeCompletion:916, Triggering beforeCompletion synchronization
2011/05/16 14:33:12 [DEBUG] AbstractPlatformTransactionManager.java/processCommit:730, Initiating transaction commit
2011/05/16 14:33:12 [DEBUG] HibernateTransactionManager.java/doCommit:651, Committing Hibernate transaction on Session [org.hibernate.impl.SessionImpl@1652b8b]
2011/05/16 14:33:12 [DEBUG] JDBCTransaction.java/commit:103, commit
2011/05/16 14:33:12 [DEBUG] JDBCTransaction.java/toggleAutoCommit:193, re-enabling autocommit
2011/05/16 14:33:12 [DEBUG] JDBCTransaction.java/commit:116, committed JDBC Connection
2011/05/16 14:33:12 [DEBUG] AbstractPlatformTransactionManager.java/triggerAfterCommit:929, Triggering afterCommit synchronization
2011/05/16 14:33:12 [DEBUG] AbstractPlatformTransactionManager.java/triggerAfterCompletion:945, Triggering afterCompletion synchronization
2011/05/16 14:33:12 [DEBUG] TransactionSynchronizationManager.java/clearSynchronization:315, Clearing transaction synchronization
2011/05/16 14:33:12 [DEBUG] TransactionSynchronizationManager.java/doUnbindResource:232, Removed value [org.springframework.orm.hibernate3.SessionHolder@461b58] for key [org.hibernate.impl.SessionFactoryImpl@f8f332] from thread [http-8080-2]
2011/05/16 14:33:12 [DEBUG] TransactionSynchronizationManager.java/doUnbindResource:232, Removed value [org.springframework.jdbc.datasource.ConnectionHolder@10dc9f5] for key [org.apache.tomcat.dbcp.dbcp.BasicDataSource@1fb24d3] from thread [http-8080-2]
2011/05/16 14:33:12 [DEBUG] HibernateTransactionManager.java/doCleanupAfterCompletion:730, Closing Hibernate Session [org.hibernate.impl.SessionImpl@1652b8b] after transaction
2011/05/16 14:33:12 [DEBUG] SessionFactoryUtils.java/closeSession:789, Closing Hibernate Session