2011年12月19日 星期一

Spring - applicationContext.xml , Set ThreadPoolExecutor to use RunTime.getRutime().availableProcessors() determine corePoolSize.

Spring 的記錄, ThreadPoolExecutor的corePoolSize 之前在測fork/join時看到ForkJoinPool裡面針對pool的設定是取決於cpu core size , 用的就是Runtime.getRuntime().availableProcessors(),然後在spring裡面的設定檔還滿麻煩的,查了一下才查到用法

靜態的Class instance如 Runtime r = Runtime.getRuntime();
在spring 裡可以這樣設
<bean id="runTime" class="java.lang.Runtime" factory-method="getRuntime"/>

然後int localMaxThread = r.availableProcessors();
我們可以這樣設定
<bean id="localMaxThread" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="targetObject">
   <ref bean="runTime"/>
  </property>
  <property name="targetMethod">
   <value>availableProcessors</value>
  </property>
 </bean>


最後完整的xml 設定例子如下

<bean id="limitedQueue" class="com.gfactor.emd.concurrent.LimitedQueue">
  <constructor-arg index="0">
   <value>1</value>
  </constructor-arg>
 </bean>

 <bean id="runTime" class="java.lang.Runtime" factory-method="getRuntime"/>

 <bean id="localMaxThread" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="targetObject">
   <ref bean="runTime"/>
  </property>
  <property name="targetMethod">
   <value>availableProcessors</value>
  </property>
 </bean>

 <bean id="fetchEmlMessagethreadPoolExecutor" class="java.util.concurrent.ThreadPoolExecutor">
  <constructor-arg type="int" ref="localMaxThread"></constructor-arg>
  <constructor-arg type="int" value="12"></constructor-arg>
  <constructor-arg type="long" value="300"></constructor-arg>
  <constructor-arg type="java.util.concurrent.TimeUnit" value="SECONDS" ></constructor-arg>
  <constructor-arg type="java.util.concurrent.BlockingQueue" ref="limitedQueue"></constructor-arg>
 </bean>

2011年11月8日 星期二

Terracotta : DSO(Distributed Shared Objects)

最近在玩Apache lucene , 針對一個IndexWriter在建立的時候只能有一個instance(同一個index folder) , 在multi-thread的時候可以把這個Instance create好丟出去給全部用,這沒什麼問題,我碰到的狀況是如果有2台機器要同時對一個index folder create indexWriter時就會出錯了,那有沒有什麼辦法能讓2台機器上的indexWriter共用(這不能算是好辦法,因為會有writer close的問題..lol).

找到的東西就是 Terracotta , Terracotta 是一個JVM-level clustering 目前最新的Open source版本是3.5.3, terracotta可以做很多的事, 這邊只做簡單的測試,主要是針對Terracotta 的DSO 功能。

有關DSO的說明跟細節可以參考 About Terracotta DSO Installations

首先要先下載Open Source版本的Terracotta 3.5.3, 可以到官網下載,這邊我下載的是binary 版本,然後將其解壓縮到Linux 機器上, 這邊都用預設的設定, 如果沒有自訂tc-config.xml 的話,預設會用com/tc/config/setup/default-config.xml來啟動Terracotta server, 這邊我只設定單台的tc server , Terracotta 支持Server Array , 可以多個tc server成為group , 當root死掉時會將root 交給其他active 的server , 有興趣可以自己看看官網的文件, The Terracotta Server Array , 下圖是terracotta server啟動的狀況



這樣tc server就算啟動完成, 再來是要寫一個簡單的sample 來測試, 這邊可以先安裝
Terracotta DSO Eclipse Plugin , 然後在Eclipse 裡面直接new 一個DSO project , 如下圖




然後寫一個簡單的Java 程式測試,

package com;

import java.util.Random;


public class DsoTest {
 private Integer shareInteger = new Random().nextInt();

 public Integer getShareInteger() {
  System.out.println("getShareInteger on call !!");
  return shareInteger;
 }

 public void setShareInteger(Integer shareInteger) {
  System.out.println("set value!!!");
  this.shareInteger = shareInteger;
 }
 
 public void print(){
  System.out.println("shareInteger = "   shareInteger.toString());
 }
 
 
 public static void main(String[] args) {
  DsoTest t1 = new DsoTest();
  t1.print();
  
  DsoTest t2 = new DsoTest();
  t2.print();
 }
}


執行後結果如下








這個是我們預期的結果,那如果加上terracotta之後呢?
先打開tc-config.xml做設定
<?xml version="1.0" encoding="UTF-8"?>
<con:tc-config xmlns:con="http://www.terracotta.org/config">
  <servers>
    <server host="192.168.3.95" name="EMD-test">
      <dso-port bind="192.168.3.95">9510</dso-port>
      <jmx-port bind="192.168.3.95">9520</jmx-port>
      <data>root/terracotta/server-data</data>
      <logs>root/terracotta/server-logs</logs>
      <statistics>root/terracotta/cluster-statistics</statistics>
    </server>
  </servers>
  <clients>
    <logs>terracotta/client-logs</logs>
  </clients>
  <application>
    <dso>
      <instrumented-classes>
        <include>
          <class-expression>com.*..*</class-expression>
        </include>
      </instrumented-classes>
      <roots>
        <root>
          <field-name>com.DsoTest.shareInteger</field-name>
        </root>
      </roots>
    </dso>
  </application>
</con:tc-config>
主要的是root 跟 field-name , 然後再加上instrumented-classes的部份, 這樣設定之後再去跑一次,
直接run as > terracotta dso application


這次看到的結果就是一樣的instance了


這邊只是單純簡單試試DSO的功能,確實是可以達到JVM-Level 的cluster ,也許那天會用上也不一定。



Reference :

Terracotta Cluster : Wiki
Introduction to Terracotta DSO
JVM-level clustering - Terracotta
用Terracotta 去Distribute Objects

2011年10月13日 星期四

OSGi : Eclipse Virgo ,EclipseLink , (Load-time-weaving and class loading issue)

之前在blog的一編文章, OSGI : Eclipse Virgo(3.0RC1) , JPA + EclipseLink (Issue for transaction and jdbc driver classloader) ,裡面有提到在Eclipse Virgo 裡面使用JPA(EclipseLink),這種使用上不太方便,而且每個Entity class都要寫在同一個bundle裡,而且後來發現一些問題,當你在OSGi 環境使用JPA時。

1: Persistence Units : Spring 在建立EntityManagerFactory 時讀取persistence.xml, 但是只能有一個, 如果是多個的話有一些解決辦,但不算是完整的,像是here 連結就有一些解法, 不過在OSGi 環境裡面這種方法也有一些問題

2: 多個EntityManagerFactory , 多個Datasource , 每個EntityManagerFactory 又來自不同DataSource :
這種問題目前知道的似乎只有透過JTA來解決? 但是上述的問題又無解, 怎麼在osgi環境裡面mapping又是一個問題,多個Persistence Units 的問題目前似乎無解(我是沒找到目前的解法) , 所以只能預設在我的系統設計暫時只有一個DataSource , 當然我也只有一個EntityManagerFactory , TransactionManager , 這也是方便在OSGi 環境裡使用(多個的話辦別上的問題可能要另解), 所以就有一張不怎麼好看的圖如下:



首先因為環境是在Eclipse Virgo 下, 整合了EclipseLink , 基本的Spring + JPA 設定不了解的可以看看官網的文件 Link , 第一個要解決的問題是如何將每個Entity Class放到獨立的Bundle裡面, 我在每個Entity Bundle裡面的MANIFEST.mf檔加上了自訂的header如下
Meta-Persistence-ScanPackage: com.gfactor.jpa.persistence.test
這定義了每個bundle裡面擁有JPA Entity Class的package , 這會在要建立EntityManagerFactory 時被讀取。

整個Project 的Bundle List 如下










如果需要Source Code的話, 可以在我的GitHut找到
OSgi-WebApp-Jpa-EntityClassBundle-

首先是JPA 的Entity Class, 這邊有2個, 分別放在不同的bundle 裡面
plugin-webdomain-jpa-persistence-test , TableInfo.java
package com.gfactor.jpa.persistence.test;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

 
@NamedQuery(name = "QueryByNameAndMail", 
query = "SELECT tableinfo FROM TableInfo tableinfo "  
  "WHERE tableinfo.user_name = :userName "  
  "and tableinfo.user_mail = :userMail ")


@Entity
@Table(name="tableinfo")
public class TableInfo implements Serializable{
 
 @Id 
    @GeneratedValue(strategy = GenerationType.AUTO)
 private long id;
 
 @Column(name="user_name")
 private String user_name;
 
 @Column(name="user_desc")
 private String user_desc;
 
 @Column(name="user_mail")
 private String user_mail;
 
 public TableInfo(){
  
 }
 
 @Override
 public String toString()
 {
  return "[TableInfo id="   id   " user_name="   user_name   " user_desc="   user_desc  
    " user_mail="   user_mail   "]";
 }

 public long getId() {
  return id;
 }

 public void setId(long id) {
  this.id = id;
 }

 public String getUser_name() {
  return user_name;
 }

 public void setUser_name(String user_name) {
  this.user_name = user_name;
 }

 public String getUser_desc() {
  return user_desc;
 }

 public void setUser_desc(String user_desc) {
  this.user_desc = user_desc;
 }

 public String getUser_mail() {
  return user_mail;
 }

 public void setUser_mail(String user_mail) {
  this.user_mail = user_mail;
 } 
}

plugin-webdomain-jpa-persistence-test2 , Bndpageinfo.java
package com.gfactor.jpa.persistence.test2;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@NamedQuery(name = "QueryByName_Ver_Entrypoint", 
      query = "SELECT bndpage FROM Bndpageinfo bndpage "  
        "WHERE bndpage.bundle_name = :bndName "  
        "and bndpage.bundle_version = :bndVer "  
        "and bndpage.entry_point = :bndEntryPoint")
        
@Entity
@Table(name="bndpageinfo")
public class Bndpageinfo implements Serializable{
 @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
 private long id;
  
 @Column(name="bundle_name")
 private String bundle_name;
 
 @Column(name="bundle_version")
 private String bundle_version;
 
 @Column(name="entry_point")
 private String entry_point;
 
 @Column(name="class_name")
 private String class_name;

 public long getId() {
  return id;
 }

 public void setId(long id) {
  this.id = id;
 }

 



 public String getBundle_name() {
  return bundle_name;
 }

 public void setBundle_name(String bundle_name) {
  this.bundle_name = bundle_name;
 }

 public String getBundle_version() {
  return bundle_version;
 }

