2011年6月27日 星期一

Spring + JMX

JMX(Java Management Extensions,即Java管理擴展)是Java平台上為應用程序、設備、系統等植入管理功能的框架, Spring 內部也有對JMX 的支持。
JMX Technology可以分成3層, 詳細的介紹可以參考 Lesson: Overview of the JMX Technology
  • Instrumentation
  • JMX agent
  • Remote management

Instrumentation : 定義了如何實現JMX管理資源的規範。一個JMX管理資源可以是一個Java應用、一個服
務或一個設備,它們可以用Java開發,或者至少能用Java進行包裝,並且能被置入JMX
框架中,從而成為JMX的一個管理構件(Managed Bean) ,簡稱MBean。
JMX agent : Agent 是 MBean Server 的容器,主要為了管理一系列的 MBean,並且提供的一系列
服務
Remote management : JMX 可以在許多不同的方式訪問,無論是通過現有的管理協議,如SNMP或通過
專有協議等, Each adaptor provides a view through a specific protocol
of all MBeans that are registered in the MBean server. For example, an
HTML adaptor could display an MBean in a browser.


JMX 的基本部分: 
MBean: 它是一個能代表管理资源的Java对象,它提供接口可以使這個class 有管理功能(如standard MBean,interface 中定義的方法使MBean具有管理功能)。
MBean server : 是管理MBean的一個java class,你需要向MBean server注册一個MBean,這個MBean才會具有管理功能。
JMX agent : agent是為了管理一系列的MBean,而提供的一系列的服務,agent可以利用Protocol adapters(例如HTTP 和SNMP)和connectors(RMI 和Jini)使不同的客户端可以访问MBean。


MBean的類型:
  • Standard MBeans
  • Dynamic MBeans
  • Open MBeans
  • Model MBeans
  • MXBeans

standard MBean : standard MBean 從必须定義為Interface 然后MBean必须implements 這個interface ,
它的命名也必须遵循一定的规範,如interface為Something , MBean implement 則為 
SomethingMBean.
dynamic MBean : 動態MBean是在運行期才定義它的屬性和方法,也就是說它有什麼屬性和方法
是可以動態改變的。動態MBean主要利用一些輔助類(構造函數類 
MBeanConstructorInfo、屬性類MBeanAttributeInfo、方法類
MBeanOperationInfo)來完成這個功能。
open MBean : An Open MBean is just a Dynamic MBean that follows some conventions which makes
it less powerful and convenient, but more portable. Open Mbeans Tutorial
model MBean : 與標準和動態的MBean相比,你可以不用寫的MBean類,只需使用
javax.management.modelmbean.RequiredModelMBean即可。與 DynamicMBean不
同的是,DynamicMBean管理的資源一般定義在DynamicMBean中(運行時才決定管理
那些資源),而模型MBean管理的資源並不在MBean的中,而是在外部(通常是一
個類),只有在運行時,才通過一套方法將其加入到模型MBean中

(JDK 5 內建了 jmx 的 client: jconsole。只要在 jvm 啟動時加上參數: )
-Dcom.sun.management.jmxremote 


   
  -Dcom.sun.management.jmxremote.port=1099 
  -Dcom.sun.management.jmxremote.ssl=false 
  -Dcom.sun.management.jmxremote.password.file=/mypath/jmxremote.password


簡介到這邊, 接下來主要是以Spring 來設定&設計 JMX 的部份, 如果想知道基本的JMX 部份可以參考Oracle 的Tutorials Standard MBeans ,

首先設計我們的MBean , 叫ManagerMBean.java
package com.jmxBean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

//@ManagerResource , 實例標記為由JMX管理的資源
@ManagedResource
public class ManagerMBean extends ApplicationObjectSupport implements DisposableBean  {

 
 private int threadPoolActiveSize;
 private boolean threadPoolStatus;
 
        //將getter或setter標記為JMX的屬性
 @ManagedAttribute(description="check how many thread on ThreadPoolTaskExecutor is active...")
 public int getThreadPoolActiveSize() {
  return threadPoolActiveSize;
 }


 public void setThreadPoolActiveSize(int threadPoolActiveSize) {
  this.threadPoolActiveSize = threadPoolActiveSize;
 }

 @ManagedAttribute(description="check ThreadPoolTaskExecutor are running.")
 public boolean isThreadPoolStatus() {
  return threadPoolStatus;
 }


 public void setThreadPoolStatus(boolean threadPoolStatus) {
  this.threadPoolStatus = threadPoolStatus;
 }

        ///把方法標記為JMX的操作
 @ManagedOperation(description = "shutdown the server")
 @Override
 public void destroy() throws Exception {
  ApplicationContext application  = getApplicationContext();
  System.out.println("Trigger command from jmx remote ..... ");
//  ThreadPoolTaskExecutor task = (ThreadPoolTaskExecutor) application.getBean("taskExecutor");
//  System.out.println("getActiveCount =  "   task.getActiveCount());
 }

}
這邊implements 的是spring 的DisposableBean , 實現org.springframework.beans.factory.DisposableBean interface 的bean 允許在容器銷毀該bean 的時候獲得一次回調,下面是相關Annotation 的說明,

Table 22.2. Source-Level Metadata Types
PurposeAnnotationAnnotation Type
Mark all instances of a Class as
JMX managed resources
@ManagedResourceClass
Mark a method as a JMX operation@ManagedOperationMethod
Mark a getter or setter as one half of a JMX
attribute
@ManagedAttributeMethod (only getters and setters)
Define descriptions for operation parameters@ManagedOperationParameter and
@ManagedOperationParameters
Method

還有parameter 的設定

Table 22.3. Source-Level Metadata Parameters
ParameterDescriptionApplies to
ObjectNameUsed by MetadataNamingStrategy
to determine the ObjectName of a
managed resource
ManagedResource
descriptionSets the friendly description of the resource,
attribute or operation
ManagedResource,
ManagedAttribute,
ManagedOperation,
ManagedOperationParameter
currencyTimeLimitSets the value of the
currencyTimeLimit descriptor field
ManagedResource,
ManagedAttribute
defaultValueSets the value of the defaultValue
descriptor field
ManagedAttribute
logSets the value of the log descriptor
field
ManagedResource
logFileSets the value of the logFile
descriptor field
ManagedResource
persistPolicySets the value of the persistPolicy
descriptor field
ManagedResource
persistPeriodSets the value of the persistPeriod
descriptor field
ManagedResource
persistLocationSets the value of the
persistLocation descriptor field
ManagedResource
persistNameSets the value of the persistName
descriptor field
ManagedResource
nameSets the display name of an operation parameterManagedOperationParameter
indexSets the index of an operation parameterManagedOperationParameter


然後我們在ApplicationContext.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:aop="http://www.springframework.org/schema/aop"
 xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

 <bean id="taskExecutor"
  class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
  <property name="corePoolSize" value="5" />
  <property name="maxPoolSize" value="10" />
  <property name="queueCapacity" value="25" />
 </bean>

 <bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
  <property name="port" value="1099" />
 </bean>

  
 <bean id="serverConnector"
  class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=rmi" />
  <property name="serviceUrl"
    value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector" />
    
  <!-- Set the rmi connection user,password that is in jmxremote.password file. -->
  <!-- 
  <property name="environmentMap">
   <map>
    <entry key="jmx.remote.x.password.file" value="/mypath/jmxremote.password">
    </entry>
   </map>
  </property>
   -->
 </bean>
 

 <!-- Step 1, defined MBean class (Instrumentation) -->
 <bean id="managerBean" class="com.jmxBean.ManagerMBean"></bean>



 <!-- Step 2, defined MBeanExporter class ,(JMX agent) -->
 <bean id="mBeanExporter" class="org.springframework.jmx.export.MBeanExporter">
  <property name="assembler" ref="assembler" />
  <property name="namingStrategy" ref="namingStrategy"/>
  <property name="autodetect" value="true"/>
  <!-- 
  <property name="beans">
   <map>
    <entry key="mbean:name=managerBean" value-ref="managerBean" />
   </map>
  </property>
   -->
 </bean>
 
 <bean id="jmxAttributeSource"
  class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource" />

 <bean id="assembler"
  class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
  <property name="attributeSource" ref="jmxAttributeSource" />
 </bean>
 
 <!-- will pick up the ObjectName from the annotation -->
 <bean id="namingStrategy"
  class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
  <property name="attributeSource" ref="jmxAttributeSource" />
 </bean>


 <!--  To create MBeanServer by JSR-160 for remote (Remote Management) -->
 <!--  
 <bean id="clientConnector"
  class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
  <property name="serviceUrl" value="service:jmx:rmi:///jndi/rmi://localhost:1099/myconnector" />
 </bean>
 -->
 <!--  Accessing MBeans via Proxies uses clientConnector  -->
 <!-- 
 <bean id="emailStopProxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
  <property name="objectName" value="mbean:name=managerBean" />
  <property name="proxyInterface"
   value="org.springframework.beans.factory.DisposableBean" />
  <property name="server" ref="clientConnector" />
 </bean>
 -->
</beans>

詳細說明一下整個XML的設定, 如果不是使用JDK 自帶的MBean server or web container 的話, 在Spring裡面要先register rmi , as below:
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
  <property name="port" value="1099" />
 </bean>


然後再來要設定serverConnector ,ObjectName 設為rmi , spring 會自動register MBserver connector 依照ObjectName ,下面透過ConnectorServerFactoryBean 建立一個JMXconnector, 如果要設定username,password的話必須設定environmentMap屬性
<bean id="serverConnector"
  class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=rmi" />
  <property name="serviceUrl"
    value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector" />
    
  <!-- Set the rmi connection user,password that is in jmxremote.password file. -->
  <!-- 
  <property name="environmentMap">
   <map>
    <entry key="jmx.remote.x.password.file" value="/mypath/jmxremote.password">
    </entry>
   </map>
  </property>
   -->
 </bean>


然後建立我們的MBean instance
<!-- Step 1, defined MBean class (Instrumentation) -->
 <bean id="managerBean" class="com.jmxBean.ManagerMBean"></bean>


再來是設定MBeanExporter , Spring 裡MBeanExporter 用來將你的MBean 註冊到JMX MBeanServer上,
assembler 這邊指向一個MetadataMBeanInfoAssembler,屬性指向一個AnnotationJmxAttributeSource,看到Annotation也就知道是用jdk1.5的Annotation了。用途是将你所需要變成MBean class的信息提取出来,並生成MBean。Spring大大简化了MBean的實現方式。namingStrategy這邊reference 是 MetadataNamingStrategy , 代表會使用 source level metadata來獲得 ObjectName.

<!-- Step 2, defined MBeanExporter class ,(JMX agent) -->
 <bean id="mBeanExporter" class="org.springframework.jmx.export.MBeanExporter">
  <property name="assembler" ref="assembler" />
  <property name="namingStrategy" ref="namingStrategy"/>
  <property name="autodetect" value="true"/>
  <!-- 
  <property name="beans">
   <map>
    <entry key="mbean:name=managerBean" value-ref="managerBean" />
   </map>
  </property>
   -->
 </bean>

其中mark 住的clientConnector 跟 emailStopProxy 是建立一個clientConnector 指向一個jmx rmi 的serverConnector , 再透過proxy 存取MBean , 相關設定可以參考Spring 的JMX設定,

