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