 public void setBundle_version(String bundle_version) {
  this.bundle_version = bundle_version;
 }

 public String getEntry_point() {
  return entry_point;
 }

 public void setEntry_point(String entry_point) {
  this.entry_point = entry_point;
 }

 public String getClass_name() {
  return class_name;
 }

 public void setClass_name(String class_name) {
  this.class_name = class_name;
 }
 
 
 @Override
 public String toString() {
  StringBuffer sb = new StringBuffer();
  sb.append("print Bndpageinfo object : \n");
  sb.append("id               : "  id   "\n");
  sb.append("bnudle_name      : "  bundle_name   "\n");
  sb.append("bundle_version   : "  bundle_version   "\n");
  sb.append("entry_point      : "  entry_point   "\n");
  sb.append("class_name       : "  class_name   "\n");
  return sb.toString();
 }
}

然後最主要的是plugin-webdomain-jpa-core-emf 的部份, 這個bundle 會負責建立EntityManagerFactor和JpaTransactionManager, 但是必須改寫一下才有辦法支援這個部份,首先OsgiPersistenceClassLoader.java, 的部份,這隻負責把所有Entity Class都找出來,回傳一個List 包含全部的Class
package com.gfactor.jpa.internal.loader;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.List;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.wiring.BundleWiring;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.osgi.context.BundleContextAware;
import org.springframework.util.ClassUtils;

public class OsgiPersistenceClassLoader implements BundleContextAware{
 public static final String JPA_MANIFEST_HEADER = "Meta-Persistence-ScanPackage";
 private final Logger logger = LoggerFactory.getLogger(this.getClass()); 
 
 private BundleContext bundleContext;
 
 public void start(){
  getPersistenceList();
 }
 
 /**
  * Get all Entity class from persistence units bundle , 
  * The persistence units bundle have Meta-Persistence-ScanPackage information on the MANIFEST.mf.
  * @return List<Class> 
  */
 public List<Class<?>> getPersistenceList(){
  List<Class<?>> clazzList = new ArrayList<Class<?>>();
  Bundle[] bundles = bundleContext.getBundles();
  
  try {
   clazzList = resolverAllPersistenceUnitsClass(bundles);
  } catch (Exception e) {
   logger.error("getPersistenceList fail...",e);   
  }  
  return clazzList;
 }
 
 private List<Class<?>> resolverAllPersistenceUnitsClass(Bundle[] bundles) throws Exception{
  List<Class<?>> classList =  new ArrayList<Class<?>>();
  logger.info("resolverAllPersistenceUnitsClass ......");
  
  for (Bundle bundle : bundles) {
   Dictionary<String, String> customJpaMetaInfoHeader =bundle.getHeaders(JPA_MANIFEST_HEADER);
   final String allScanPackageListString = customJpaMetaInfoHeader.get(JPA_MANIFEST_HEADER);
   
   if (allScanPackageListString == null)
    continue;
   
   logger.info("bundle name = "   bundle.getSymbolicName());   
   logger.info("get JPA_MANIFEST_HEADER String = "   allScanPackageListString);
   
   foreachBundleGetPersistenceUnitsClass(classList, bundle,allScanPackageListString);    
  }
  
  logger.info("all persistence class size = "   classList.size());
  return classList;
 }

 private void foreachBundleGetPersistenceUnitsClass(List<Class<?>> classList,
   Bundle bundle, final String allScanPackageListString) {
  for (String scanClassPath : allScanPackageListString.split(",")) {
   BundleWiring wiring = bundle.adapt(BundleWiring.class);
   String resourceClassPath = convertClassNameToResourcePath(scanClassPath);    
   Collection<String> allResourcePathClassName= wiring.listResources(resourceClassPath,"*.class", BundleWiring.LISTRESOURCES_LOCAL);
   logger.info("resolverAllPersistenceUnitsClass resolverAllPersistenceUnitsClass : Check BundleWiring = ["  wiring   "]"); 
   
   for (String string : allResourcePathClassName) {      
    String loadClassName = convertResourcePathToClassName(string);     
    printDebugMessage(wiring, resourceClassPath, allResourcePathClassName,loadClassName);     
    
    try {
     Class<?> loadClazz = bundle.loadClass(loadClassName);
     classList.add(loadClazz);
    } catch (ClassNotFoundException e) {
     logger.error("can't load class..." , e);
    }    
   }
   logger.info("");
  }
 }


 private void printDebugMessage(BundleWiring wiring,
   String resourceClassPath, Collection<String> resources,String loadClassName) {
  
//  if(!logger.isDebugEnabled()) return; 
  
  logger.info("resolverAllPersistenceUnitsClass resolverAllPersistenceUnitsClass : resourceClassPath = ["  resourceClassPath   "]");
  logger.info("resolverAllPersistenceUnitsClass resolverAllPersistenceUnitsClass : resources Class = ["  resources   "]");
  logger.info("resolverAllPersistenceUnitsClass resolverAllPersistenceUnitsClass : loadClassName = ["  loadClassName   "]");
  logger.info("--");
 }
 
 /**
  * Convert a "."-based fully qualified class name to a "/"-based resource path. 
  * @param packageName
  * @return String(ResourcePathName)
  */
 private String convertClassNameToResourcePath(String packageName){
  return ClassUtils.convertClassNameToResourcePath(packageName);
 }
 
 /**
  * Convert a "/"-based resource path to a "."-based fully qualified class name.
  * The param className will replace all ".class" to "" , 
  * e.g: com/my/test.class to com.my.test  
  * @param className
  * @return String(className)
  */
 private String convertResourcePathToClassName(String className){
  String returnClassName = className.replaceAll(".class", "");
  returnClassName = ClassUtils.convertResourcePathToClassName(returnClassName);
  return returnClassName;
 }
 
 @Override
 public void setBundleContext(BundleContext bundleContext) {
  this.bundleContext = bundleContext;
 }
}


然後我把原本的LocalContainerEntityManagerFactoryBean做了一些變化,讓scan出來的Class加到persistenceUnitInfo裡面,
LoadPersistenceBundleEntityManagerFactoryBean.java
package com.gfactor.jpa.internal.core;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import javax.persistence.spi.PersistenceProvider;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.io.ResourceLoader;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.jdbc.datasource.lookup.SingleDataSourceLookup;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
import org.springframework.orm.jpa.persistenceunit.SmartPersistenceUnitInfo;
import org.springframework.util.ClassUtils;

import com.gfactor.jpa.internal.loader.OsgiPersistenceClassLoader;

/**
 * @author momo
 *
 */
public class LoadPersistenceBundleEntityManagerFactoryBean extends
  AbstractEntityManagerFactoryBean {
 
 private final Logger logger = LoggerFactory.getLogger(this.getClass()); 
 private PersistenceUnitManager persistenceUnitManager;
 private final DefaultPersistenceUnitManager internalPersistenceUnitManager = new DefaultPersistenceUnitManager();
 private PersistenceUnitInfo persistenceUnitInfo;
// private PersistenceProvider persistenceProvider;
 private OsgiPersistenceClassLoader osgiPersistenceClassLoader;
 
 
// public PersistenceProvider getPersistenceProvider() {
//  return persistenceProvider;
// }
//
// public void setPersistenceProvider(PersistenceProvider persistenceProvider) {
//  this.persistenceProvider = persistenceProvider;
// }

 public void setOsgiPersistenceClassLoader(
   OsgiPersistenceClassLoader osgiPersistenceClassLoader) {
  this.osgiPersistenceClassLoader = osgiPersistenceClassLoader;
 }

 public void setPersistenceUnitManager(PersistenceUnitManager persistenceUnitManager) {
  this.persistenceUnitManager = persistenceUnitManager;
 }
 
 public void setPersistenceXmlLocation(String persistenceXmlLocation) {
  this.internalPersistenceUnitManager.setPersistenceXmlLocations(new String[] {persistenceXmlLocation});
 }
 
 public void setDataSource(DataSource dataSource) {
  this.internalPersistenceUnitManager.setDataSourceLookup(new SingleDataSourceLookup(dataSource));
  this.internalPersistenceUnitManager.setDefaultDataSource(dataSource);
 }
 
 public void setPersistenceUnitPostProcessors(PersistenceUnitPostProcessor[] postProcessors) {
  this.internalPersistenceUnitManager.setPersistenceUnitPostProcessors(postProcessors);
 }
 
 public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
  this.internalPersistenceUnitManager.setLoadTimeWeaver(loadTimeWeaver);
 }

 public void setResourceLoader(ResourceLoader resourceLoader) {
  this.internalPersistenceUnitManager.setResourceLoader(resourceLoader);
 }
 
 
 /* (non-Javadoc)
  * @see org.springframework.orm.jpa.AbstractEntityManagerFactoryBean#createNativeEntityManagerFactory()
  */
 @Override
 protected EntityManagerFactory createNativeEntityManagerFactory()
   throws PersistenceException {
  
  logger.info("createNativeEntityManagerFactory start....");
  logger.info("chk ltw instance ..."   this.internalPersistenceUnitManager.getLoadTimeWeaver());
  PersistenceUnitManager managerToUse = this.persistenceUnitManager;
  if (this.persistenceUnitManager == null) {
   this.internalPersistenceUnitManager.afterPropertiesSet();
   managerToUse = this.internalPersistenceUnitManager;
  }
  
  logger.info("managerToUse = "  managerToUse);
  
  this.persistenceUnitInfo = determinePersistenceUnitInfo(managerToUse);
  JpaVendorAdapter jpaVendorAdapter = getJpaVendorAdapter();
  if (jpaVendorAdapter != null && this.persistenceUnitInfo instanceof SmartPersistenceUnitInfo) {
   ((SmartPersistenceUnitInfo) this.persistenceUnitInfo).setPersistenceProviderPackageName(
     jpaVendorAdapter.getPersistenceProviderRootPackage());
  }
  
  logger.info("this.persistenceUnitInfo getPersistenceUnitName = "  this.persistenceUnitInfo.getPersistenceUnitName());

//  PersistenceProvider provider = getPersistenceProvider();
  PersistenceProvider provider = getPersistenceProvider();

  if (provider == null) {
   String providerClassName = this.persistenceUnitInfo.getPersistenceProviderClassName();
   if (providerClassName == null) {
    throw new IllegalArgumentException(
      "No PersistenceProvider specified in EntityManagerFactory configuration, "  
      "and chosen PersistenceUnitInfo does not specify a provider class name either");
   }
   Class<?> providerClass = ClassUtils.resolveClassName(providerClassName, getBeanClassLoader());
   provider = (PersistenceProvider) BeanUtils.instantiateClass(providerClass);
  }
  
  if (provider == null) {
   throw new IllegalStateException("Unable to determine persistence provider. "  
     "Please check configuration of "   getClass().getName()   "; "  
     "ideally specify the appropriate JpaVendorAdapter class for this provider.");
  }
  logger.info("provider = "   provider.toString());
  logger.info("provider.getProviderUtil = "   provider.getProviderUtil());
  logger.info("provider getClass = "   provider.getClass());  
  logger.info("provider getClassLoader= "   provider.getClass().getClassLoader());
  
  List<Class<?>> persistenceClassList = osgiPersistenceClassLoader.getPersistenceList();
  scanEntitys(persistenceClassList);
  logger.info("persistenceClassList list size = "   persistenceClassList);
  
  if (logger.isInfoEnabled()) {
   logger.info("Building JPA container EntityManagerFactory for persistence unit '"  
     this.persistenceUnitInfo.getPersistenceUnitName()   "'");
  } 
  
  this.nativeEntityManagerFactory = provider.createContainerEntityManagerFactory(this.persistenceUnitInfo, getJpaPropertyMap());
  
  postProcessEntityManagerFactory(this.nativeEntityManagerFactory, this.persistenceUnitInfo);
  logger.info("nativeEntityManagerFactory = "   nativeEntityManagerFactory);
//  EntityManager em = this.nativeEntityManagerFactory.createEntityManager();
//  logger.info("em = "   em);
  
  return this.nativeEntityManagerFactory;
 }
 
 
 private void scanEntitys(List<Class<?>> classList) {
  
  for (Class clazz : classList) {
   logger.info("class name = "   clazz.getName());
      
   persistenceUnitInfo.getManagedClassNames().add(clazz.getName());
  }
  
  List<String> managedClass = persistenceUnitInfo.getManagedClassNames();  
  logger.info("managedClass = "   managedClass);
  List<String> mappingFiles = persistenceUnitInfo.getMappingFileNames();
  logger.info("mappingFiles = "   mappingFiles);

//        String[] pgs = StringUtils.commaDelimitedListToStringArray(scanPackages);  
//        if (pgs.length > -1) {  
//            ClassPathScaner p = new ClassPathScaner();  
//            // p.addIncludeFilter(new AssignableTypeFilter(TypeFilter.class));  
//            // Set<MetadataReader> bd = p.findCandidateClasss("org.springframework");  
//            p.addIncludeFilter(new AnnotationTypeFilter(Entity.class));  
//            Set<MetadataReader> bd = p.findCandidateClasss(pgs);  
//            List<String> managedClass = persistenceUnitInfo.getManagedClassNames();  
//            for (MetadataReader b : bd) {  
//                if (!(managedClass.contains(b.getClassMetadata().getClassName()))) {  
//                    managedClass.add(b.getClassMetadata().getClassName());  
//                }  
//            }  
//        }  
    }  
 
 protected PersistenceUnitInfo determinePersistenceUnitInfo(PersistenceUnitManager persistenceUnitManager) {
  if (getPersistenceUnitName() != null) {
   return persistenceUnitManager.obtainPersistenceUnitInfo(getPersistenceUnitName());
  }
  else {
   return persistenceUnitManager.obtainDefaultPersistenceUnitInfo();
  }
 }
 
 protected void postProcessEntityManagerFactory(EntityManagerFactory emf, PersistenceUnitInfo pui) {
 }


 @Override
 public PersistenceUnitInfo getPersistenceUnitInfo() {
  return this.persistenceUnitInfo;
 }

 @Override
 public String getPersistenceUnitName() {
  if (this.persistenceUnitInfo != null) {
   return this.persistenceUnitInfo.getPersistenceUnitName();
  }
  return super.getPersistenceUnitName();
 }

 @Override
 public DataSource getDataSource() {
  if (this.persistenceUnitInfo != null) {
   return this.persistenceUnitInfo.getNonJtaDataSource();
  }
  return this.internalPersistenceUnitManager.getDefaultDataSource();
 }
}