再來透過jconsole 連線到server , 位置就是serverConnection的設定
service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector, as below :

連線之後就能看到ManagerMBean的information ,



Reference:

Spring Doc : 22. JMX
Trail: Java Management Extensions (JMX): Table of Contents
Quickly Exposing Spring Beans as JMX MBeans
Spring by Example : JMX
Jconsole Remote Management

2011年6月22日 星期三

Java Swing - Model-View-Controller (MVC) Structure


上圖是一個用Swing刻出來的簡單GUI, 目的是更新Database設定,Mailserver 設定....等等, 這邊GUI是依照Swing 的MVC 架構做出來的, Swing 的MVC包含了以下3個:

Model :包含了資料源和所有基於對這些資料的操作
View :Model 跟UI 介面的顯示
Control:在Model和View之間起到了溝通的作用,處理用戶在View上的輸入,並轉發給Model。這樣Model和View兩者之間可以做到鬆散耦合,甚至可以彼此不知道對方,而由Controller連接起這兩個部分。

在Oracle 的 Java SE Application Design With MVC 提到完整的MVC 細節如下:


  • Model - The model represents data and the rules that govern access to and updates of this data. In enterprise software, a model often serves as a software approximation of a real-world process.





  • View - The view renders the contents of a model. It specifies exactly how the model data should be presented. If the model data changes, the view must update its presentation as needed. This can be achieved by using a push model, in which the view registers itself with the model for change notifications, or apull model, in which the view is responsible for calling the model when it needs to retrieve the most current data.





  • Controller - The controller translates the user's interactions with the view into actions that the model will perform. In a stand-alone GUI client, user interactions could be button clicks or menu selections, whereas in an enterprise web application, they appear asGET andPOST HTTP requests. Depending on the context, a controller may also select a new view -- for example, a web page of results -- to present back to the user.

    整個GUI Project 結構如下:


    實作是參考 Model-View-Controller (MVC) Structure 而來的。
    首先是Model 的部份, 以GUI的Database Setting 來說是一個TabbedPanel裡的Item , 所以Model屬性會包含了所有相關元件的資料, 在這邊就是5種屬性, driverclassname,dburl,dbusername,dbuserpwd,databasetype, 我將這些封裝成一個JavaBean, 在DataBaseSettingModel裡面操作, DataBaseSettingModel針對DbSettingObject 做setter&getter 的動作,get時會從db拿值,set時會update db的資料。


    DbSettingObject.java
    package com.main.ui.dataobject;
    
    public class DbSettingObject {
     private String driverClassName;
     private String dbUrl;
     private String dbUserName;
     private String dbUserPwd;
     private String dataBaseType;
     
     public DbSettingObject(){
      this.driverClassName = "";
      this.dbUrl = "";
      this.dbUserName = "";
      this.dbUserPwd = "";
      this.dataBaseType =  "";
      
     }
     
     public String getDriverClassName() {
      return driverClassName;
     }
     public void setDriverClassName(String driverClassName) {
      this.driverClassName = driverClassName;
     }
     public String getDbUrl() {
      return dbUrl;
     }
     public void setDbUrl(String dbUrl) {
      this.dbUrl = dbUrl;
     }
     public String getDbUserName() {
      return dbUserName;
     }
     public void setDbUserName(String dbUserName) {
      this.dbUserName = dbUserName;
     }
     public String getDbUserPwd() {
      return dbUserPwd;
     }
     public void setDbUserPwd(String dbUserPwd) {
      this.dbUserPwd = dbUserPwd;
     }
     public String getDataBaseType() {
      return dataBaseType;
     }
     public void setDataBaseType(String dataBaseType) {
      this.dataBaseType = dataBaseType;
     }
     
    
     @Override
     public String toString(){
      StringBuffer tostring = new StringBuffer();
      
      tostring.append("driverClassName = "   driverClassName "\n");
      tostring.append("dbUrl = "   dbUrl "\n");
      tostring.append("dbUserName = "   dbUserName "\n");
      tostring.append("dbUserPwd = "   dbUserPwd "\n");
      tostring.append("dataBaseType = "   dataBaseType "\n");
      
      return tostring.toString();
     }
     
    }
    


    DbSettingModel.java
    package com.main.ui.model;
    
    import java.sql.SQLException;
    
    import com.main.dao.IQueryUiObjectDao;
    import com.main.dao.QueryDatabaseSettingDao;
    import com.main.ui.dataobject.DbSettingObject;
    
    public class DbSettingModel {
     
     private DbSettingObject dbObj;
     private IQueryUiObjectDao<DbSettingObject> queryDao;
     
     public DbSettingModel(){
      queryDao = new QueryDatabaseSettingDao();
     }
     
     public DbSettingObject getDbSettingObject(){
      try {
       this.dbObj = queryDao.getDataObject();
      } catch (SQLException e) {
       dbObj = new DbSettingObject();
      }  
      return dbObj;
     }
    
    
     
     public void setDbSettingObject(DbSettingObject obj){
      try {
       queryDao.setDataObject(obj);
      } catch (SQLException e) {
      }
      this.dbObj = obj;
     }
    }
    

    再來是View 的部份, DataBase Setting 的Panel設計如下
    一個View Object 是extends JPanel ,建立的相關的UI, 取得或修改UI的顯示,view 會提供Controller 相關的method 來操作。

    DbSettingView.java
    package com.main.ui.view;
    import java.awt.event.ActionListener;
    
    import javax.swing.JButton;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JSeparator;
    import javax.swing.JTextField;
    
    import com.main.ui.dataobject.DbSettingObject;
    import com.main.ui.model.DbSettingModel;
    
    public class DbSettingView extends JPanel{
     private JLabel passwd;
     private JLabel databaseType;
     private JTextField changeDbType;
     private JTextField changeUserName;
     private JTextField changePassword;
     private JTextField changeDbUrl;
     private JTextField changeDriverClassName;
     private JButton Save;
     private JSeparator jSeparator1;
     private JTextField passwordValue;
     private JTextField userNameValue;
     private JTextField dbUrlValue;
     private JTextField dbTypeValue;
     private JTextField driverClassNameValue;
     private JLabel dbUserName;
     private JLabel dbUrl;
     private JLabel DriverClassName;
     private DbSettingModel model ;
     
     public DbSettingView(DbSettingModel imodel){
      this.model = imodel;
      initGUI();
      initTextValues();
     }
     
     private void initGUI() {
      try {
       {
        this.setPreferredSize(new java.awt.Dimension(498, 222));
        this.setRequestFocusEnabled(false);
        this.setOpaque(false);
        this.setLayout(null);
        {
         DriverClassName = new JLabel();
         this.add(DriverClassName);
         DriverClassName.setBounds(12, 12, 119, 19);
         DriverClassName.setText("Driver Class Name:");
        }
        {
         dbUrl = new JLabel();
         this.add(dbUrl);
         dbUrl.setText("DB Url :");
         dbUrl.setBounds(12, 59, 95, 15);
        }
        {
         dbUserName = new JLabel();
         this.add(dbUserName);
         dbUserName.setText("UserName :");
         dbUserName.setBounds(12, 81, 95, 15);
        }
        {
         passwd = new JLabel();
         this.add(passwd);
         passwd.setText("Password :");
         passwd.setBounds(12, 102, 95, 15);
        }
        {
         databaseType = new JLabel();
         this.add(databaseType);
         databaseType.setText("DB Type :");
         databaseType.setBounds(12, 37, 95, 15);
        }
        {
         driverClassNameValue = new JTextField();
         this.add(driverClassNameValue);
         driverClassNameValue.setBounds(143, 11, 146, 22);
         driverClassNameValue.setEnabled(false);
        }
        {
         dbTypeValue = new JTextField();
         this.add(dbTypeValue);
         dbTypeValue.setBounds(143, 34, 146, 22);
         dbTypeValue.setEnabled(false);
        }
        {
         dbUrlValue = new JTextField();
         this.add(dbUrlValue);
         dbUrlValue.setBounds(143, 56, 146, 22);
         dbUrlValue.setEnabled(false);
        }
        {
         userNameValue = new JTextField();
         this.add(userNameValue);
         userNameValue.setBounds(143, 78, 146, 22);
         userNameValue.setEnabled(false);
        }
        {
         passwordValue = new JTextField();
         this.add(passwordValue);
         passwordValue.setBounds(143, 99, 146, 22);
         passwordValue.setEnabled(false);
        }
        {
         jSeparator1 = new JSeparator();
         this.add(jSeparator1);
         jSeparator1.setBounds(12, 133, 447, 7);
        }
        {
         Save = new JButton();
         this.add(Save);
         Save.setText("Save");
         Save.setBounds(394, 146, 65, 22);
        }
        {
         changeDriverClassName = new JTextField();
         this.add(changeDriverClassName);
         changeDriverClassName.setEnabled(true);
         changeDriverClassName.setBounds(313, 11, 146, 22);
        }
        {
         changeDbUrl = new JTextField();
         this.add(changeDbUrl);
         changeDbUrl.setEnabled(true);
         changeDbUrl.setBounds(313, 56, 146, 22);
        }
        {
         changePassword = new JTextField();
         this.add(changePassword);
         changePassword.setEnabled(true);
         changePassword.setBounds(313, 99, 146, 22);
        }
        {
         changeUserName = new JTextField();
         this.add(changeUserName);
         changeUserName.setEnabled(true);
         changeUserName.setBounds(313, 78, 146, 22);
        }
        {
         changeDbType = new JTextField();
         this.add(changeDbType);
         changeDbType.setEnabled(true);
         changeDbType.setBounds(313, 34, 146, 22);
        }
       }
      } catch(Exception e) {
       e.printStackTrace();
      }
     }
     
     private void initTextValues(){
      System.out.println("init text values , dbSettingView");
      if(this.model != null){
       DbSettingObject obj = this.model.getDbSettingObject();
       this.dbTypeValue.setText(obj.getDataBaseType());
       this.dbUrlValue.setText(obj.getDbUrl());
       this.userNameValue.setText(obj.getDbUserName());
       this.passwordValue.setText(obj.getDbUserPwd());
       this.driverClassNameValue.setText(obj.getDriverClassName());
       
      }
     }
     
     public void changeSetting(DbSettingObject object){
      this.dbTypeValue.setText(object.getDataBaseType());
      this.dbUrlValue.setText(object.getDbUrl());
      this.userNameValue.setText(object.getDbUserName());
      this.passwordValue.setText(object.getDbUserPwd());
      this.driverClassNameValue.setText(object.getDriverClassName());
     }
     
     public DbSettingObject getCurrentSettingObject(){
      DbSettingObject currentObject = new DbSettingObject();
      currentObject.setDataBaseType(this.dbTypeValue.getText());
      currentObject.setDbUrl(this.dbUrlValue.getText());
      currentObject.setDbUserName(this.userNameValue.getText());
      currentObject.setDbUserPwd(this.passwordValue.getText());;
      currentObject.setDriverClassName(this.driverClassNameValue.getText());
      
      return currentObject;
     }
     
     
     public void addChangeSettingListener(ActionListener mal){
      this.Save.addActionListener(mal);
     }
     
     public JLabel getPasswd() {
      return passwd;
     }
    
     public JLabel getDatabaseType() {
      return databaseType;
     }
    
     public JTextField getChangeDbType() {
      return changeDbType;
     }
    
     public JTextField getChangeUserName() {
      return changeUserName;
     }
    
     public JTextField getChangePassword() {
      return changePassword;
     }
    
     public JTextField getChangeDbUrl() {
      return changeDbUrl;
     }
    
     public JTextField getChangeDriverClassName() {
      return changeDriverClassName;
     }
    
     public JButton getSave() {
      return Save;
     }
    
     public JSeparator getjSeparator1() {
      return jSeparator1;
     }
    
     public JTextField getPasswordValue() {
      return passwordValue;
     }
    
     public JTextField getUserNameValue() {
      return userNameValue;
     }
    
     public JTextField getDbUrlValue() {
      return dbUrlValue;
     }
    
     public JTextField getDbTypeValue() {
      return dbTypeValue;
     }
    
     public JTextField getDriverClassNameValue() {
      return driverClassNameValue;
     }
    
     public JLabel getDbUserName() {
      return dbUserName;
     }
    
     public JLabel getDbUrl() {
      return dbUrl;
     }
    
     public JLabel getDriverClassName() {
      return DriverClassName;
     } 
    }
    

    再來建立Controller , 會傳入model 跟 view, 然後統過Constructor 新增view的listener , 在此操作Model 跟view 的相關動作。

    DbSettingController.java
    package com.main.ui.controller;
    
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    import com.main.ui.dataobject.DbSettingObject;
    import com.main.ui.model.DbSettingModel;
    import com.main.ui.view.DbSettingView;
    
    
    public class DbSettingController {
     private DbSettingModel model;
     private DbSettingView view;
     
     public DbSettingController(DbSettingModel iModel,DbSettingView iView ){
      this.model = iModel;
      this.view = iView;
      this.view.addChangeSettingListener(new ChangeDbSettingListener());
     }
     
     class ChangeDbSettingListener implements ActionListener {
    
      //Actionlistener for change database setting when "save" button  on click
      public void actionPerformed(ActionEvent e) {
       
       DbSettingObject newSettingobject = getNewSettingObject();   
       DbSettingObject currentSettingObj = view.getCurrentSettingObject();     
       changeNewSettingObjectValues(newSettingobject, currentSettingObj);
       
       model.setDbSettingObject(newSettingobject);//update object & database values   
       view.changeSetting(model.getDbSettingObject());//get object from database
      }
    
      private void changeNewSettingObjectValues(DbSettingObject newSettingobject,DbSettingObject currentSettingObj) {
       if(newSettingobject.getDriverClassName().length() <= 0){
        newSettingobject.setDriverClassName(currentSettingObj.getDriverClassName());
       }
       if(newSettingobject.getDbUrl().length() <= 0){
        newSettingobject.setDbUrl(currentSettingObj.getDbUrl());
       }
       if(newSettingobject.getDataBaseType().length() <= 0){
        newSettingobject.setDataBaseType(currentSettingObj.getDataBaseType());
       }
       if(newSettingobject.getDbUserName().length() <= 0){
        newSettingobject.setDbUserName(currentSettingObj.getDbUserName());
       }
       if(newSettingobject.getDbUserPwd().length() <= 0){
        newSettingobject.setDbUserPwd(currentSettingObj.getDbUserPwd());
       }
      }
    
      private DbSettingObject getNewSettingObject() {
       DbSettingObject newSettingobject = new DbSettingObject();   
       newSettingobject.setDataBaseType(view.getChangeDbType().getText());
       newSettingobject.setDbUrl(view.getChangeDbUrl().getText());
       newSettingobject.setDbUserName(view.getChangeUserName().getText());
       newSettingobject.setDbUserPwd(view.getChangePassword().getText());
       newSettingobject.setDriverClassName(view.getChangeDriverClassName().getText());
       return newSettingobject;
      }
      
      
      
    //  private DbSettingObject checkObjectContent(){
    //   
    //  }
      
     }
    }
    
    

    當我們的元系都設計好的時候,就可以建立起來
    ....
    ....
    DbSettingModel dbSettingmodel = new DbSettingModel();
    DbSettingView dbSettingPanel = new DbSettingView(dbSettingmodel);
    DbSettingController controller = new DbSettingController(dbSettingmodel,dbSettingPanel);        
    tabbedPane.addTab("DataBase Setting", icon, dbSettingPanel,"change db setting values");
    dbSettingPanel.setPreferredSize(new java.awt.Dimension(551, 211));
    ...
    ...
    



    Reference:

    Java SE Application Design With MVC
    Model-View-Controller (MVC) Structure
    A Swing Architecture Overview
    Building Graphical User Interfaces
    with the MVC Pattern

    Javaworld.com - MVC meets Swing
    Javaworld(TW) Swing MVC and Model

  • 2011年6月16日 星期四

    XSLT - Using XSLT to generate HTML from XML

    可擴展樣式錶轉換語言(Extensible Stylesheet Language Transformations,簡稱XSLT) , 前陣子剛碰到這玩意, 可以用來對XML檔做轉換的動作, 這次是用在把XML > HTML ,XSLT本身也是一份XML檔案,所以它也必須遵守嚴格的XML規範。 XSLT Wiki 有一些說明,還有一些link , 建議可以先看看XSLT的簡介, 還有XPATH的範例說明等,下面2個連結可以先參考看看:
    XPATH 語法
    XSLT 基礎

    這邊主要是針對XML to HTML做處理, 還有在XSLT裡面使用java method , javabean 來達到一些效果。

    原始的XML檔案大概長這樣,實作EDRM 的mail XML 檔會長的...非常難看
    <?xml version='1.0' encoding='UTF-8' ?>
      <Root caseId="Case1" description="Test Case" locale="US" majorVersion="1" minorVersion="2" rootFilePath="/temp/">
        <batch>
          <Document MimeType="multipart/mixed" DocType="Header" DocID="1">
            <FieldValues>
              <contentType>multipart/mixed;
     boundary="------------010709010402030905080005"</contentType>
              <mailAuthor>N2&lt;tamino2@tsai.james&gt;</mailAuthor>
              <mailCC>tamino0@tsai.james</mailCC>
              <mailSubject>2011/06/14-test01</mailSubject>
              <mailTo>tamino1@tsai.james,tamino0@tsai.james</mailTo>
              <messageID>&lt;4DF6BDD9.7090905@tsai.james&gt;</messageID>
            </FieldValues >
          </Document>
          <Document MimeType="multipart/alternative" DocType="MIME Entity" DocID="2">
            <FieldValues>
              <contentType>multipart/alternative;
     boundary="------------080407070307020309080702"</contentType>
            </FieldValues>
          </Document>
          <Document MimeType="text/plain" DocType="MIME Entity" DocID="3">
            <FieldValues>
              <content>aabbccd
    *eeffggy*
    myImg1
    
    
    myImg2
    
    </content>
              <contentType>text/plain; charset=UTF-8; format=flowed</contentType>
            </FieldValues>
          </Document>
          <Document MimeType="multipart/related" DocType="MIME Entity" DocID="4">
            <FieldValues>
              <contentType>multipart/related;
     boundary="------------000300090903000901010201"</contentType>
            </FieldValues>
          </Document>
          <Document MimeType="text/html" DocType="MIME Entity" DocID="5">
            <FieldValues>
              <content>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"&gt;
    &lt;html&gt;
      &lt;head&gt;
    
        &lt;meta http-equiv="content-type" content="text/html; charset=UTF-8"&gt;
      &lt;/head&gt;
      &lt;body text="#000000" bgcolor="#ffffff"&gt;
        aabbccd&lt;br&gt;
        &lt;b&gt;&lt;big&gt;&lt;big&gt;&lt;big&gt;eeffggy&lt;/big&gt;&lt;/big&gt;&lt;/big&gt;&lt;/b&gt;&lt;br&gt;
        myImg1&lt;br&gt;
        &lt;img src="cid:part1.03050007.07010308@tsai.james" alt=""&gt;&lt;br&gt;
        &lt;br&gt;
        myImg2&lt;br&gt;
        &lt;img src="cid:part2.01030500.03060103@tsai.james" alt=""&gt;&lt;br&gt;
      &lt;/body&gt;
    &lt;/html&gt;
    </content>
              <contentType>text/html; charset=UTF-8</contentType>
            </FieldValues>
          </Document>
          <Document MimeType="image/jpeg" DocType="MIME Entity" DocID="6">
            <FieldValues>
              <content>/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a
    HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy
    MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAaAVADASIA
    </content>
              <contentID>&lt;part1.03050007.07010308@tsai.james&gt;</contentID>
              <contentTransferEncoding>base64</contentTransferEncoding>
              <contentType>image/jpeg;
     name="new_pagingtoolbar.JPG"</contentType>
            </FieldValues>
          </Document>
          <Document MimeType="image/jpeg" DocType="MIME Entity" DocID="7">
            <FieldValues>
              <content>/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a
    HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy
    </content>
              <contentID>&lt;part2.01030500.03060103@tsai.james&gt;</contentID>
              <contentTransferEncoding>base64</contentTransferEncoding>
              <contentType>image/jpeg;
     name="SaveAs_SubMenu.JPG"</contentType>
            </FieldValues>
          </Document>
          <Document MimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" DocType="Attach File" DocID="8">
            <FieldValues>
              <contentTransferEncoding>7bit</contentTransferEncoding>
              <contentType>application/vnd.openxmlformats-officedocument.wordprocessingml.document;
     name="temp.docx"</contentType>
            </FieldValues>
          </Document>
          <Document MimeType="application/octet-stream" DocType="Attach File" DocID="9">
            <FieldValues>
              <contentTransferEncoding>base64</contentTransferEncoding>
              <contentType>application/octet-stream;
     name="test.rar"</contentType>
            </FieldValues>
          </Document>
        </batch>
        <fields/>
      </Root>
    
    為了閱讀方便,我把file content 的base64 encode 部份刪掉部份,所以上面的xml是會有問題的, 一般使用XSLT在做XML轉換的時候,如果XML定義檔很單純簡單的話,XSL檔的設計上就會簡單很多,碰到上面這種XML很復雜的時候處理上就會麻煩點。先來看看轉出來的結果是長什麼樣子, 轉成HTML 檔 或著是回傳HTML String ,大概像這樣:

    說穿了只是把原始的E-Mail內容轉換成EDRM XML , 然後會有一堆XML檔,透過XSLT轉換成HTML View , 所以在XSL的設計上就要把相關的屬性取出來,然後變成HTML, 下面是實作出來的XSL檔
    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
     xmlns:cu="com.mypackage.util.XsltCommonUtils"
     xmlns:obj="com.mypackage.xsltObject.XsltDataObject"
     exclude-result-prefixes="cu obj" >
     
     <xsl:output method="html" encoding="UTF-8" />
     
     
     <xsl:variable name="Documents">
      <xsl:value-of select="/Documents" />
     </xsl:variable>
     <xsl:variable name="From"> 
      <xsl:value-of select="cu:processEscapingString(//FieldValues/mailAuthor)" disable-output-escaping="yes"/>
     </xsl:variable>
     <xsl:variable name="To">
      <xsl:value-of select="//FieldValues/mailTo" />
     </xsl:variable>
     <xsl:variable name="Cc">
      <xsl:value-of select="//FieldValues/mailCC" />
     </xsl:variable>
     <xsl:variable name="Bcc">
      <xsl:value-of select="//FieldValues/mailBCC" />
     </xsl:variable>
     <xsl:variable name="SendDate">
      <xsl:value-of select="//FieldValues/mailSentDate" />
     </xsl:variable>
     <xsl:variable name="Subject">
      <xsl:value-of select="//FieldValues/mailSubject" />
     </xsl:variable>
     <xsl:variable name="uid">
      <xsl:value-of select="//FieldValues/messageID" />
     </xsl:variable>
     
     <xsl:variable name="bodyObj" select="obj:new()"/>  
     
     <xsl:param name="htmlBody">
      <xsl:for-each select="//Document">
       <xsl:choose>
        <xsl:when test="contains(@MimeType,'text/html')">
            <xsl:value-of select="cu:setXlstDataObject($bodyObj,FieldValues/content)" disable-output-escaping="yes"/>     
            <xsl:value-of select="cu:setXlsDataObjectIsHtml($bodyObj)"/>
        </xsl:when>
       </xsl:choose>
      </xsl:for-each>       
     </xsl:param>
    
     <xsl:variable name="textBody">
      <xsl:if test="not(cu:getXlsDataObjectIsHtml($bodyObj))">
       <xsl:for-each select="//Document">
        <xsl:choose>
        <xsl:when test="contains(@MimeType,'text/plain')">
            <xsl:if test="contains(@DocType,'MIME Entity')">
             <xsl:value-of select="//FieldValues/content" />
            </xsl:if> 
        </xsl:when>
       </xsl:choose>
       </xsl:for-each>    
      </xsl:if> 
     </xsl:variable>
     
     <xsl:template match="/">
      <html>
       <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <style type="text/css">
         BODY, TD {
         font-family: Arial;
         color: black;
         font-size: 9pt;
         font-weight: normal;
         text-decoration: none; }
         * {margin:0; padding:0;}
         .header{
         background-image: url(img/bg_line.jpg);
         background-repeat: repeat-x;
         background-position: left top;
         height: 94px;
         width: 100%;
         }
         .headerbg{
         background-image: url(img/bg.jpg);
         background-repeat: no-repeat;
         background-position: left top;
         height: 94px;
         width: 100%;
         }
         .main{
         margin-right: 5px;
         padding-top: 15px;
         width: 200px;
         }
         .right{
         float: right;
         margin-right: 5px;
         margin-left: 3px;
         padding-top: 4px;
         text-align: right;
         }
         .left{
         float: left;
         margin-right: 5px;
         margin-left: 3px;
         padding-top: 4px;
         text-align: left;
         }
         .logoimg{
         width: 50px;
         padding-right: 5px;
         padding-left: 10px;
         padding-top: 15px;
         }
    
         .input_a{
         border: 1px solid #333;
         font-family: Arial;
         font-size: 9pt;
         font-weight: normal;
         color: #000;
         height: 18px;
         width: 312px;
         background-image: url(img/usericon.jpg);
         background-repeat: no-repeat;
         background-color: #FFF;
         padding-left: 17px;
         background-position: 2px;
         }
         .input_b{
         border: 1px solid #333;
         font-family: Arial;
         font-size: 9pt;
         font-weight: normal;
         color: #000;
         height: 18px;
         width: 500px;
         background-color: #FFF;
         padding-left: 0px;
         background-position: 2px;
         }
         TABLE.tablestyle1 TD, TABLE.tablestyle1
         TD { border: 1 solid black;
         }
         TABLE.tablestyle1 { border-collapse:
         collapse; border-spacing: 0px
         0px; empty-cells: show }
        </style>
       </head>
       <body>
       
        
        <table width="100%" border="0" cellspacing="0" cellpadding="0">
         <tr>
          <td align="left" valign="top" class="headerbg">
           <table width="100%" border="0" cellspacing="0" cellpadding="0">
            <tr>
             <td align="left" valign="top" class="logoimg">
              <img src="img/logo.gif" width="60" height="60" alt="" />
             </td>
             <td align="left" valign="top" class="main">
              <b>
               <xsl:value-of select="$From"
                disable-output-escaping="yes" />
              </b>
              <br />
              <xsl:value-of select="$SendDate" />
             </td>
             <td align="left" valign="top" class="left">
              <table width="50px" border="0" cellspacing="1"
               cellpadding="0">
               <tr>
                <td align="right" valign="top">To%uFF1A</td>
                <td width="20%" align="right" valign="top">
                 <xsl:text disable-output-escaping="yes"><![CDATA[<input type="text" size="50" class="input_b" readonly="true" value=']]></xsl:text>
                 <xsl:value-of select="$To"
                  disable-output-escaping="yes" />
                 <xsl:text disable-output-escaping="yes"><![CDATA['/>]]></xsl:text>
                </td>
               </tr>
               <tr>
                <td align="right" valign="top">cc%uFF1A</td>
                <td align="right" valign="top">
                 <xsl:text disable-output-escaping="yes"><![CDATA[<input type="text" size="50" class="input_b" readonly="true" value=']]></xsl:text>
                 <xsl:value-of select="$Cc"
                  disable-output-escaping="yes" />
                 <xsl:text disable-output-escaping="yes"><![CDATA['/>]]></xsl:text>
                </td>
               </tr>
               <tr>
                <td align="right" valign="top">bcc%uFF1A</td>
                <td align="right" valign="top">
                 <xsl:text disable-output-escaping="yes"><![CDATA[<input type="text" size="50" class="input_b" readonly="true" value=']]></xsl:text>
                 <xsl:value-of select="$Bcc"
                  disable-output-escaping="yes" />
                 <xsl:text disable-output-escaping="yes"><![CDATA['/>]]></xsl:text>
                </td>
               </tr>
               <tr>
                <td align="right" valign="top">Subject%uFF1A</td>
                <td align="right" valign="top">
                 <xsl:text disable-output-escaping="yes"><![CDATA[<input type="text" size="50" class="input_b" readonly="true" value=']]></xsl:text>
                 <xsl:value-of select="$Subject"
                  disable-output-escaping="yes" />
                 <xsl:text disable-output-escaping="yes"><![CDATA['/>]]></xsl:text>
                </td>
               </tr>
              </table>
             </td>
            </tr>
           </table>
          </td>
         </tr>
        </table>
        <br />
        <table width="100%">
         <tr>
          <td>
           
           <!-- 
           <xsl:for-each select="//item[@name='Body']/itemdata[@type='19']">
            <pd4ml-include encoding="qp" debug="false">
             <xsl:copy-of select="text()" />
            </pd4ml-include>
           </xsl:for-each>
    
           <xsl:for-each select="//item/rawitemdata[@type='19']">
            <table border="0" width="100%">
             <tr>
              <td style="word-wrap: break-word">
               <pd4ml-include xencoding="qp" debug="false">
                <xsl:copy-of select="text()" />
               </pd4ml-include>
              </td>
             </tr>
            </table>
           </xsl:for-each>
            -->
           <!-- 
           <xsl:for-each
            select="//item[@name='$FILE']/object/file[@name='mime.htm']/filedata">
            <pd4ml-include debug="false">
             <xsl:attribute name="encoding"><xsl:value-of
              select="../@encoding" /></xsl:attribute>
             <xsl:copy-of select="text()" />
            </pd4ml-include>
           </xsl:for-each>
           
           <xsl:apply-templates select="item" />
            -->
           <!-- commented out because file[contains(@name,'mime.gif')] causes 
            java.lang.ArrayIndexOutOfBoundsException in Xalan under some conditions xsl:for-each 
            select="//item[@name='$FILE']/object/file[contains(@name,'mime.gif')]/filedata"> 
            <img> <xsl:attribute name="src">data:image/gif;base64,<xsl:value-of select="normalize-space(.)" 
            disable-output-escaping="yes"/></xsl:attribute> <xsl:attribute name="border">0</xsl:attribute> 
            </img> </xsl:for-each -->
          </td>
         </tr>
         </table>
         <!-- 
        <xsl:for-each select="//Document">
         <xsl:choose>
          <xsl:when test="contains(@DocType,'MIME Entity')">
           <xsl:value-of select="Document/FieldValues/ContentID" disable-output-escaping="yes"/>  
           <xsl:value-of select="cu:testNodeSet(Document/FieldValues)" />
          </xsl:when>
       
         </xsl:choose>
        </xsl:for-each>
         -->
         
         
         <!--  to get body string with inline images. -->
         <xsl:apply-templates select="//Document" ></xsl:apply-templates>
         
         <xsl:value-of select="$textBody" disable-output-escaping="yes"/>
         <xsl:value-of select="cu:getXlstDataObject($bodyObj)" disable-output-escaping="yes"/>
         
       </body>
      </html>
     </xsl:template>
     
     
     <xsl:template match="Document">    
       <xsl:choose>
         <xsl:when test="contains(@MimeType,'image')">
           <xsl:variable name="contentId">
            <xsl:value-of select="FieldValues/contentID" disable-output-escaping="yes"/> 
           </xsl:variable>
           <xsl:variable name="content">       
            <xsl:value-of select="FieldValues/content" disable-output-escaping="yes"/>
           </xsl:variable>
           <xsl:variable name="fileType">       
            <xsl:value-of select="Files/File/@FileType" disable-output-escaping="yes"/>
           </xsl:variable>
    
           <xsl:variable name="filePath">
            <xsl:value-of select="Files/File/ExternalFile/@FilePath" disable-output-escaping="yes"/>
           </xsl:variable>
           <xsl:variable name="fileName">       
            <xsl:value-of select="Files/File/ExternalFile/@FileName" disable-output-escaping="yes"/>
           </xsl:variable>
           <xsl:variable name="hashMd5">
            <xsl:value-of select="Files/File/ExternalFile/@Hash" disable-output-escaping="yes"/>
           </xsl:variable>
          <xsl:value-of select="cu:setDataObjectBodyByInlineImageUrl($bodyObj,$contentId,$fileType,$content,$fileName,$hashMd5) "  disable-output-escaping="yes"/>               
        </xsl:when>
        <xsl:when test="contains(@DocType,'Attach File')">
         <!-- do something -->
        </xsl:when>
       </xsl:choose>   
     </xsl:template>
     
    </xsl:stylesheet> 
    


    還有相關的Java Class
    XsltDataObject.java
    package com.mypackage.xsltObject;
    
    public class XsltDataObject {
     private  String bodyString;
     private boolean isHtml = false;
     
     
     
     
     public boolean isHtml() {
      return isHtml;
     }
    
     public void setHtml(boolean isHtml) {
      this.isHtml = isHtml;
     }
    
     public  String getBodyString() {
      return bodyString;
     }
    
     public  void setBodyString(String bodyString) {
      this.bodyString = bodyString;
     }
    }
    

    還有XsltCommonUtils.java
    package com.mypackage.util;
    
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    
    import com.mypackage.xsltObject.XsltDataObject;
    
    public class XsltCommonUtils {
     
     
     
     public static void setXlstDataObject(XsltDataObject object,String bodyString){
      String bodyStr = bodyString;
      bodyStr = bodyStr.replaceAll("cid:", "");
      object.setBodyString(bodyStr);
     }
     
     public static String getXlstDataObject(XsltDataObject object){
      return object.getBodyString();
     }
     
     public static void setXlsDataObjectIsHtml(XsltDataObject object){
      System.out.println("setXlsDataObjectIsHtml to true ");
      object.setHtml(true);
     }
     
     public static boolean getXlsDataObjectIsHtml(XsltDataObject object){
      System.out.println("getXlsDataObjectIsHtml  = "  object.isHtml());
      return object.isHtml();
     }
     
     
     public static void setDataObjectBodyByInlineImageUrl(XsltDataObject inObject,
       String contentId, String fileType, String content,
       String fileName, String hashMd5) {
      
      
      //for debug
      System.out.println("contentId = " contentId);
      System.out.println("fileType = " fileType);
    
      System.out.println("fileName = " fileName);
      
      
      String contentIdStr = replaceContentId(contentId.trim());
      System.out.println("contentIdStr =" contentIdStr);
      StringBuffer resultString = new StringBuffer();
      String bodyString = inObject.getBodyString();
      
      resultString.append("data:");
      resultString.append(fileType);
      resultString.append(";base64,");
      
      
      //will be change to get file name rule.
      /*
      String fullFileName = filePath "/"   contentIdStr;
      System.out.println("fullFileName = " fullFileName);
      fullFileName =  fullFileName.replaceAll("\\\\", "/");
      
      File f = new File(fullFileName);
      BufferedReader reader = null;
      StringBuffer contents = new StringBuffer();
      
      if(f.exists()){
       try {
        reader = new BufferedReader(new FileReader(f));
        String text = null;
        while ((text = reader.readLine()) != null) {
         contents.append(text);
        }
        reader.close();
       } catch (FileNotFoundException e) {    
        e.printStackTrace();
       } catch (IOException e) {
        e.printStackTrace();
       }
      }
      */
    //  System.out.println("contents = "  contents.toString());
      resultString.append(content);
      
      bodyString = bodyString.replaceAll(contentIdStr, resultString.toString());
      
    //  System.out.println("Result bodyString = "   bodyString);  
      inObject.setBodyString(bodyString);
     }
     
     
     private static String replaceContentId(String contentId){
      String result = contentId;
      result = result.replaceAll("&lt;", "");
      result = result.replaceAll("&gt;", "");
      result = result.replaceAll("<", "");
      result = result.replaceAll(">", "");
      
      return result;
     }
     
     public static String processEscapingString(String inputString){
      System.out.println("processEscapingString = " inputString);
      String result = inputString;
      result = result.replaceAll("&lt;", "&lt; ");
      result = result.replaceAll("&gt;", " &gt;");
      result = result.replaceAll("<", "< ");
      result = result.replaceAll(">", " >");
      System.out.println("processEscapingString result= " result);
      return result;
      
     }
    
    }
    

    可以先別理會Java Class在做什麼事, 在原始的XSL 檔裡面可以注意下面這個
    <xsl:variable name="bodyObj" select="obj:new()"/> 
    
    這邊會設定一個variable 為bodyObj ,XSLT 的variable 一旦設定後就不可變更,但是我們可以對這個Java Object的值做處理。

    看看下面的param設定
    <xsl:param name="htmlBody">
      <xsl:for-each select="//Document">
       <xsl:choose>
        <xsl:when test="contains(@MimeType,'text/html')">
            <xsl:value-of select="cu:setXlstDataObject($bodyObj,FieldValues/content)" disable-output-escaping="yes"/>     
            <xsl:value-of select="cu:setXlsDataObjectIsHtml($bodyObj)"/>
        </xsl:when>
       </xsl:choose>
      </xsl:for-each>       
     </xsl:param>
    

    cu:setXlstDataObject , cu是我們指定的xmlns name = cu , 指向java class = com.mypackage.util.XsltCommonUtils, 所以當你在XSLT裡調用
    cu:setXlstDataObject($bodyObj,FieldValues/content)
    
    會呼叫XsltCommonUtils的setXlstDataObject method,
    public static void setXlstDataObject(XsltDataObject object,String bodyString){
      String bodyStr = bodyString;
      bodyStr = bodyStr.replaceAll("cid:", "");
      object.setBodyString(bodyStr);
    }
    
    注意在XSL 裡面$bodyObj會自動對應Java method裡面的setXlstDataObject為XsltDataObject, 然後在XsltCommonUtils就寫一些針對JavaBean做處理的動作把XSL裡面的$bodyObj再做操作處理。
    相同的處理在XSL 跟 Java之間的物件型態轉換可以自己參考文件。

    所以我們的XSL檔跟XML都有了之後就是要透過TransformerFactory轉換成HTML

    XmlToXLSTtest.java
    package com;
    
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    
    import javax.xml.transform.Transformer;
    import javax.xml.transform.TransformerConfigurationException;
    import javax.xml.transform.TransformerException;
    import javax.xml.transform.TransformerFactory;
    import javax.xml.transform.stream.StreamResult;
    import javax.xml.transform.stream.StreamSource;
    
    public class XmlToXLSTtest {
     
     /**       
      * @param args 
      */
     public static void main(String[] args) {
      TransformerFactory tFactory = TransformerFactory.newInstance();
      Transformer transformer; 
      try {  
       transformer = tFactory.newTransformer(new javax.xml.transform.stream.StreamSource("test.xsl"));
       
    //   StreamSource xslt = new StreamSource(dxlReader);
    //   StreamResult result = new StreamResult(outWriter);
       
       transformer.transform(new javax.xml.transform.stream.StreamSource("EDRM_Sample-2.xml"), 
                       new javax.xml.transform.stream.StreamResult(new FileOutputStream("EDRM.html")));
       
       System.out.println("transformer finished.");
      } catch (TransformerConfigurationException e) {
       e.printStackTrace();
      } catch (FileNotFoundException e) {
       e.printStackTrace();
      } catch (TransformerException e) {
       e.printStackTrace();
      }
     }
    
    }
    
    


    Reference:
    Java and XSLT
    XSLT Wiki
    Processing XML with Java
    XSL Transformations (XSLT) Version 2.0 (W3c)

    XPATH 語法
    XSLT 基礎
    简单的 Xalan 扩展函数

    2011年6月3日 星期五

    Apache Betwixt - JavaBean to XML, XML to JavaBean

    最近用到把JavaBean 轉成 XML , 再把XML轉成JavaBean,用到的的Apache commons下面的一個項目,Commons Betwixt ,betwixt 提供了一個機制,可以把XML 跟JavaBean做Mapping, 主要是透過XMLIntrospector和XMLBeanInfo來完成,如果有一個良好的schema時,JAXP和Castor可以自動生成beans來解析和處理XML。
    而Betwixt,只需要編寫beans,而不需要關注XML schemas,並且提供了比較簡單的命名規範(使用getter,setter,adder方法)。
    在網路上有看到一編文章在比較Betwixt & Xstream 的不同處,Betwixt 在解析XML時是使用SAX, 而Xstream是使用DOM,Xstream使用上似乎比Betwixt來的方便一些,個人在使用Betwixt時碰到一些設定會讓Betwixt抓不到相對應的bean。另外一個是Jdk 6裡面帶的StAx,有興趣的可以看看, Introduction to StAX


    XML Parser API Feature Summary
    Feature
    StAX
    SAX
    DOM
    TrAX
    API Type
    Pull, streaming
    Push, streaming
    In memory tree
    XSLT Rule
    Ease of Use
    High
    Medium
    High
    Medium
    XPath Capability
    No
    No
    Yes
    Yes
    CPU and Memory Efficiency
    Good
    Good
    Varies
    Varies
    Forward Only
    Yes
    Yes
    No
    No
    Read XML
    Yes
    Yes
    Yes
    Yes
    Write XML
    Yes
    No
    Yes
    Yes
    Create, Read, Update, Delete
    No
    No
    Yes
    No
    這邊針對Betwixt 做簡單的使用介紹,
    首先要下載Betwixt, 可以在 這邊 找到

    首先要將Bean轉成XML,先看看最後轉出來的XML長什麼樣子
    <?xml version='1.0' encoding='UTF-8' ?>
    <MailRootBean>
      <versionCheckObject>
        <version>1.0</version>
      </versionCheckObject>
      <mailInfoObject>
        <attachList>
          <attachementObject>
            <attachementDataStream>cGFja2FnZSBjb20uZ2ZhY3Rvci5lbWFpbGRpc2NvdmVyeS51dGlsczsNCg0KaW1wb3J0IGphdmEudXRpbC5BcnJheXM7DQoNCmltcG9ydCBqYXZheC5hbm5vdGF0aW9uLlBvc3RDb25zdHJ1Y3Q7DQoNCmltcG9ydCBvcmcuYXBhY2hlLmNvbW1vbnMubG9nZ2luZy5Mb2c7DQppbXBvcnQgb3JnLmFzcGVjdGoubGFuZy5Qcm9jZWVkaW5nSm9pblBvaW50Ow0KaW1wb3J0IG9yZy5hc3BlY3RqLmxhbmcuYW5ub3RhdGlvbi5Bcm91bmQ7DQppbXBvcnQgb3JnLmFzcGVjdGoubGFuZy5hbm5vdGF0aW9uLkFzcGVjdDsNCmltcG9ydCBvcmcuYXNwZWN0ai5sYW5nLmFubm90YXRpb24uUG9pbnRjdXQ7DQppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay51dGlsLkFzc2VydDsNCmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnV0aWwuU3RvcFdhdGNoOw0KDQoNCkBBc3BlY3QNCnB1YmxpYyBjbGFzcyBQcm9jZXNzVGltZUxvZ2dlciB7DQoJcHJpdmF0ZSBTdHJpbmcgYmVmb3JlTWVzc2FnZTsNCglwcml2YXRlIFN0cmluZyBhZnRlck1lc3NhZ2U7DQoJcHJpdmF0ZSBzdGF0aWMgZmluYWwgTG9nIGxvZ2dlciA9IE15VXRpbHMuZ2V0TG9nKCk7IA0KCQ0KCUBQb2ludGN1dCgiZXhlY3V0aW9uKCogY29tLmdmYWN0b3IuZW1haWxkaXNjb3Zlcnkuc2VydmljZS4qLiooLi4pKSIpDQoJcHJpdmF0ZSB2b2lkIHRlc3RCZWFuRXhlY3V0aW9uKCkgeyB9DQogDQoJDQoJQFBvaW50Y3V0KCJleGVjdXRpb24oKiBjb20uZ2ZhY3Rvci5lbWFpbGRpc2NvdmVyeS5kYW8uaW1wbC4qLiooLi4pKSIpDQoJcHJpdmF0ZSB2b2lkIGRhb0V4ZWN1dGlvbigpIHsgfQ0KCQ0KCUBBcm91bmQoImRhb0V4ZWN1dGlvbigpIikNCglwdWJsaWMgT2JqZWN0IGRvUHJvY2VzcyhQcm9jZWVkaW5nSm9pblBvaW50IHBqcCkgdGhyb3dzIFRocm93YWJsZSB7DQoJCVN0cmluZyBjbGFzc05hbWUgPSBwanAuZ2V0VGFyZ2V0KCkuZ2V0Q2xhc3MoKS5nZXROYW1lKCk7DQoJCVN0cmluZyBtZXRob2QgPSBwanAuZ2V0U2lnbmF0dXJlKCkuZ2V0TmFtZSgpOw0KCQlsb2dnZXIuaW5mbyhTdHJpbmcuZm9ybWF0KHRoaXMuYmVmb3JlTWVzc2FnZSxjbGFzc05hbWUsIG1ldGhvZCxBcnJheXMudG9TdHJpbmcocGpwLmdldEFyZ3MoKSkpKTsNCgkJDQoJCVN0b3BXYXRjaCB3YXRjaCA9IG5ldyBTdG9wV2F0Y2goKTsNCgkJd2F0Y2guc3RhcnQoKTsNCgkJT2JqZWN0IHJlc3VsdCA9IHBqcC5wcm9jZWVkKCk7DQoJCXdhdGNoLnN0b3AoKTsNCgkJDQoJCWxvZ2dlci5pbmZvKFN0cmluZy5mb3JtYXQodGhpcy5hZnRlck1lc3NhZ2UsbWV0aG9kLGNsYXNzTmFtZSwgQXJyYXlzLnRvU3RyaW5nKHBqcC5nZXRBcmdzKCkpKSk7DQoJCVN0cmluZyBsb2cgPSBjbGFzc05hbWUgKyAiIFJ1biAiICsgbWV0aG9kICsgIm1ldGhvZCBjb21wbGV0ZSBpbiAiKyB3YXRjaC5nZXRUb3RhbFRpbWVNaWxsaXMoKSArICIgbXMiOw0KCQlsb2dnZXIuaW5mbyhsb2cpOw0KCQlyZXR1cm4gcmVzdWx0Ow0KLy8JCS8vIERvIHdoYXQgeW91IHdhbnQgd2l0aCB0aGUgam9pbiBwb2ludCBhcmd1bWVudHMNCi8vCQlmb3IgKCBPYmplY3Qgb2JqZWN0IDogam9pblBvaW50LmdldEFyZ3MoKSkgew0KLy8JCQkNCi8vCQkJbG9nZ2VyLmluZm8oIioqKioqKiBBT1Agb2JqZWN0ID0iICsgb2JqZWN0KTsNCi8vCQl9DQovLwkJcmV0dXJuIGpvaW5Qb2ludDsNCgkJDQoJfQ0KCQ0KCUBQb3N0Q29uc3RydWN0DQoJcHVibGljIHZvaWQgaW5pdGlhbGl6ZSgpIHsNCgkgICAgQXNzZXJ0Lm5vdE51bGwodGhpcy5iZWZvcmVNZXNzYWdlLA0KCSAgICAgICAgIlRoZSBbYmVmb3JlTWVzc2FnZV0gcHJvcGVydHkgb2YgWyIgKyBnZXRDbGFzcygpLmdldE5hbWUoKSArDQoJICAgICAgICAiXSBtdXN0IGJlIHNldC4iKTsNCgkgICAgQXNzZXJ0Lm5vdE51bGwodGhpcy5hZnRlck1lc3NhZ2UsDQoJICAgICAgICAiVGhlIFthZnRlck1lc3NhZ2VdIHByb3BlcnR5IG9mIFsiICsgZ2V0Q2xhc3MoKS5nZXROYW1lKCkgKw0KCSAgICAgICAgIl0gbXVzdCBiZSBzZXQuIik7DQoJfQ0KCXB1YmxpYyB2b2lkIHNldEJlZm9yZU1lc3NhZ2UoU3RyaW5nIGJlZm9yZU1lc3NhZ2UpIHsNCgkgICAgdGhpcy5iZWZvcmVNZXNzYWdlID0gYmVmb3JlTWVzc2FnZTsNCgl9DQoJcHVibGljIHZvaWQgc2V0QWZ0ZXJNZXNzYWdlKFN0cmluZyBhZnRlck1lc3NhZ2UpIHsNCgkgICAgdGhpcy5hZnRlck1lc3NhZ2UgPSBhZnRlck1lc3NhZ2U7DQoJfQ0KfQ0K</attachementDataStream>
            <attachmentContentType>text/plain;
     name="ProcessTimeLogger.java"</attachmentContentType>
            <attachmentFileName>ProcessTimeLogger.java</attachmentFileName>
          </attachementObject>
          <attachementObject>
            <attachementDataStream>cGFja2FnZSBjb20uZ2ZhY3Rvci5lbWFpbGRpc2NvdmVyeS51dGlsczsNCg0KaW1wb3J0IHN0YXRpYyBjb20uZ2ZhY3Rvci5lbWFpbGRpc2NvdmVyeS51dGlscy5UcmFuc2FjdGlvbkluZGljYXRpbmdVdGlsLmdldFRyYW5zYWN0aW9uU3RhdHVzOw0KDQppbXBvcnQgb3JnLmFwYWNoZS5sb2c0ai5zcGkuRmlsdGVyOw0KaW1wb3J0IG9yZy5hcGFjaGUubG9nNGouc3BpLkxvZ2dpbmdFdmVudDsNCg0KcHVibGljIGNsYXNzIFRyYW5zYWN0aW9uSW5kaWNhdGluZ0ZpbHRlciBleHRlbmRzIEZpbHRlciB7DQogICAgQE92ZXJyaWRlDQogICAgcHVibGljIGludCBkZWNpZGUoTG9nZ2luZ0V2ZW50IGxvZ2dpbmdFdmVudCkgew0KICAgICAgICBsb2dnaW5nRXZlbnQuc2V0UHJvcGVydHkoInhhTmFtZSIsIGdldFRyYW5zYWN0aW9uU3RhdHVzKHRydWUpICk7DQogICAgICAgIGxvZ2dpbmdFdmVudC5zZXRQcm9wZXJ0eSgieGFTdGF0dXMiLCBnZXRUcmFuc2FjdGlvblN0YXR1cyhmYWxzZSkgKTsNCiAgICAgICAgcmV0dXJuIEZpbHRlci5ORVVUUkFMOw0KICAgIH0gIA0KfQ0K</attachementDataStream>
            <attachmentContentType>text/plain;
     name="TransactionIndicatingFilter.java"</attachmentContentType>
            <attachmentFileName>TransactionIndicatingFilter.java</attachmentFileName>
          </attachementObject>
          <attachementObject>
            <attachementDataStream>cGFja2FnZSBjb20uZ2ZhY3Rvci5lbWFpbGRpc2NvdmVyeS51dGlsczsNCg0KcHVibGljIGNsYXNzIFRyYW5zYWN0aW9uSW5kaWNhdGluZ1V0aWwgew0KCXByaXZhdGUgZmluYWwgc3RhdGljIFN0cmluZyBUU01fQ0xBU1NOQU1FID0gIm9yZy5zcHJpbmdmcmFtZXdvcmsudHJhbnNhY3Rpb24uc3VwcG9ydC5UcmFuc2FjdGlvblN5bmNocm9uaXphdGlvbk1hbmFnZXIiOw0KDQoJcHVibGljIHN0YXRpYyBTdHJpbmcgZ2V0VHJhbnNhY3Rpb25TdGF0dXMoYm9vbGVhbiB2ZXJib3NlKSB7DQoJCVN0cmluZyBzdGF0dXMgPSBudWxsOw0KDQoJCXRyeSB7DQoJCQlDbGFzc0xvYWRlciBjb250ZXh0Q2xhc3NMb2FkZXIgPSBUaHJlYWQuY3VycmVudFRocmVhZCgpLmdldENvbnRleHRDbGFzc0xvYWRlcigpOw0KCQkJDQoJCQlpZiAoY29udGV4dENsYXNzTG9hZGVyICE9IG51bGwpIHsNCgkJCQlDbGFzcyB0c21DbGFzcyA9IGNvbnRleHRDbGFzc0xvYWRlci5sb2FkQ2xhc3MoVFNNX0NMQVNTTkFNRSk7DQoJCQkJQm9vbGVhbiBpc0FjdGl2ZSA9IChCb29sZWFuKSB0c21DbGFzcy5nZXRNZXRob2QoImlzQWN0dWFsVHJhbnNhY3Rpb25BY3RpdmUiLCBudWxsKS5pbnZva2UobnVsbCwgbnVsbCk7DQoJCQkJDQoJCQkJaWYgKCF2ZXJib3NlKSB7DQoJCQkJCXN0YXR1cyA9IChpc0FjdGl2ZSkgPyAiWytdICIgOiAiWy1dICI7DQoJCQkJfSBlbHNlIHsNCgkJCQkJU3RyaW5nIHRyYW5zYWN0aW9uTmFtZSA9IChTdHJpbmcpIHRzbUNsYXNzLmdldE1ldGhvZCgiZ2V0Q3VycmVudFRyYW5zYWN0aW9uTmFtZSIsIG51bGwpLmludm9rZShudWxsLG51bGwpOw0KCQkJCQkNCgkJCQkJc3RhdHVzID0gKGlzQWN0aXZlKSA/ICJbIiArIHRyYW5zYWN0aW9uTmFtZSArICJdICIgOiAiW25vIHRyYW5zYWN0aW9uXSAiOw0KCQkJCX0NCgkJCX0gZWxzZSB7DQoJCQkJc3RhdHVzID0gKHZlcmJvc2UpID8gIltjY2wgdW5hdmFpbGFibGVdICIgOiAiW3ggXSI7DQoJCQl9DQoJCX0gY2F0Y2ggKEV4Y2VwdGlvbiBlKSB7DQoJCQlzdGF0dXMgPSAodmVyYm9zZSkgPyAiW3NwcmluZyB1bmF2YWlsYWJsZV0gIiA6ICJbeCBdIjsNCgkJfQ0KCQlyZXR1cm4gc3RhdHVzOw0KCX0NCn0NCg==</attachementDataStream>
            <attachmentContentType>text/plain;
     name="TransactionIndicatingUtil.java"</attachmentContentType>
            <attachmentFileName>TransactionIndicatingUtil.java</attachmentFileName>
          </attachementObject>
        </attachList>
        <bccRecipipent/>
        <body>testests</body>
        <bodyContentType>text/plain; charset=UTF-8; format=flowed</bodyContentType>
        <ccRecipipent/>
        <mailId>&lt;4DE4AC51.9060806@mydomain.james&gt;</mailId>
        <sender>admin@mydomain.james</sender>
        <subject>testes</subject>
        <toRecipipent>test@mydomain.james</toRecipipent>
      </mailInfoObject>
    </MailRootBean>
    

    這邊總供是分成3個Bean, Root的MailRootBean, 在root裡面包含2個區塊,versionCheckObject,mailInfoObject, 寫入的時候會將直接透過write寫入, 但是讀取的時候會以MailRootBean為mapping, code如下

    MailInfoObject.java
    package com.mypackage.MailXmlObject;
    
    import java.util.ArrayList;
    
    public class MailInfoObject {
     private String mailId; 
     private String sender;
     private String ToRecipipent;
     private String CcRecipipent;
     private String BccRecipipent;
     private String Subject;
     private String BodyContentType;
     private String body;
     private ArrayList<AttachementObject> attachList = new ArrayList<AttachementObject>();
     
     
     
     public String getSender() {
      return sender;
     }
     public void setSender(String sender) {
      this.sender = sender;
     }
     public String getToRecipipent() {
      return ToRecipipent;
     }
     public void setToRecipipent(String toRecipipent) {
      ToRecipipent = toRecipipent;
     }
     public String getCcRecipipent() {
      return CcRecipipent;
     }
     public void setCcRecipipent(String ccRecipipent) {
      CcRecipipent = ccRecipipent;
     }
     public String getBccRecipipent() {
      return BccRecipipent;
     }
     public void setBccRecipipent(String bccRecipipent) {
      BccRecipipent = bccRecipipent;
     }
     public String getSubject() {
      return Subject;
     }
     public void setSubject(String subject) {
      Subject = subject;
     }
     public String getBodyContentType() {
      return BodyContentType;
     }
     public void setBodyContentType(String bodyContentType) {
      BodyContentType = bodyContentType;
     }
     public String getBody() {
      return body;
     }
     public void setBody(String body) {
      this.body = body;
     }
     public ArrayList<AttachementObject> getAttachList() {
      return attachList;
     }
     public void setAttachList(ArrayList<AttachementObject> attachList) {
      this.attachList = attachList;
     }  
    
     public void addAttachementObject(AttachementObject attachementObject) {
      attachList.add(attachementObject);
     }
     public String getMailId() {
      return mailId;
     }
     public void setMailId(String mailId) {
      this.mailId = mailId;
     }
     
     @Override
     public String toString(){
      StringBuilder resultBuilder = new StringBuilder();  
      resultBuilder.append("mailId : "   this.getMailId()   "\n");
      resultBuilder.append("sender : "   this.getSender()   "\n");
      resultBuilder.append("ToRecipipent : "   this.getToRecipipent() "\n");
      resultBuilder.append("CcRecipipent : "   this.getCcRecipipent() "\n");
      resultBuilder.append("BccRecipipent : "   this.getBccRecipipent() "\n");
      resultBuilder.append("Subject : "   this.getSubject() "\n");
      resultBuilder.append("BodyContentType : "   this.getBodyContentType() "\n");
      resultBuilder.append("body : "   this.getBody() "\n");
      resultBuilder.append("attachList : "  attachList "\n");
      if(attachList != null){
       resultBuilder.append("attachList size ="   attachList.size() "\n");
       for (int i = 0; i < attachList.size(); i  ) {
        resultBuilder.append(attachList.get(i).toString() "\n");
       }
      }
      
      return resultBuilder.toString();
      
     }
    }
    
    

    AttachementObject.java
    package com.mypackage.MailXmlObject;
    
    public class AttachementObject {
     private String attachmentContentType;
     private String attachmentFileName;
     private String attachementDataStream;
     public String getAttachmentContentType() {
      return attachmentContentType;
     }
     public void setAttachmentContentType(String attachmentContentType) {
      this.attachmentContentType = attachmentContentType;
     }
     public String getAttachmentFileName() {
      return attachmentFileName;
     }
     public void setAttachmentFileName(String attachmentFileName) {
      this.attachmentFileName = attachmentFileName;
     }
     public String getAttachementDataStream() {
      return attachementDataStream;
     }
     public void setAttachementDataStream(String attachementDataStream) {
      this.attachementDataStream = attachementDataStream;
     }
     
     @Override
     public String toString(){
      StringBuilder resultBuilder = new StringBuilder();
      resultBuilder.append("attachmentFileName : "  this.getAttachmentFileName() "\n");
      resultBuilder.append("attachmentContentType : "  this.getAttachmentContentType() "\n");
      resultBuilder.append("attachementDataStream : "  this.getAttachementDataStream() "\n");
      return resultBuilder.toString();
     }
    }
    

    versionChkObj.java
    package com.mypackage.MailXmlObject;
    
    public class VersionCheckObject {
     private String version;
    
     public String getVersion() {
      return version;
     }
    
     public void setVersion(String version) {
      this.version = version;
     }
     
     
    }
    

    BetwixtXmlBuilder.java
    /**
     * 
     */
    package com.mypackage.xmlbuilder;
    
    
    import java.beans.IntrospectionException;
    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.StringReader;
    import java.io.StringWriter;
    
    import org.apache.commons.betwixt.io.BeanReader;
    import org.apache.commons.betwixt.io.BeanWriter;
    import org.dom4j.Document;
    import org.dom4j.DocumentException;
    import org.dom4j.io.SAXReader;
    import org.xml.sax.SAXException;
    
    import com.mypackage.MailXmlObject.MailInfoObject;
    import com.mypackage.MailXmlObject.MailRootBean;
    import com.mypackage.MailXmlObject.VersionCheckObject;
    
    /**
     * @author momo
     *
     */
    public class BetwixtXmlBuilder extends AbstractXmlBuilder {
     MailInfoObject mailobject;
     VersionCheckObject versionChkObj;
     
     public BetwixtXmlBuilder(){
      
     }
     public BetwixtXmlBuilder(MailInfoObject mailobj,VersionCheckObject verchkobj){
      this.mailobject = mailobj;
      this.versionChkObj = verchkobj;
     }
     
     /* (non-Javadoc)
      * @see com.mypackage.xmlbuilder.AbstractXmlBuilder#builderXml()
      */
     @Override
     public void builderXml() {
      try{
       String fileName = this.defaultFilePath this.mailobject.getMailId() ".xml";
       fileName = fileName.replaceAll("<", "");
       fileName = fileName.replaceAll(">", "");
       
       StringWriter outputWriter = new StringWriter();
       outputWriter.write("<?xml version='1.0' encoding='UTF-8' ?>\n");
       outputWriter.write("<MailRootBean>\n");
       BeanWriter beanWriter = new BeanWriter(outputWriter);
       beanWriter.getXMLIntrospector().getConfiguration().setAttributesForPrimitives(false);
       beanWriter.getBindingConfiguration().setMapIDs(false);
       beanWriter.enablePrettyPrint( );
       beanWriter.write("versionCheckObject",this.versionChkObj );
       beanWriter.write("mailInfoObject",this.mailobject );
       //write to files
       outputWriter.write("</MailRootBean>\n");
       BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
       out.write(outputWriter.toString());
       out.close();
       
       beanWriter.close();
             outputWriter.close();
      }catch(Exception ex){
       
      }
      
     }
    
     
     
    
    }
    
    
    這邊會先建立一個StringWriter, 先寫入XML version tag, 跟root tag, 然後再透過BeanWriter的constructor 傳入一個StringWriter 物件, 然後設定setAttributesForPrimitives 為false , setAttributesForPrimitives設定為false 的差異如下,
    setAttributesForPrimitives = true
    <root>
        <age>21</age>
    </root>
    
    setAttributesForPrimitives = false
    <root age="21"/>
    

    setMapIDs 設成false , 代表不自動生成ID,
    然後再透過beanwrite將2個bean寫入,
    beanWriter.write("versionCheckObject",this.versionChkObj );
    beanWriter.write("mailInfoObject",this.mailobject );
    
    這會將versionChkObj bean 寫入到xml , tag 為versionCheckObject, 另一個為mailInfoObject,
    這樣就能簡單的把Bean轉入成 XML ,


    讀取XML並且轉回成Bean物件, 如果有看到上面的MailInfoObject.java 的話可以注意到有一個method
    public void addAttachementObject(AttachementObject attachementObject) {
      attachList.add(attachementObject);
     }
    
    這個method如果你是用eclipse 生成bean的getter&setter的話,要手動加上這段, 主要是因為在mailInfoObject 的XML tag裡面包含了Attachement file , 而Attachement 的格式如下
    <attachList>
          <attachementObject>
            <attachementDataStream>cGFja2FnZSBjb20uZ2ZhY3Rvci5lbWFpbGRpc2NvdmVyeS51dGlsczsNCg0KaW1wb3J0IGphdmEudXRpbC5BcnJheXM7DQoNCmltcG9ydCBqYXZheC5hbm5vdGF0aW9uLlBvc3RDb25zdHJ1Y3Q7DQoNCmltcG9ydCBvcmcuYXBhY2hlLmNvbW1vbnMubG9nZ2luZy5Mb2c7DQppbXBvcnQgb3JnLmFzcGVjdGoubGFuZy5Qcm9jZWVkaW5nSm9pblBvaW50Ow0KaW1wb3J0IG9yZy5hc3BlY3RqLmxhbmcuYW5ub3RhdGlvbi5Bcm91bmQ7DQppbXBvcnQgb3JnLmFzcGVjdGoubGFuZy5hbm5vdGF0aW9uLkFzcGVjdDsNCmltcG9ydCBvcmcuYXNwZWN0ai5sYW5nLmFubm90YXRpb24uUG9pbnRjdXQ7DQppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay51dGlsLkFzc2VydDsNCmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnV0aWwuU3RvcFdhdGNoOw0KDQoNCkBBc3BlY3QNCnB1YmxpYyBjbGFzcyBQcm9jZXNzVGltZUxvZ2dlciB7DQoJcHJpdmF0ZSBTdHJpbmcgYmVmb3JlTWVzc2FnZTsNCglwcml2YXRlIFN0cmluZyBhZnRlck1lc3NhZ2U7DQoJcHJpdmF0ZSBzdGF0aWMgZmluYWwgTG9nIGxvZ2dlciA9IE15VXRpbHMuZ2V0TG9nKCk7IA0KCQ0KCUBQb2ludGN1dCgiZXhlY3V0aW9uKCogY29tLmdmYWN0b3IuZW1haWxkaXNjb3Zlcnkuc2VydmljZS4qLiooLi4pKSIpDQoJcHJpdmF0ZSB2b2lkIHRlc3RCZWFuRXhlY3V0aW9uKCkgeyB9DQogDQoJDQoJQFBvaW50Y3V0KCJleGVjdXRpb24oKiBjb20uZ2ZhY3Rvci5lbWFpbGRpc2NvdmVyeS5kYW8uaW1wbC4qLiooLi4pKSIpDQoJcHJpdmF0ZSB2b2lkIGRhb0V4ZWN1dGlvbigpIHsgfQ0KCQ0KCUBBcm91bmQoImRhb0V4ZWN1dGlvbigpIikNCglwdWJsaWMgT2JqZWN0IGRvUHJvY2VzcyhQcm9jZWVkaW5nSm9pblBvaW50IHBqcCkgdGhyb3dzIFRocm93YWJsZSB7DQoJCVN0cmluZyBjbGFzc05hbWUgPSBwanAuZ2V0VGFyZ2V0KCkuZ2V0Q2xhc3MoKS5nZXROYW1lKCk7DQoJCVN0cmluZyBtZXRob2QgPSBwanAuZ2V0U2lnbmF0dXJlKCkuZ2V0TmFtZSgpOw0KCQlsb2dnZXIuaW5mbyhTdHJpbmcuZm9ybWF0KHRoaXMuYmVmb3JlTWVzc2FnZSxjbGFzc05hbWUsIG1ldGhvZCxBcnJheXMudG9TdHJpbmcocGpwLmdldEFyZ3MoKSkpKTsNCgkJDQoJCVN0b3BXYXRjaCB3YXRjaCA9IG5ldyBTdG9wV2F0Y2goKTsNCgkJd2F0Y2guc3RhcnQoKTsNCgkJT2JqZWN0IHJlc3VsdCA9IHBqcC5wcm9jZWVkKCk7DQoJCXdhdGNoLnN0b3AoKTsNCgkJDQoJCWxvZ2dlci5pbmZvKFN0cmluZy5mb3JtYXQodGhpcy5hZnRlck1lc3NhZ2UsbWV0aG9kLGNsYXNzTmFtZSwgQXJyYXlzLnRvU3RyaW5nKHBqcC5nZXRBcmdzKCkpKSk7DQoJCVN0cmluZyBsb2cgPSBjbGFzc05hbWUgKyAiIFJ1biAiICsgbWV0aG9kICsgIm1ldGhvZCBjb21wbGV0ZSBpbiAiKyB3YXRjaC5nZXRUb3RhbFRpbWVNaWxsaXMoKSArICIgbXMiOw0KCQlsb2dnZXIuaW5mbyhsb2cpOw0KCQlyZXR1cm4gcmVzdWx0Ow0KLy8JCS8vIERvIHdoYXQgeW91IHdhbnQgd2l0aCB0aGUgam9pbiBwb2ludCBhcmd1bWVudHMNCi8vCQlmb3IgKCBPYmplY3Qgb2JqZWN0IDogam9pblBvaW50LmdldEFyZ3MoKSkgew0KLy8JCQkNCi8vCQkJbG9nZ2VyLmluZm8oIioqKioqKiBBT1Agb2JqZWN0ID0iICsgb2JqZWN0KTsNCi8vCQl9DQovLwkJcmV0dXJuIGpvaW5Qb2ludDsNCgkJDQoJfQ0KCQ0KCUBQb3N0Q29uc3RydWN0DQoJcHVibGljIHZvaWQgaW5pdGlhbGl6ZSgpIHsNCgkgICAgQXNzZXJ0Lm5vdE51bGwodGhpcy5iZWZvcmVNZXNzYWdlLA0KCSAgICAgICAgIlRoZSBbYmVmb3JlTWVzc2FnZV0gcHJvcGVydHkgb2YgWyIgKyBnZXRDbGFzcygpLmdldE5hbWUoKSArDQoJICAgICAgICAiXSBtdXN0IGJlIHNldC4iKTsNCgkgICAgQXNzZXJ0Lm5vdE51bGwodGhpcy5hZnRlck1lc3NhZ2UsDQoJICAgICAgICAiVGhlIFthZnRlck1lc3NhZ2VdIHByb3BlcnR5IG9mIFsiICsgZ2V0Q2xhc3MoKS5nZXROYW1lKCkgKw0KCSAgICAgICAgIl0gbXVzdCBiZSBzZXQuIik7DQoJfQ0KCXB1YmxpYyB2b2lkIHNldEJlZm9yZU1lc3NhZ2UoU3RyaW5nIGJlZm9yZU1lc3NhZ2UpIHsNCgkgICAgdGhpcy5iZWZvcmVNZXNzYWdlID0gYmVmb3JlTWVzc2FnZTsNCgl9DQoJcHVibGljIHZvaWQgc2V0QWZ0ZXJNZXNzYWdlKFN0cmluZyBhZnRlck1lc3NhZ2UpIHsNCgkgICAgdGhpcy5hZnRlck1lc3NhZ2UgPSBhZnRlck1lc3NhZ2U7DQoJfQ0KfQ0K</attachementDataStream>
            <attachmentContentType>text/plain;
     name="ProcessTimeLogger.java"</attachmentContentType>
            <attachmentFileName>ProcessTimeLogger.java</attachmentFileName>
          </attachementObject>
          <attachementObject>
            <attachementDataStream>cGFja2FnZSBjb20uZ2ZhY3Rvci5lbWFpbGRpc2NvdmVyeS51dGlsczsNCg0KaW1wb3J0IHN0YXRpYyBjb20uZ2ZhY3Rvci5lbWFpbGRpc2NvdmVyeS51dGlscy5UcmFuc2FjdGlvbkluZGljYXRpbmdVdGlsLmdldFRyYW5zYWN0aW9uU3RhdHVzOw0KDQppbXBvcnQgb3JnLmFwYWNoZS5sb2c0ai5zcGkuRmlsdGVyOw0KaW1wb3J0IG9yZy5hcGFjaGUubG9nNGouc3BpLkxvZ2dpbmdFdmVudDsNCg0KcHVibGljIGNsYXNzIFRyYW5zYWN0aW9uSW5kaWNhdGluZ0ZpbHRlciBleHRlbmRzIEZpbHRlciB7DQogICAgQE92ZXJyaWRlDQogICAgcHVibGljIGludCBkZWNpZGUoTG9nZ2luZ0V2ZW50IGxvZ2dpbmdFdmVudCkgew0KICAgICAgICBsb2dnaW5nRXZlbnQuc2V0UHJvcGVydHkoInhhTmFtZSIsIGdldFRyYW5zYWN0aW9uU3RhdHVzKHRydWUpICk7DQogICAgICAgIGxvZ2dpbmdFdmVudC5zZXRQcm9wZXJ0eSgieGFTdGF0dXMiLCBnZXRUcmFuc2FjdGlvblN0YXR1cyhmYWxzZSkgKTsNCiAgICAgICAgcmV0dXJuIEZpbHRlci5ORVVUUkFMOw0KICAgIH0gIA0KfQ0K</attachementDataStream>
            <attachmentContentType>text/plain;
     name="TransactionIndicatingFilter.java"</attachmentContentType>
            <attachmentFileName>TransactionIndicatingFilter.java</attachmentFileName>
          </attachementObject>
          <attachementObject>
            <attachementDataStream>cGFja2FnZSBjb20uZ2ZhY3Rvci5lbWFpbGRpc2NvdmVyeS51dGlsczsNCg0KcHVibGljIGNsYXNzIFRyYW5zYWN0aW9uSW5kaWNhdGluZ1V0aWwgew0KCXByaXZhdGUgZmluYWwgc3RhdGljIFN0cmluZyBUU01fQ0xBU1NOQU1FID0gIm9yZy5zcHJpbmdmcmFtZXdvcmsudHJhbnNhY3Rpb24uc3VwcG9ydC5UcmFuc2FjdGlvblN5bmNocm9uaXphdGlvbk1hbmFnZXIiOw0KDQoJcHVibGljIHN0YXRpYyBTdHJpbmcgZ2V0VHJhbnNhY3Rpb25TdGF0dXMoYm9vbGVhbiB2ZXJib3NlKSB7DQoJCVN0cmluZyBzdGF0dXMgPSBudWxsOw0KDQoJCXRyeSB7DQoJCQlDbGFzc0xvYWRlciBjb250ZXh0Q2xhc3NMb2FkZXIgPSBUaHJlYWQuY3VycmVudFRocmVhZCgpLmdldENvbnRleHRDbGFzc0xvYWRlcigpOw0KCQkJDQoJCQlpZiAoY29udGV4dENsYXNzTG9hZGVyICE9IG51bGwpIHsNCgkJCQlDbGFzcyB0c21DbGFzcyA9IGNvbnRleHRDbGFzc0xvYWRlci5sb2FkQ2xhc3MoVFNNX0NMQVNTTkFNRSk7DQoJCQkJQm9vbGVhbiBpc0FjdGl2ZSA9IChCb29sZWFuKSB0c21DbGFzcy5nZXRNZXRob2QoImlzQWN0dWFsVHJhbnNhY3Rpb25BY3RpdmUiLCBudWxsKS5pbnZva2UobnVsbCwgbnVsbCk7DQoJCQkJDQoJCQkJaWYgKCF2ZXJib3NlKSB7DQoJCQkJCXN0YXR1cyA9IChpc0FjdGl2ZSkgPyAiWytdICIgOiAiWy1dICI7DQoJCQkJfSBlbHNlIHsNCgkJCQkJU3RyaW5nIHRyYW5zYWN0aW9uTmFtZSA9IChTdHJpbmcpIHRzbUNsYXNzLmdldE1ldGhvZCgiZ2V0Q3VycmVudFRyYW5zYWN0aW9uTmFtZSIsIG51bGwpLmludm9rZShudWxsLG51bGwpOw0KCQkJCQkNCgkJCQkJc3RhdHVzID0gKGlzQWN0aXZlKSA/ICJbIiArIHRyYW5zYWN0aW9uTmFtZSArICJdICIgOiAiW25vIHRyYW5zYWN0aW9uXSAiOw0KCQkJCX0NCgkJCX0gZWxzZSB7DQoJCQkJc3RhdHVzID0gKHZlcmJvc2UpID8gIltjY2wgdW5hdmFpbGFibGVdICIgOiAiW3ggXSI7DQoJCQl9DQoJCX0gY2F0Y2ggKEV4Y2VwdGlvbiBlKSB7DQoJCQlzdGF0dXMgPSAodmVyYm9zZSkgPyAiW3NwcmluZyB1bmF2YWlsYWJsZV0gIiA6ICJbeCBdIjsNCgkJfQ0KCQlyZXR1cm4gc3RhdHVzOw0KCX0NCn0NCg==</attachementDataStream>
            <attachmentContentType>text/plain;
     name="TransactionIndicatingUtil.java"</attachmentContentType>
            <attachmentFileName>TransactionIndicatingUtil.java</attachmentFileName>
          </attachementObject>
        </attachList>
    
    這樣代表我們的mailInfoObject bean裡面包含的不單純是一個屬性,而是一個Collection類別,這邊是ArrayList , 所以首先要在Bean裡面加上一段。

    讀取XML to Bean的code

    File f = new File("C:/james-server-container-spring-3.0-M2/Mail-XML/4DE4AC51.9060806@mydomain.james.xml");
    BeanReader reader = new BeanReader();  reader.getXMLIntrospector().getConfiguration().setAttributesForPrimitives(false);
    reader.getBindingConfiguration().setMapIDs(false);      
    reader.registerBeanClass(&quot;MailRootBean&quot;, MailRootBean.class);
    MailRootBean mailrootBean = (MailRootBean)reader.parse(f);//
    System.out.println("mailrootBean = " mailrootBean);
    System.out.println("mailrootbean.getVersionCheckObject = " mailrootBean.getVersionCheckObject().getVersion());
    MailInfoObject obj = mailrootBean.getMailInfoObject();
    System.out.println(obj);
    

    沒問題的話這個在輸出的時候只有versionCheckObject會有值, 而mailInfoObject無法取到相關的值,因為他為null, 這主要是因為還缺少了一個.betwixt 的檔, 檔名跟bean name一樣, 如下
    <?xml version='1.0'?>
    <info>
     <element name='mailInfoObject'>
      <element name='attachList'>
                <element name='attachementObject' property='attachList' updater='addAttachementObject'/>
            </element>
            <addDefaults/>
     </element>
    </info>
    

    這邊的info tag為root, 代表這個bean開始的root block , 然後裡面的element name 為mailInfoObject, 在element name為mailInfoObject的下面有一個element 叫attachList, 而這個attachList裡面的屬性是attachementObject , property 為attachList(getAttachList), update則是要對這個list裡面的物件透過addAttachementObject來增加。

    如無意外,bean將可以從xml讀取出來


    Reference:
    Betwixt Getting Started
    Betwixt and List/ArrayList