這樣取代之後,在使用上會發生一些問題,最常見到的問題就是『Cannot apply class transformer without loadtimeweaver specified』, , 弄了半天都是Load-Time-Weaving 的問題,原本應該設定spring-agent的部份,然後再指定weaving class, 但是在Eclipse Virgo裡面後來有查到,在這邊能看到討論 How do you set up Spring AOP/AspectJ/Virgo , 裡面有提到在Eclipse Virgo裡面當你在xml設定如下
<context:load-time-weaver aspectj-weaving="on"/>
kernel會把Spring的LoadTimeWeaver 取代成KernelLoadTimeWeaver, 如果只設定這樣也是不能work , LoadPersistenceBundleEntityManagerFactoryBean裡面會找不到weaver class, 所以完整的XMl設定如下
這邊要把loadTimeWeaver指向"loadTimeWeaver"這個bean(因為LTW 打開會自動instance)

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" xmlns:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xsi:schemaLocation="
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.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:load-time-weaver aspectj-weaving="on" />
 <!-- <context:load-time-weaver aspectj-weaving="on" weaver-class="org.eclipse.equinox.weaving.springweaver.EquinoxAspectsLoadTimeWeaver"/> -->

 <context:annotation-config />
 <!-- -->
 <tx:annotation-driven mode="aspectj" />

 <bean id="osgiPersistenceClassLoader"
  class="com.gfactor.jpa.internal.loader.OsgiPersistenceClassLoader" />

 <bean
  class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
 <!-- <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" 
  /> -->
 <!-- Transaction manager for a single JPA EntityManagerFactory (alternative 
  to JTA) -->
 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
  p:entityManagerFactory-ref="entityManagerFactory" />

 <!-- <bean id="defaultPuMgr" class="com.gfactor.jpa.internal.core.MergingPersistenceUnitManager"> 
  <property name="defaultDataSource" ref="dataSource"/> <property name="osgiPersistenceClassLoader" 
  ref="osgiPersistenceClassLoader"/> <property name="persistenceXmlLocations"> 
  <list> <value>classpath:META-INF/persistence*.xml</value> </list> </property> 
  </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
  <property name="persistenceUnitManager" ref="defaultPuMgr"/> </bean> -->

 <bean id="entityManagerFactory"
  class="com.gfactor.jpa.internal.core.LoadPersistenceBundleEntityManagerFactoryBean"
  p:dataSource-ref="dataSource">
  <property name="persistenceUnitName" value="plugin-web-domain"></property>
  <property name="osgiPersistenceClassLoader" ref="osgiPersistenceClassLoader"></property>
  <property name="jpaVendorAdapter">
   <bean id="jpaVendorAdapter"
    class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter"
    p:databasePlatform="org.eclipse.persistence.platform.database.MySQLPlatform"
    p:showSql="true" />
  </property>
  <property name="loadTimeWeaver" ref="loadTimeWeaver"/>
  <property name="jpaProperties">
   <bean id="jpaProperties"
    class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="properties">
     <props>
      <prop key="eclipselink.weaving">false</prop>
     </props>
    </property>
   </bean>
  </property>

 </bean>



</beans>

還有osgi 的xml 設定
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="dataSource" interface="javax.sql.DataSource" />
     <!-- 
     <osgi:reference id="persistenceProvider" interface="javax.persistence.spi.PersistenceProvider" />
      -->
      <osgi:service ref="entityManagerFactory" interface="javax.persistence.EntityManagerFactory" />
     <osgi:service ref="transactionManager" interface="org.springframework.transaction.PlatformTransactionManager" />    
     
</beans>


然後再使用plugin-webdomain-service-test , 來測試bundle , 這時又會出現『 unknown entity type』 的問題, 所以在eclipselink.weaving 是設為false的,但是重要的還是MANIFEST.mf檔的設定,最後為了解決上面的問題我使用了DynamicImport-Package: * , 才解決上面的問題。
plugin-webdomain-jpa-core-emf 的 MANIFEST.mf
Manifest-Version: 1.0
Unversioned-Imports: *
Build-Jdk: 1.6.0_26
Built-By: momo
Bundle-Version: 1.0.0
Tool: Bnd-1.43.0
Bnd-LastModified: 1313477275968
Bundle-Name: Spring OSGi Bundle for jpa EMF service
Bundle-ManifestVersion: 2
Created-By: Apache Maven Bundle Plugin
Bundle-SymbolicName: plugin-webdomain-jpa-core-emf
Import-Bundle: org.eclipse.persistence.jpa;version="[2.0.0,2.4.0]",
 org.eclipse.persistence.core;version="[2.0.0,2.4.0]"
Import-Package: javax.persistence,
 javax.persistence.spi,
 javax.sql,
 org.eclipse.persistence.logging,
 org.eclipse.persistence.config,
 org.osgi.framework,
 org.osgi.framework.wiring,
 org.slf4j,
 org.springframework.core,
 org.springframework.beans,
 org.springframework.beans.factory,
 org.springframework.beans.factory.support,
 org.springframework.beans.factory.config,
 org.springframework.dao.support,
 org.springframework.core.io,
 org.springframework.instrument.classloading,
 org.springframework.jdbc.datasource.lookup,
 org.springframework.orm.jpa,
 org.springframework.orm.jpa.persistenceunit,
 org.springframework.context.weaving,
 org.springframework.transaction.aspectj,
 org.springframework.transaction.interceptor,
 org.springframework.orm.jpa.support,
 org.springframework.orm.jpa.vendor,
 org.springframework.osgi.context,
 org.springframework.transaction,
 org.springframework.transaction.support,
 org.springframework.util,
 org.aspectj.weaver,
 org.aspectj.runtime,
 org.aspectj.lang,
 org.aspectj.runtime.reflect
DynamicImport-Package: *

Reference :

Eclipse Community Forums : How do you set up Spring AOP/AspectJ/Virgo
Spring Forums : Transactions with aspectj on Virgo
EclipseLink Wiki
非J2EE 容器环境下Spring +JPA 多持久化单元/多个JAR归档注解实体 的实体扫描问题及解决办法

2011年10月11日 星期二

OSGi : get all package list using BundleWiring on Virgo

這邊主要是利用BundleWiring 在OSGI 4.3新增的listResources()method 來取得package下的所有class , 原本可以透過Spring 的Resource 來取得File。
但是Resource.getFile()沒辦法直接list全部的檔案(傳folder 進去會出IOException : cannot be resolved as absolute), 要用其他的api,
不過後來發現沒有使用 BundleWiring.listResources() , 來的方便,最主要的目的是針對有設定某個MANIFEST header 指定的package 下的全部class, BundleWiring.listResources()的用法如下

BundleWiring wiring = bundle.adapt(BundleWiring.class);
Collection<String> allResourcePathClassName= wiring.listResources(resourceClassPath,"*.class", BundleWiring.LISTRESOURCES_LOCAL);

這邊有2個屬性能使用, 一個是
BundleWiring.LISTRESOURCES_RECURSE
The list resource names operation must limit the result to the names of matching resources contained in this bundle wiring's bundle revision and its attached fragment 
revisions.

BundleWiring.LISTRESOURCES_LOCAL
The list resource names operation must recurse into subdirectories.

這邊我只scan 指定的package下,所以使用LISTRESOURCES_LOCAL, listResources()還支持了pattern , 這邊使用的是"*.class" ,回傳的result 就會如下
resources = [com/gfactor/jpa/persistence/test/test.class, com/gfactor
/jpa/persistence/test/test1.class] 

Sapmle
package com.gfactor.jpa.internal.loader;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.List;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.wiring.BundleWiring;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.osgi.context.BundleContextAware;
import org.springframework.util.ClassUtils;

public class OsgiPersistenceClassLoader implements BundleContextAware{
 public static final String JPA_MANIFEST_HEADER = "Meta-Persistence-ScanPackage";
 private final Logger logger = LoggerFactory.getLogger(this.getClass()); 
 
 private BundleContext bundleContext;
 
 
 /**
  * Get all Entity class from persistence units bundle , 
  * The persistence units bundle have Meta-Persistence-ScanPackage information on the MANIFEST.mf.
  * @return List<Class> 
  */
 public List<Class<?>> getPersistenceList(){
  List<Class<?>> clazzList = new ArrayList<Class<?>>();
  Bundle[] bundles = bundleContext.getBundles();
  
  clazzList = resolverAllPersistenceUnitsClass(bundles);
  
  return clazzList;
 }
 
 private List<Class<?>> resolverAllPersistenceUnitsClass(Bundle[] bundles){
  List<Class<?>> classList =  new ArrayList<Class<?>>();
  logger.info("resolverAllPersistenceUnitsClass ......");
  
  for (Bundle bundle : bundles) {      
   Dictionary<String, String> customJpaMetaInfoHeader =bundle.getHeaders(JPA_MANIFEST_HEADER);
   final String allScanPackageListString = customJpaMetaInfoHeader.get(JPA_MANIFEST_HEADER);
   
   if (allScanPackageListString == null)
    continue;
   
   
   logger.info("bundle name = "   bundle.getSymbolicName());   
   logger.info("get JPA_MANIFEST_HEADER String = "   allScanPackageListString);
   
   for (String scanClassPath : allScanPackageListString.split(",")) {    
    BundleWiring wiring = bundle.adapt(BundleWiring.class);
    String resourceClassPath = convertClassNameToResourcePath(scanClassPath);    
    Collection<String> allResourcePathClassName= wiring.listResources(resourceClassPath,"*.class", BundleWiring.LISTRESOURCES_LOCAL);
       
    for (String string : allResourcePathClassName) {         
     String loadClassName = convertResourcePathToClassName(string);     
     printDebugMessage(wiring, resourceClassPath, allResourcePathClassName,loadClassName);     
     
     try {
      Class<?> loadClazz = bundle.loadClass(loadClassName);
      classList.add(loadClazz);
     } catch (ClassNotFoundException e) {
      logger.error("can't load class..." , e);
     }
     
    }
    logger.info("");
   }
    
  }
  
  logger.info("all persistence class size = "   classList.size());
  return classList;
 }


 private void printDebugMessage(BundleWiring wiring,
   String resourceClassPath, Collection<String> resources,String loadClassName) {
  
//  if(!logger.isDebugEnabled()) return;
  
  logger.info("resolverAllPersistenceUnitsClass resolverAllPersistenceUnitsClass : Check BundleWiring = ["  wiring   "]"); 
  logger.info("resolverAllPersistenceUnitsClass resolverAllPersistenceUnitsClass : resourceClassPath = ["  resourceClassPath   "]");
  logger.info("resolverAllPersistenceUnitsClass resolverAllPersistenceUnitsClass : resources Class = ["  resources   "]");
  logger.info("resolverAllPersistenceUnitsClass resolverAllPersistenceUnitsClass : loadClassName = ["  loadClassName   "]");
 }
 
 /**
  * Convert a "."-based fully qualified class name to a "/"-based resource path. 
  * @param packageName
  * @return String(ResourcePathName)
  */
 private String convertClassNameToResourcePath(String packageName){
  return ClassUtils.convertClassNameToResourcePath(packageName);
 }
 
 /**
  * Convert a "/"-based resource path to a "."-based fully qualified class name.
  * The param className will replace all ".class" to "" , 
  * e.g: com/my/test.class to com.my.test  
  * @param className
  * @return String(className)
  */
 private String convertResourcePathToClassName(String className){
  String returnClassName = className.replaceAll(".class", "");
  returnClassName = ClassUtils.convertResourcePathToClassName(returnClassName);
  return returnClassName;
 }
 
 @Override
 public void setBundleContext(BundleContext bundleContext) {
  this.bundleContext = bundleContext;
 }

}


Reference :

Interface BundleWiring

2011年9月19日 星期一

OSGi : Web Application for Wicket 1.5, Spring 3.0.5, Spring Security and JPA 1.0/2.0 with EclipseLink 2.x

從我的Blog tag OSGi(SpringDM) 可以看到舊的一些文章, 舊的OSGi Web Application 是基於Wicket 1.4.17, 因為Wicket 1.5 Release 的版本跟1.4.17有一些改變, 所以在OSGI 環境裡面也會有一些改變,本篇主要是針對如何Upgrade Wicket 1.4.17 to 1.5.0

如果有需要的話可以在我的github 找到 1.4.17 版本的Project source
Link here 1.4.17
而1.5.0 版本的Project Source 在這邊
Link here 1.5.0

在舊的1.4.17 的版本裡,從Wicket 官網download 下來後的檔案就包含了OSGI 版本的MANIFEST.mf檔, 可以直接放到Virgo下的ext/usr 裡面使用,但是在1.5.0 放上去會產生一些問題, 下面是Wicket 1.5.0 的jar檔

wicket-auth-roles-1.5.0.jar
wicket-core-1.5.0.jar
wicket-datetime-1.5.0.jar
wicket-devutils-1.5.0.jar
wicket-extensions-1.5.0.jar
wicket-guice-1.5.0.jar
wicket-ioc-1.5.0.jar
wicket-jmx-1.5.0.jar
wicket-objectsizeof-agent-1.5.0.jar
wicket-request-1.5.0.jar
wicket-spring-1.5.0.jar
wicket-util-1.5.0.jar
wicket-velocity-1.5.0.jar
當你deploy 到Virgo server 上時, 在Server Console 輸入 vsh package list 可以看到如下的 package list:

....
...
org.apache.request
org.apache.request
....
....

然後在Web Application 執行時就會出現ClassNotFoundException , 這是因為Export org.apache.request package 的有2個bundle , wicket-core-1.5.0.jar 跟 wicket-request-1.5.0.jar, 可以參考 Wicket forum , Wicket 1.5 and OSGi 的討論,
因為OSGI 針對相同package 的部份會以先install 的bundle 優先為準, 解決辦法之一就是將這些相同Export Package 的bundle 合成一個bundle , 在這邊我是把wicket-util-1.5.0.jar , wicket-request-1.5.0.jar 2個bundle 合到wicket-core-1.5.0.jar 裡面去

在wicket 1.5.0 download 的檔案包含了source code , 將他import 進來(using maven import) , project 如下 :

然後這邊我把wicket-request and wicket-util 的source code 完整的copy 到wicket-core裡面,compiler 完後把MANIFEST.mf檔做些修正,如下
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: 1.5.0_22 (Sun Microsystems Inc.)
Built-By: igor
Build-Jdk: 1.5.0_22
Specification-Title: Wicket Core
Specification-Version: 1.5.0
Specification-Vendor: Apache Software Foundation
Implementation-Title: Wicket Core
Implementation-Version: 1.5.0
Implementation-Vendor-Id: org.apache.wicket
Implementation-Vendor: Apache Software Foundation
Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt
Import-Package: org.apache.wicket,
 org.apache.wicket.request.flow,
 org.apache.wicket.request.http,
 org.apache.wicket.request.http.handler,
 org.apache.wicket.request.mapper.info,
 org.apache.wicket.request.mapper.mount,
 org.apache.wicket.request.mapper.parameter,
 org.apache.wicket.serialize,
 org.apache.wicket.util,
 org.apache.wicket.util.collections,
 org.apache.wicket.util.convert,
 org.apache.wicket.util.convert.converter,
 org.apache.wicket.util.diff,
 org.apache.wicket.util.listener,
 org.apache.wicket.util.lang,
 org.apache.wicket.util.string,
 org.apache.wicket.util.parse.metapattern,
 org.apache.wicket.util.parse.metapattern.parsers,
 org.apache.wicket.util.time,
 org.apache.wicket.util.upload,
 org.apache.wicket.util.value,
 org.apache.wicket.util.visit,
 org.apache.wicket.util.watch
Bnd-LastModified: 1314977204960
Export-Package: org.apache.wicket,
 org.apache.wicket.ajax,
 org.apache.wicket.ajax.calldecorator,
 org.apache.wicket.ajax.form,
 org.apache.wicket.ajax.markup.html,
 org.apache.wicket.ajax.markup.html.form,
 org.apache.wicket.ajax.markup.html.navigation.paging,
 org.apache.wicket.application,
 org.apache.wicket.authentication,
 org.apache.wicket.authentication.strategy,
 org.apache.wicket.authorization,
 org.apache.wicket.authorization.strategies,
 org.apache.wicket.authorization.strategies.action,
 org.apache.wicket.authorization.strategies.page,
 org.apache.wicket.behavior,
 org.apache.wicket.css,
 org.apache.wicket.event,
 org.apache.wicket.feedback,
 org.apache.wicket.javascript,
 org.apache.wicket.markup,
 org.apache.wicket.markup.html,
 org.apache.wicket.markup.html.basic,
 org.apache.wicket.markup.html.border,
 org.apache.wicket.markup.html.debug,
 org.apache.wicket.markup.html.form,
 org.apache.wicket.markup.html.form.upload,
 org.apache.wicket.markup.html.form.validation,
 org.apache.wicket.markup.html.image,
 org.apache.wicket.markup.html.image.resource,
 org.apache.wicket.markup.html.include,
 org.apache.wicket.markup.html.internal,
 org.apache.wicket.markup.html.link,
 org.apache.wicket.markup.html.list,
 org.apache.wicket.markup.html.navigation.paging,
 org.apache.wicket.markup.html.pages,
 org.apache.wicket.markup.html.panel,
 org.apache.wicket.markup.html.tree,
 org.apache.wicket.markup.html.tree.res,
 org.apache.wicket.markup.loader,
 org.apache.wicket.markup.parser,
 org.apache.wicket.markup.parser.filter,
 org.apache.wicket.markup.renderStrategy,
 org.apache.wicket.markup.repeater,
 org.apache.wicket.markup.repeater.data,
 org.apache.wicket.markup.repeater.util,
 org.apache.wicket.markup.resolver,
 org.apache.wicket.markup.transformer,
 org.apache.wicket.mock,
 org.apache.wicket.model,
 org.apache.wicket.model.util,
 org.apache.wicket.page,
 org.apache.wicket.pageStore,
 org.apache.wicket.pageStore.memory,
 org.apache.wicket.protocol.http,
 org.apache.wicket.protocol.http.documentvalidation,
 org.apache.wicket.protocol.http.mock,
 org.apache.wicket.protocol.http.request,
 org.apache.wicket.protocol.http.servlet,
 org.apache.wicket.protocol.https,
 org.apache.wicket.request,
 org.apache.wicket.request.component,
 org.apache.wicket.request.cycle,
 org.apache.wicket.request.flow,
 org.apache.wicket.request.http,
 org.apache.wicket.request.http.flow,
 org.apache.wicket.request.http.handler,
 org.apache.wicket.request.handler,
 org.apache.wicket.request.handler.render,
 org.apache.wicket.request.handler.resource,
 org.apache.wicket.request.mapper,
 org.apache.wicket.request.mapper.info,
 org.apache.wicket.request.mapper.mount,
 org.apache.wicket.request.mapper.parameter,
 org.apache.wicket.request.parameter,
 org.apache.wicket.request.resource,
 org.apache.wicket.request.resource.caching,
 org.apache.wicket.request.resource.caching.version,
 org.apache.wicket.resource,
 org.apache.wicket.resource.aggregation,
 org.apache.wicket.resource.dependencies,
 org.apache.wicket.resource.filtering,
 org.apache.wicket.resource.loader,
 org.apache.wicket.response,
 org.apache.wicket.response.filter,
 org.apache.wicket.serialize,
 org.apache.wicket.serialize.java,
 org.apache.wicket.session,
 org.apache.wicket.settings,
 org.apache.wicket.settings.def,
 org.apache.wicket.util,
 org.apache.wicket.util.cookies,
 org.apache.wicket.util.collections,
 org.apache.wicket.util.convert,
 org.apache.wicket.util.convert.converter,
 org.apache.wicket.util.crypt,
 org.apache.wicket.util.diff,
 org.apache.wicket.util.diff.myers,
 org.apache.wicket.util.file,
 org.apache.wicket.util.io,
 org.apache.wicket.util.iterator,
 org.apache.wicket.util.lang,
 org.apache.wicket.util.listener,
 org.apache.wicket.util.parse.metapattern,
 org.apache.wicket.util.parse.metapattern.parsers,
 org.apache.wicket.util.resource,
 org.apache.wicket.util.resource.locator,
 org.apache.wicket.util.resource.locator.caching,
 org.apache.wicket.util.string,
 org.apache.wicket.util.string.interpolator,
 org.apache.wicket.util.thread,
 org.apache.wicket.util.time,
 org.apache.wicket.util.template,
 org.apache.wicket.util.tester,
 org.apache.wicket.util.upload,
 org.apache.wicket.util.value,
 org.apache.wicket.util.visit,
 org.apache.wicket.util.watch,
 org.apache.wicket.util.xml,
 org.apache.wicket.validation,
 org.apache.wicket.validation.validator 
Bundle-Version: 1.5.0
Bundle-Name: Wicket Core
Bundle-Description: Wicket is a Java web application framework that ta
 kes simplicity,   separation of concerns and ease of development to a
  whole new level.   Wicket pages can be mocked up, previewed and late
 r revised using   standard WYSIWYG HTML design tools. Dynamic content
  processing and   form handling is all handled in Java code using a f
 irst-class   component model backed by POJO data beans that can easil
 y be   persisted using your favorite technology.
Bundle-DocURL: http://apache.org
Bundle-Vendor: Apache Software Foundation
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.apache.wicket.core
Tool: Bnd-1.15.0
DynamicImport-Package: *

這邊做的是將wicket-util ,wicket-request裡面的MANIFEST.mf檔的information merg到wicket-core裡面, 針對Improt-package & export-package的部份,3個 bundle的MANIFEST.mf檔能在 jar檔裡面找到, 完成後要從新build 或著是 package都行.

再來就是針對Wicket 1.5 的部份做修改, ClassLoader 跟@springbean , @ijnect 的部份可以參考Wicket Stuff裡面的osgi project , wicket-osgi-parent ,
原本1.4.17 的WebAuthenticatedWebSession 的部份使用@Ijnect ,injectDependencies()裡面的改動如下
InjectorHolder.getInjector().inject(this); 
↓
Injector.get() .inject(this),

雖然在WicketApplication裡面設定了OsgiComponentInjector , 但是在WebAuthenticatedWebSession 裡面還是會取不到inject instance, 解決的辦法是使用@Springbean, 而WicketApplication跟1.4.17 版本不同的地方如下
protected void init() {
   logger.info("WicketApplication init........");
   logger.info("osgiClassResolver = "  osgiClassResolver);
//     this.getPageSettings().addComponentResolver(new OsgiExtensionPointResolver(this.bundleCtx));
     super.init(); 
    
    
     this.getApplicationSettings().setClassResolver(osgiClassResolver);
     this.getResourceSettings().setResourceStreamLocator(new OsgiResourceStreamLocator());
     OsgiComponentInjector compInj = new OsgiComponentInjector(true);
     getComponentInstantiationListeners().add(new SpringComponentInjector(this));
     getComponentInstantiationListeners().add(compInj);
     getMarkupSettings().setDefaultMarkupEncoding(DEFAULT_ENCODING);

 }
在1.5 版本裡面 getComponentInstantiationListeners() 裡面已經改成使用Lst 去存取,
private final List<T> listeners = new CopyOnWriteArrayList<T>();
在這邊就把SpringComponentInjector and OsgiComponentInjector 2個都加到listeners 裡面, 然後在
WebAuthenticatedWebSession 需要AuthenticationManager的部份就使用@SpringBean(name="authenticationManager") 來Inject , 而其他的wicket page 就使用@Inject 來取的OSGi Service

Reference :

Wicket Stuff
Wicket Forum: Wicket 1.5 and OSGi

2011年8月23日 星期二

OSGI : Eclipse Virgo(3.0RC1) , JPA + EclipseLink (Issue for transaction and jdbc driver classloader)

OSGI : Eclipse Virgo + JPA with EclipseLink(JPA2.0) or Hibernate(JPA 1.0)

上面的文章裡提到在Eclipse Virgo 上使用JPA + EcliseLink ,經過幾天測試後發現一些問題,
第一個問題是JPA Transaction commit 後沒有反應
第二個問題是jdbc driver classloader 的問題

下面是Bundle 的狀況
120 	S 	plugin-webexport-iface       1.0.0 Active
139 	S 	plugin-webdomain-page-html     1.0.0 Active
134 	S 	plugin-webdomain-service.jpa-eplk  1.0.0 Active
128 	S 	plugin-webdomain-datasource     1.0.0 Active 


Jdbc driver classloader issue
當page-html call service from service.jpa-eplk時 , 會出現Exception 如下
Caused by: org.apache.commons.dbcp.SQLNestedException: Cannot load JDBC driver class 'com.mysql.Driver'
at org.apache.commons.dbcp.BasicDataSource.createData Source(BasicDataSource.java:1146)
at org.apache.commons.dbcp.BasicDataSource.getConnect ion(BasicDataSource.java:882)
at org.springframework.jdbc.datasource.DataSourceUtil s.doGetConnection(DataSourceUtils.java:113)
at org.springframework.jdbc.datasource.DataSourceUtil s.getConnection(DataSourceUtils.java:79)
而這個Exception的來源竟然是在plugin-webdomain-page-html bundle裡面,但是我們調用的是osgi service, 並沒有直接存取JPA的部份,後來在spring forum上找到了一個文章
Thread: Question about "Cannot load JDBC driver"  , 裡面提到了的是關於
Commons DBCP 的問題,因為我的Datasource export 成OSGi Service 時是使用Commons DBCP , 在默認的狀況下,要在被使用的時候Commons DBCP 才會實例化JDBC Driver , 文中提到一段就是

" It uses the context classloader that's set at this time to load the JDBC driver. This means that if you first access your Common DBCP-powered datasource via a Web request it'll be the Web module's classloader that's set and therefore your Web bundle will need to be able to see the JDBC driver's classes. "

也就是說我們會在plugin-webdomain-page-html bundle 調用serice 實例化jdbc driver,此時在plugin-webdomain-page-html budnel裡面因為沒有import jdbc driver所以會造成問題。

解法就是在plugin-webdomain-datasource Bundle 裡面在datasource bean 實例化時加上init-method & destroy-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="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		  init-method="createDataSource" destroy-method="close">
		  
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://127.0.0.1:3306/test" />
		<property name="username" value="root" />
		<property name="password" value="xenogears" />

	</bean>
</beans>

這樣在log裡面在datasource bundle start 時會看到以下log
[2011-08-23 15:50:04.687] DEBUG region-dm-11                 o.s.beans.factory.support.DefaultListableBeanFactory              Creating shared instance of singleton bean 'dataSource' 
[2011-08-23 15:50:04.687] DEBUG region-dm-11                 o.s.beans.factory.support.DefaultListableBeanFactory              Creating instance of bean 'dataSource' 
[2011-08-23 15:50:04.687] DEBUG region-dm-11                 o.s.beans.factory.support.DefaultListableBeanFactory              Eagerly caching bean 'dataSource' to allow for resolving potential circular references 
[2011-08-23 15:50:04.687] DEBUG region-dm-11                 o.s.beans.factory.support.DefaultListableBeanFactory              Invoking init method  'createDataSource' on bean with name 'dataSource' 
[2011-08-23 15:50:04.921] DEBUG region-dm-11                 o.s.beans.factory.support.DefaultListableBeanFactory              Finished creating instance of bean 'dataSource' 

Transaction commit issue
第二個問題是plugin-webdomain-page-html 調用plugin-webdomain-service.jpa-eplk 時transaction 在commit 時沒有insert 資料,
下面是一個完整正常的Transaction Log
[2011-08-23 15:50:10.078] DEBUG region-dm-1                  org.springframework.orm.jpa.JpaTransactionManager                 Initiating transaction commit 
[2011-08-23 15:50:10.078] DEBUG region-dm-1                  org.springframework.orm.jpa.JpaTransactionManager                 Committing JPA transaction on EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@18f67d0] 
[2011-08-23 15:50:10.078] INFO  region-dm-1                  System.out                                                        [EL Fine]: 2011-08-23 15:50:10.078--ClientSession(10164575)--Connection(10408771)--Thread(Thread[region-dm-1,5,main])--INSERT INTO bndpageinfo (id, bundle_name, bundle_version, class_name, entry_point) VALUES (?, ?, ?, ?, ?) 
[2011-08-23 15:50:10.078] INFO  region-dm-1                  System.out                                                        	bind => [139, plugin-webdomain-page-html, 1.0.0, com.gfactor.page.html.internal.NonePage, imagePage1] 
[2011-08-23 15:50:10.140] DEBUG region-dm-1                  org.springframework.orm.jpa.JpaTransactionManager                 Closing JPA EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@18f67d0] after transaction 
[2011-08-23 15:50:10.140] DEBUG region-dm-1                  org.springframework.orm.jpa.EntityManagerFactoryUtils             Closing JPA EntityManager 

問題的發生在於commit結束之後insert SQL並沒有被執行, Log就會變成如下
[2011-08-23 15:50:10.078] DEBUG region-dm-1                  org.springframework.orm.jpa.JpaTransactionManager                 Initiating transaction commit 
[2011-08-23 15:50:10.078] DEBUG region-dm-1                  org.springframework.orm.jpa.JpaTransactionManager                 Committing JPA transaction on EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@18f67d0] 
[2011-08-23 15:50:10.140] DEBUG region-dm-1                  org.springframework.orm.jpa.JpaTransactionManager                 Closing JPA EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@18f67d0] after transaction 
[2011-08-23 15:50:10.140] DEBUG region-dm-1                  org.springframework.orm.jpa.EntityManagerFactoryUtils             Closing JPA EntityManager 

在google search一陣子之後, 大部份都是JPA Transaction 沒有驅動, 或著是在setting xml 檔的問題之類的, 比如說在Stackoverflow上這篇文章 Transaction not rolling back 就提到沒有rollback 的問題, 最主要的原因目前還沒有辦法完整的確定, 相關的討論有些是認為driver classloader 的問題, 有些可能是因為AOP Interceptor 的問題, 有的是AspectJ class proxy的問題, ok , 不論如何, 這邊碰到的是Transaction 有正常開啟, commit 也有正常執行, 無exception , 沒error , 百思不得其解的狀況下, 我做了以下幾件事情,後來就正常(這不是100%解, 也不一定在類似的問題上也可行)

1: 在上面的datasource 增加init-method & destroy-method , 讓datasource export service時instance
2: 將EclipseLink 相關的jar file 從EclipseVirgo下的repository\usr 移動到 repository\ext
3: 修改plugin-webdomain-service.jpa-eplk bundle 的dao implement class, 把
	
        @PersistenceUnit
	private EntityManagerFactory entityManagerFactory;
換成
       @PersistenceContext
        private EntityManager em;

完成上述2個之後,將Server重啟, 一切正常 , 這邊在使用JPA + EclipseLink (or other framework)時要注意的是在OSGI環境裡面除了要注意AOP classes proxy 的問題還有一些package import , export 的問題外有時還要小心datasource 的錯誤.....Eclipse Virgo上的greenpage demo 版本是2.3 , 這一個版本就沒有在DBCP 的datasource init & destrop ...

當然有人的建議是分層架構修正, 我這邊是plugin-webdomain-page-html 去呼叫 plugin-webdomain-service.jpa-eplk 的Service , 而plugin-webdomain-service.jpa-eplk 裡面defined 了TransactionManage , EntryManager....等, 而在spring forum上是有人建議bundle把transactionManager export 成osgi service , 然後在調用的service layer 使用,
Datasource(export datasource instance for osgi service)
Dao(export DAO service , TransactionManager) 
Service(reference TransactionManager from osgi service) 
這種也許也好一點


Reference:

Thread: Question about "Cannot load JDBC driver"
Transaction not rolling back
Thread: No data written using JpaTransactionManager and openJpa
Thread: JPA + @Transactional - After Rollback, SQL Querys seems to be pendent?


2011年8月18日 星期四

OSGI : Eclipse Virgo + JPA with EclipseLink(JPA2.0) or Hibernate(JPA 1.0)

本篇主要是針對Eclipse Virgo 3.0.0-RC1 版本上使用JPA 做設定, Virgo 3.0.0 RC1 預設jar 檔是只支援JPA 1.0 ,而Hibernate 官網的jar 檔是不包含OSGI 版本的,所以在這邊針對Hibernate 只有使用在SpringSource EBR 上找到的osgi bundle版本,JPA 2.0是使用EclipseLink 2.3。


JPA 1.0 + Hibernate
首先在SpringSource EBR上可以找到以下的jar 檔,

org.hibernate.ejb-library-3.3.1.ga.libd
com.springsource.org.hibernate.annotations-3.4.0.GA-A.jar
com.springsource.org.hibernate.annotations.common-3.3.0.ga.jar
com.springsource.org.hibernate.ejb-3.4.0.GA-A.jar
com.springsource.org.hibernate-3.3.2.GA.jar

這邊要注意版本的問題,org.hibernate.ejb-library-3.3.1.ga.libd裡面import的 bundle version可能跟下載的版本有些差異,如果碰到exception 可能要手動修改bundle裡面 Import-Package,Require-bundle 的版本資訊,這邊我有手動修改。 然後下面的是上面所需要的其他bundle

com.springsource.antlr-2.7.7.jar
com.springsource.org.apache.commons.collections-3.2.0.jarz
com.springsource.org.apache.commons.logging-1.1.1.jar
com.springsource.org.dom4j-1.6.1.jar
com.springsource.org.jgroups-2.5.1.jar
com.springsource.javassist-3.9.0.GA.jar
com.springsource.org.objectweb.asm-1.5.3.jar
com.springsource.net.sf.ehcache-1.5.0.jar
com.springsource.net.sf.jsr107cache-1.0.0.jar

然後將這些放到$EclipseVirgoHome/repository/usr 下,再來就能直接編寫測試用的bundle


先看看project結構


com.gfactor.service.iface 定義了service 的interface ,
com.gfactor.service.internal.dao 定義了dao interface &amp; implements
com.gfactor.service.jpa 定義jpa 的pojo object
下面列出來的的檔案相同也用在JPA 2.0 + EclipseLink 的例子上,

Bndpageinfo entity

package com.gfactor.service.jpa;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="bndpageinfo")
public class Bndpageinfo implements Serializable{
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
	private long id;
	
	@Column(name="bundle_name")
	private String bundle_name;
	
	@Column(name="bundle_version")
	private String bundle_version;
	
	@Column(name="entry_point")
	private String entry_point;
	
	@Column(name="class_name")
	private String class_name;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	



	public String getBundle_name() {
		return bundle_name;
	}

	public void setBundle_name(String bundle_name) {
		this.bundle_name = bundle_name;
	}

	public String getBundle_version() {
		return bundle_version;
	}

	public void setBundle_version(String bundle_version) {
		this.bundle_version = bundle_version;
	}

	public String getEntry_point() {
		return entry_point;
	}

	public void setEntry_point(String entry_point) {
		this.entry_point = entry_point;
	}

	public String getClass_name() {
		return class_name;
	}

	public void setClass_name(String class_name) {
		this.class_name = class_name;
	}
	
	
	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append("print Bndpageinfo object : \n");
		sb.append("id               : "  id   "\n");
		sb.append("bnudle_name      : "  bundle_name   "\n");
		sb.append("bundle_version   : "  bundle_version   "\n");
		sb.append("entry_point      : "  entry_point   "\n");
		sb.append("class_name       : "  class_name   "\n");
		return sb.toString();
	}
}

Providing the JPA metadata


<persistence xmlns="http://java.sun.com/xml/ns/persistence"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
	version="1.0">

	<persistence-unit name="plugin-web-domain"
		transaction-type="RESOURCE_LOCAL">

		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		<class> com.gfactor.service.jpa.Bndpageinfo</class>
		<properties>
			<property name="hibernate.show_sql" value="true" />
			<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
			<property name="hibernate.jdbc.batch_size" value="100" />
		</properties>
	</persistence-unit>

</persistence>


Service Layer 


IRegisterWicketPageBndIdentify.java
package com.gfactor.service.iface;

import com.gfactor.service.jpa.Bndpageinfo;

public interface IRegisterWicketPageBndIdentify {
	public boolean registerPageInfo(Bndpageinfo bnd);
	public boolean unregisterPageInfo(Bndpageinfo bnd);
}


RegisterWicketPageBndIdentifyImpl.java
package com.gfactor.service.iface.internal.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.gfactor.service.iface.IRegisterWicketPageBndIdentify;
import com.gfactor.service.internal.dao.RegWicketPageBndDao;
import com.gfactor.service.jpa.Bndpageinfo;

/**
 * @author momo
 *
 */
public class RegisterWicketPageBndIdentifyImpl implements
		IRegisterWicketPageBndIdentify {
	
	@Autowired 
	private RegWicketPageBndDao regWicketPageBndDao;
	
	/* (non-Javadoc)
	 * @see com.gfactor.service.iface.IRegisterWicketPageBndIdentify#registerPageInfo()
	 */
	
	@Transactional
	public boolean registerPageInfo(Bndpageinfo bnd) {
		System.out.println("registerPageInfo , bnd = " bnd);
		 
		Bndpageinfo bndObj = regWicketPageBndDao.getBndPageInfo(bnd.getBundle_name(), bnd.getBundle_version(), bnd.getEntry_point());
		System.out.println("registerPageInfo , bndObj = " bndObj);
//		List<Bndpageinfo> findObj = regWicketPageBndDao.findBndPageInfoList(bnd.getBundle_name(), bnd.getBundle_version(), bnd.getEntry_point());
//		System.out.println("registerPageInfo , check findObj size = " findObj.size());
		 
		if(bndObj==null){
			System.out.println("register page info.......");
			regWicketPageBndDao.saveBndPageInfo(bnd);			
			return true;
		}else{
			System.out.println("page info already exists..........");
			return false;
		}

		
		
	}

	/* (non-Javadoc)
	 * @see com.gfactor.service.iface.IRegisterWicketPageBndIdentify#unregisterPageInfo()
	 */
	@Transactional
	public boolean unregisterPageInfo(Bndpageinfo bnd) {
		Bndpageinfo bndObj = regWicketPageBndDao.getBndPageInfo(bnd.getBundle_name(), bnd.getBundle_version(), bnd.getEntry_point());
//		List<Bndpageinfo> findObj = regWicketPageBndDao.findBndPageInfoList(bnd.getBundle_name(), bnd.getBundle_version(), bnd.getEntry_point());
//		System.out.println("unregisterPageInfo , check findObj size = " findObj.size());
		if(bndObj!=null){
			System.out.println("unregister page info....");
			regWicketPageBndDao.delete(bndObj);
			return true;
		}else{
			System.out.println("no page info delete , object doesn't exists.");
			return false;
		}
		
		
	}

}


DAO

RegWicketPageBndDao.java
package com.gfactor.service.internal.dao;

import java.util.List;

import com.gfactor.service.jpa.Bndpageinfo;


public interface RegWicketPageBndDao {
	public List findUser(String bndName,String bndVer,String entry_point);
	public void saveUser(Bndpageinfo bndpageinfo);
	public Bndpageinfo update(Bndpageinfo bndpageinfo);
	public void delete(Bndpageinfo bndpageinfo);
}


RegWicketPageBndDaoImpl.java
package com.gfactor.service.internal.dao.impl;

import java.util.List;

import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;

import org.springframework.orm.jpa.support.JpaDaoSupport;
import org.springframework.transaction.annotation.Transactional;

import com.gfactor.service.internal.dao.RegWicketPageBndDao;
import com.gfactor.service.jpa.Bndpageinfo;


/**
 * @author momo
 *
 */
public class RegWicketPageBndDaoImpl extends JpaDaoSupport implements RegWicketPageBndDao {
	
	@PersistenceUnit
	private EntityManagerFactory entityManagerFactory;
	
//	/**
//     * Constructor
//     */
//    @Autowired
//    public RegWicketPageBndDaoImpl(EntityManagerFactory entityManagerFactory) {
//    	System.out.println("to set entityManagerFactory ="+ entityManagerFactory);
//        super.setEntityManagerFactory(entityManagerFactory);
//    }
//    
    /**
     * get Bndpageinfo object by bndname,bndver and entry_point
     */
    
	public List findUser(String bndName, String bndVer,
			String entry_point) {
		return getJpaTemplate()
				.find("select r from Bndpageinfo r where r.bundle_name = ?1 and r.bundle_version =?2 and r.entry_point = ?3",
						bndName, bndVer, entry_point);
	}
	
	/* (non-Javadoc)
	 * @see com.gfactor.service.internal.dao.RegWicketPageBndDao#saveUser(com.gfactor.service.internal.jpa.Bndpageinfo)
	 */
	
	public void saveUser(Bndpageinfo bndpageinfo) {
		System.out.println("save user " + bndpageinfo);
		System.out.println("to get getEntityManager =" + getJpaTemplate().getEntityManager());
		System.out.println("to get getEntityManagerFactory =" + getJpaTemplate().getEntityManagerFactory());
		getJpaTemplate().persist(bndpageinfo);
		System.out.println("to get getEntityManager =" + getJpaTemplate().getEntityManager());
		
	}

	/* (non-Javadoc)
	 * @see com.gfactor.service.internal.dao.RegWicketPageBndDao#update(com.gfactor.service.internal.jpa.Bndpageinfo)
	 */
	
	public Bndpageinfo update(Bndpageinfo bndpageinfo) {
		return getJpaTemplate().merge(bndpageinfo);
	}

	/* (non-Javadoc)
	 * @see com.gfactor.service.internal.dao.RegWicketPageBndDao#delete(com.gfactor.service.internal.jpa.Bndpageinfo)
	 */
	
	public void delete(Bndpageinfo bndpageinfo) {
		
		getJpaTemplate().remove(bndpageinfo);

	}
}


再來是Spring的設定, 因為這邊是deploy在osgi container上, 所以datasource 來源也是osgi service , 下面是bundle-context-osgi.xml &amp; bundle-context.xml的設定
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 -->
       
        <!-- import the DataSource from OSGi -->
    <osgi:reference id="dataSource" interface="javax.sql.DataSource" />
</beans>

bundle-context.xml for hibernate jpa 1.0
<?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:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="
			http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
			http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
			http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
			http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.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:load-time-weaver weaver-class="org.eclipse.equinox.weaving.springweaver.EquinoxAspectsLoadTimeWeaver" 
		/> -->
	<context:load-time-weaver aspectj-weaving="on" />
	<context:annotation-config />
	<!-- -->
	<aop:aspectj-autoproxy />
	<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
	<!-- <tx:annotation-driven mode="proxy" proxy-target-class="true"></tx:annotation-driven> -->
	<bean
		class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
	<bean
		class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>

	
	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="plugin-web-domain"></property>
		<property name="dataSource" ref="dataSource"></property>	
	  <property name="jpaVendorAdapter"> 
	  	<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
			<property name="showSql" value="true" /> 
			<property name="generateDdl" value="true"/> 
			<property name="databasePlatform" value="org.hibernate.dialect.MySQLInnoDBDialect" />
	  </property>
	</bean>

	<!-- <bean id="queryUserDao" class="com.dao.impl.QueryUserDaoImpl" > <property 
		name="entityManagerFactory" ref="entityManagerFactory"/> </bean> -->



	<!-- defined dao bean start -->
	<bean id="regWicketPageBndDao"
		class="com.gfactor.service.internal.dao.impl.RegWicketPageBndDaoImpl">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>

	<bean id="registerWicketPageBndIdentify"
		class="com.gfactor.service.iface.internal.impl.RegisterWicketPageBndIdentifyImpl">

	</bean>

	<!-- activator bean for register page info to database at bundle start time. -->
	<bean id="bndActivator" class="com.gfactor.service.BndActivator"
		init-method="start" destroy-method="stop"></bean>

</beans>

MANIFEST.mf檔透過spring bundlor plugin可以生成,缺少的class 再手動補上就行,如果這邊想要JPA 2.0 + Hibernate 3.5↑的話就比較麻煩了一點,因為hibernate 給的jar檔是不支援osgi的,所以必須自己build一版出來,有需要的話可以參考下面的連結,
Creating custom Hibernate OSGi bundles for JPA 2.0

再來就要設定最麻煩的JPA 2.0 + EclipseLink 2.3, 下面的設定是目前的版本, 除了spring的設定 跟persistence.xml 的不同外,就是manifest.mf檔的improt-package要很完整,不然會跳一堆奇奇怪怪的exception出來....

JPA 2.0 + EclipseLink 2.3 Setting

先到EclipseLink 官網下載檔案,要選擇OSGI Bundle 版本的, Latest Release Downloads
下面的檔案放到$EclipseVirgoHome/repository/usr 下面
commonj.sdo_2.1.1.v200905221342.jar
  eclipselink-jpa-modelgen_2.3.0.v20110604-r9504.jar
  javax.persistence_2.0.3.v201010191057.jar
  javax.resource_1.5.0.jar
  javax.xml.bind_2.2.0.v201005080402.jar
  javax.xml.ws_2.0.0.v200902170419.jar
  org.eclipse.persistence.antlr_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.asm_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.core_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.dbws.builder_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.dbws_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.jpa.equinox.weaving_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.jpa.equinox_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.jpa.jpql_1.0.0.v20110604-r9504.jar
  org.eclipse.persistence.jpa.modelgen_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.jpa.osgi_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.jpa_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.moxy_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.oracle_2.3.0.v20110604-r9504.jar
  org.eclipse.persistence.sdo_2.3.0.v20110604-r9504.jar

然後這邊是已經有的(因為我的server用很久,不確定是否後來自己多加的,記的自我確認)
javax.activation_1.1.0.v201005080500.jar
        javax.jms_1.1.0.jar
        javax.mail_1.4.0.v201005080615.jar
        javax.persistence_1.0.0.v200905011740.jar
        javax.servlet_2.4.0.v200806031604.jar
        javax.transaction_1.1.0.v201002051055.jar
        javax.xml.soap_1.3.0.jar
        javax.xml.stream_1.0.1.v201004272200.jar


 

Providing the JPA metadata for EclipseLink

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
	version="2.0">

	<persistence-unit name="plugin-web-domain"
		transaction-type="RESOURCE_LOCAL">
		<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
		<!-- 
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		 -->
		<class> com.gfactor.service.jpa.Bndpageinfo</class>
		
		<properties>
			 <property name="eclipselink.weaving" value="true" />
		</properties>
		<!-- 
		<properties>
			<property name="hibernate.show_sql" value="true" />
			<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
			<property name="hibernate.jdbc.batch_size" value="100" />
		</properties>
		 -->
	</persistence-unit>

</persistence>


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" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="
			http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
			http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
			http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
			http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.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:load-time-weaver weaver-class="org.eclipse.equinox.weaving.springweaver.EquinoxAspectsLoadTimeWeaver" 
		/> -->
	<context:load-time-weaver aspectj-weaving="on" />
	<context:annotation-config />
	<!-- -->
	<aop:aspectj-autoproxy />
	<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
	<!-- <tx:annotation-driven mode="proxy" proxy-target-class="true"></tx:annotation-driven> -->
	<bean
		class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
	<bean
		class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	<!-- <property name="persistenceUnitName" value="SM" /> -->
	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="plugin-web-domain"></property>
		<property name="dataSource" ref="dataSource"></property>
		<property name="jpaVendorAdapter">
			<bean id="jpaVendorAdapter"
				class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter"
				p:databasePlatform="org.eclipse.persistence.platform.database.MySQLPlatform"
				p:showSql="true" />
		</property>
		<property name="loadTimeWeaver">
			<bean
				class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver " />
		</property>
		<!-- <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
			<property name="showSql" value="true" /> <property name="generateDdl" value="true" 
			/> <property name="databasePlatform" value="org.hibernate.dialect.MySQLInnoDBDialect" 
			/> </bean> </property> -->
	</bean>


	<!-- <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" 
		p:dataSource-ref="dataSource" p:persistenceUnitName="plugin-web-domain"> 
		<property name="jpaVendorAdapter"> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter" 
		p:databasePlatform="org.eclipse.persistence.platform.database.MySQLPlatform" 
		p:showSql="true" /> </property> </bean> -->




	<!-- <bean id="queryUserDao" class="com.dao.impl.QueryUserDaoImpl" > <property 
		name="entityManagerFactory" ref="entityManagerFactory"/> </bean> -->



	<!-- defined dao bean start -->
	<bean id="regWicketPageBndDao"
		class="com.gfactor.service.internal.dao.impl.RegWicketPageBndDaoImpl">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>

	<bean id="registerWicketPageBndIdentify"
		class="com.gfactor.service.iface.internal.impl.RegisterWicketPageBndIdentifyImpl">

	</bean>

	<!-- activator bean for register page info to database at bundle start time. -->
	<bean id="bndActivator" class="com.gfactor.service.BndActivator"
		init-method="start" destroy-method="stop"></bean>

</beans>
這邊最主要的問題是context:load-time-weaver, tx:annotation-driven的設定,如果import-package有缺少的話並不一定會有exception,但是transaction可能因此失效,
後面會提到碰到過的issue,再來是完整的MANIFEST.mf 
 
Manifest-Version: 1.0
Export-Package: com.gfactor.service;version="1.0.0";uses:="org.springframework.beans.factory.annotation",
 com.gfactor.service.iface;version="1.0.0";uses:="com.gfactor.service.jpa",
 com.gfactor.service.jpa;version="1.0.0";uses:="javax.persistence"
Unversioned-Imports: *
Built-By: momo
Tool: Bnd-1.43.0
Bundle-Name: Spring OSGi Bundle for jpa bundle service layer
Created-By: Apache Maven Bundle Plugin
Bundle-Version: 1.0.0
Build-Jdk: 1.6.0_26
Bnd-LastModified: 1313477275968
Bundle-ManifestVersion: 2
Import-Library: 
 org.springframework.spring;version="[3.0.3, 3.1)"
Import-Package: com.mysql.jdbc,
 javax.persistence,
 javax.persistence.criteria,
 javax.persistence.metamodel, 
 javax.persistence.spi,
 javax.sql,
 org.aopalliance.aop,
 org.aspectj.lang,
 org.aspectj.runtime.reflect,
 org.springframework.aop,
 org.springframework.aop.config, 
 org.springframework.aop.aspectj,
 org.springframework.aop.aspectj.annotation,
 org.springframework.aop.aspectj.autoproxy,
 org.springframework.aop.framework,
 org.springframework.stereotype,
 org.springframework.core,
 org.springframework.context,
 org.springframework.context.config,
 org.springframework.context.weaving,
 org.springframework.beans, 
 org.springframework.beans.factory, 
 org.springframework.beans.factory.wiring,
 org.springframework.beans.factory.aspectj,
 org.springframework.beans.factory.support,
 org.springframework.beans.factory.config,
 org.springframework.beans.factory.annotation,
 org.springframework.dao.annotation,
 org.springframework.dao.support,
 org.springframework.orm.jpa,
 org.springframework.orm.jpa.support,
 org.springframework.orm.jpa.vendor, 
 org.springframework.transaction,
 org.springframework.transaction.interceptor,
 org.springframework.transaction.support,
 org.springframework.transaction.annotation,
 org.springframework.transaction.aspectj,
 org.springframework.instrument.classloading,
 org.eclipse.persistence.jpa,
 org.eclipse.persistence.expressions,
 org.eclipse.persistence.internal.weaving,
 org.eclipse.persistence.internal.descriptors,
 org.eclipse.persistence.queries,
 org.eclipse.persistence.sessions,
 org.eclipse.persistence.descriptors.changetracking,
 net.sf.cglib.proxy,
 net.sf.cglib.core,
 net.sf.cglib.reflect
Bundle-SymbolicName: plugin-webdomain-service.jpa-eplk

然後在BndActivator.java裡面做測試,在bundle start,如下
package com.gfactor.service;

import org.springframework.beans.factory.annotation.Autowired;

import com.gfactor.service.iface.IRegisterWicketPageBndIdentify;
import com.gfactor.service.jpa.Bndpageinfo;

public class BndActivator {
	
	@Autowired
	private IRegisterWicketPageBndIdentify registerWicketPageBndIdentify;
	
	public void start(){
		System.out.println("to get registerWicketPageBndIdentify = "  registerWicketPageBndIdentify);
		Bndpageinfo bndobj = new Bndpageinfo();
//		bndobj.setId(10);
		bndobj.setBundle_name("test");
		bndobj.setBundle_version("1.0.0");
		bndobj.setClass_name("testPage");
		bndobj.setEntry_point("test-main");
//		registerWicketPageBndIdentify.registerPageInfo(bndobj);
		registerWicketPageBndIdentify.unregisterPageInfo(bndobj);
//		regWicketPageBndDao.update(bndobj);
		
//		List&lt;Bndpageinfo&gt; getobj =  regWicketPageBndDao.findUser("test2", "1.0.1", "mains") ;
//		System.out.println("getobj = "  getobj);
	}
	
	public void stop(){
		
	}

}
沒問題的話可以在log看到transaction相關的log information,
to get registerWicketPageBndIdentify = com.gfactor.service.iface.internal.impl.RegisterWicketPageBndIdentifyImpl@158adc7 
[2011-08-18 16:21:56.625] DEBUG region-dm-8                  o.s.transaction.annotation.AnnotationTransactionAttributeSource   Adding transactional method 'unregisterPageInfo' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' 
[2011-08-18 16:21:56.625] DEBUG region-dm-8                  o.s.beans.factory.support.DefaultListableBeanFactory              Returning cached instance of singleton bean 'transactionManager' 
[2011-08-18 16:21:56.625] DEBUG region-dm-8                  org.springframework.orm.jpa.JpaTransactionManager                 Creating new transaction with name [com.gfactor.service.iface.internal.impl.RegisterWicketPageBndIdentifyImpl.unregisterPageInfo]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        [EL Info]: 2011-08-18 16:21:56.625--ServerSession(21139682)--Thread(Thread[region-dm-8,5,main])--EclipseLink, version: Eclipse Persistence Services - 2.3.0.v20110604-r9504 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        [EL Config]: 2011-08-18 16:21:56.625--ServerSession(21139682)--Connection(30247146)--Thread(Thread[region-dm-8,5,main])--connecting(DatabaseLogin( 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	platform=&gt;MySQLPlatform 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	user name=&gt; "" 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	connector=&gt;JNDIConnector datasource name=&gt;null 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        )) 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        [EL Config]: 2011-08-18 16:21:56.625--ServerSession(21139682)--Connection(9801033)--Thread(Thread[region-dm-8,5,main])--Connected: jdbc:mysql://127.0.0.1:3306/test 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	User: root@localhost 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	Database: MySQL  Version: 5.5.10 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	Driver: MySQL-AB JDBC Driver  Version: mysql-connector-java-5.1.15 ( Revision: ${bzr.revision-id} ) 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        [EL Config]: 2011-08-18 16:21:56.625--ServerSession(21139682)--Connection(7085189)--Thread(Thread[region-dm-8,5,main])--connecting(DatabaseLogin( 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	platform=&gt;MySQLPlatform 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	user name=&gt; "" 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	connector=&gt;JNDIConnector datasource name=&gt;null 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        )) 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        [EL Config]: 2011-08-18 16:21:56.625--ServerSession(21139682)--Connection(30977512)--Thread(Thread[region-dm-8,5,main])--Connected: jdbc:mysql://127.0.0.1:3306/test 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	User: root@localhost 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	Database: MySQL  Version: 5.5.10 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	Driver: MySQL-AB JDBC Driver  Version: mysql-connector-java-5.1.15 ( Revision: ${bzr.revision-id} ) 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        [EL Info]: 2011-08-18 16:21:56.625--ServerSession(21139682)--Thread(Thread[region-dm-8,5,main])--file:/C:/Eclipse-Virgo-Tomcat-3.0/work/org.eclipse.virgo.kernel.deployer_3.0.0.RC1/staging/global/bundle/plugin-webdomain-service.jpa-eplk/1.0.0/plugin-webdomain-service.jpa-eplk.jar/_plugin-web-domain login successful 
[2011-08-18 16:21:56.625] DEBUG region-dm-8                  org.springframework.orm.jpa.JpaTransactionManager                 Opened new EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@33d742] for JPA transaction 
[2011-08-18 16:21:56.625] DEBUG region-dm-8                  org.springframework.orm.jpa.JpaTransactionManager                 Exposing JPA transaction as JDBC transaction [SimpleConnectionHandle: jdbc:mysql://127.0.0.1:3306/test, UserName=root@localhost, MySQL-AB JDBC Driver] 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        [EL Fine]: 2011-08-18 16:21:56.625--ClientSession(33415531)--Connection(14317413)--Thread(Thread[region-dm-8,5,main])--SELECT ID, bundle_name, bundle_version, class_name, entry_point FROM bndpageinfo WHERE (((bundle_name = ?) AND (bundle_version = ?)) AND (entry_point = ?)) 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        	bind =&gt; [test, 1.0.0, test-main] 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        unregisterPageInfo , check findObj size = 0 
[2011-08-18 16:21:56.625] INFO  region-dm-8                  System.out                                                        no page info delete , object doesn't exists. 
[2011-08-18 16:21:56.625] DEBUG region-dm-8                  org.springframework.orm.jpa.JpaTransactionManager                 Initiating transaction commit 
[2011-08-18 16:21:56.625] DEBUG region-dm-8                  org.springframework.orm.jpa.JpaTransactionManager                 Committing JPA transaction on EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@33d742] 
[2011-08-18 16:21:56.625] DEBUG region-dm-8                  org.springframework.orm.jpa.JpaTransactionManager                 Closing JPA EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@33d742] after transaction 
[2011-08-18 16:21:56.625] DEBUG region-dm-8                  org.springframework.orm.jpa.EntityManagerFactoryUtils             Closing JPA EntityManager 

issue resolve :

整合中碰到的一個exception,
org.aspectj.weaver.tools.jdk14trace error : cannot register non aspect...,然後指向一個class , annotationasyncexecutionaspect,後來找到的問題出在Manifest.mf檔的package,從 Spring transactions with aspectj on Virgo這邊看到相關的問題,這種狀況在Transaction 為jdk proxy 時不會出問題,換成aspectj 後就會找不到transaction,當然最常出現的都是dependency 的問題:xD


Reference:

Wicket, Spring 3, JPA2 &amp; Hibernate OSGi Application on Apache Karaf
 
Creating custom Hibernate OSGi bundles for JPA 2.0
 
WCreating an application with EclipseRT Virgo Web Server
 
EclipseLink with Spring - Can't persist into Db
 
在Eclise Virgo 上运行Struts2+Spring3+hibernate3.5