顯示具有 Spring 標籤的文章。 顯示所有文章
顯示具有 Spring 標籤的文章。 顯示所有文章

26 7月 2012

Query By Example In Spring-Data-JPA

Query By Example (QBE) 是個常用的查詢模式,Hibernate有Example Query來實踐,但JPA沒有,這對我常用的開發模式而言是個不小的困擾,所以基本上我都把JPA放在一邊,直接用Hibernate。
其實QBE的概念並不難實現,只要分析傳入的Domain Object,哪些property有值,加入查詢條件即可, 趁著覆習Spring Data的同時,就做個簡單的實作好了。

先建個ExpressionParam來儲存分析Domain Object Class後的結果,readMethod就是用來取值,而attribue則是用來取得Criteria的Path

public class ExpressionParam<T> {
	private String name;
	private Method readMethod;
	private SingularAttribute<T, ?> attribute;
	
	public ExpressionParam(String name, Method readMethod, SingularAttribute<T, ?> attribute) {
		super();
		this.name = name;
		this.readMethod = readMethod;
		this.attribute = attribute;
	}
	.....
}

再來就是配合Spring-Data的Specification Query,實作一個ExampleSpecification,其中作個簡單的Cache機制,以免同樣的Domain Object Class要一直重覆分析有哪些ReadMethod。

public class ExampleSpecification<T> implements Specification<T> {
	private static final Logger logger = LoggerFactory.getLogger(ExampleSpecification.class);
	protected static final Map<Class<?>, List<ExpressionParam<?>>> classCache = Collections.synchronizedMap(new WeakHashMap<Class<?>, List<ExpressionParam<?>>>());
	
	EntityManager entityManager;
	T example;
	
	public ExampleSpecification(final EntityManager entityManager, final T example) {
		this.entityManager = entityManager;
		this.example = example;
	}
	
	@Override
	public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
			CriteriaBuilder cb) {
		List<Predicate> predicates = new ArrayList<Predicate>();
		
		EntityType<T> entity = entityManager.getMetamodel().entity((Class<T>)example.getClass());
		List<ExpressionParam<?>> params = parseReadMethod(entity);
		for (ExpressionParam<?> param : params) {
			try {
				Object value = param.getReadMethod().invoke(example);
				if (null != value && StringUtils.isNotEmpty(value.toString())) {
					predicates.add(cb.equal(root.get((SingularAttribute<T, ?>)param.getAttribute()), value));
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return predicates.isEmpty()?cb.conjunction() : cb.and(predicates.toArray(new Predicate[predicates.size()]));
	}

	protected List<ExpressionParam<?>> parseReadMethod(EntityType<T> entityType) {
		Class<T> clazz = (Class<T>) entityType.getClass();
		if (classCache.containsKey(clazz)) {
			return classCache.get(clazz);
		}
		logger.info("First Parsing Read Method for Class[{}]", clazz);
		
		List<ExpressionParam<?>> methods = new ArrayList<ExpressionParam<?>>();
		classCache.put(clazz, methods);
		PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(example.getClass());
		
		Set<SingularAttribute<T, ?>> atts = entityType.getDeclaredSingularAttributes();
		for (SingularAttribute<T,?> sat : atts) {
			if (PersistentAttributeType.MANY_TO_ONE == sat.getPersistentAttributeType()
					|| PersistentAttributeType.ONE_TO_ONE == sat.getPersistentAttributeType()) {
				continue;
			}
			String name = sat.getName();
			Method readMethod = null;
			for (PropertyDescriptor pd : pds) {
				if (pd.getName().equals(name)) {
					readMethod = pd.getReadMethod();
					break;
				}
			}
			
			logger.debug("Property {} - Method {}", name, readMethod);
			if (null != readMethod) {
				methods.add(new ExpressionParam<T>(name, readMethod, sat));
			}
		}
		
		return methods;
	}
}

由於Spring Data的Repository產生機制不容易修改(其實是我還沒找到好的切入點....)所以只好在Service Layer來達到QBE的作用,可以參考下面UserService的做法。

@Service
@Transactional(readOnly=true)
public class UserService {
	@PersistenceContext
	private EntityManager entityManager;
	
	@Autowired
	private UserJpaDao userDao;
	
	public List findByExample(User example) {
		ExampleSpecification es = new ExampleSpecification(entityManager, example);
		return userDao.findAll(es);
	}
}

再來看一下Test的實際應用

public class UserJpaDaoTest {
	@Autowired
	private UserService userService;
	@Test
	public void test() {
		User example = new User();
		example.setName("Bob");
		
		this.userService.findByExample(example);
	}

}

順便提一下,不才小弟又要找新工作了,若有覺得適合小弟的可以聯絡交流一下。

21 3月 2011

Spring Security (3) Basic Configuration fo <authentication-provider>

<authentication-manager>共本上提供了兩種Authentication Provider,一個是authentication-provider,另一個是ldap-authentication-provider

預設的authentication-provide就是採用DaoAuthenticationProvider,有三種基本的帳號密碼檢核與提供人員資料方式

  • user-service:建立一個in-memory的UserDetailService,帳密可以自properties file載入或在xml以<user>建立
  • jdbc-user-service:Spring Security提供了SQL Schema,所以相關的帳密可以自DB載入,只要給jdbc-user-service一個DataSource就可以使用,或是現有欄位定稱與Spring Security不同,那也可以透過提供users-by-username-query、authorities-by-username-query等SQL來取得資料。
  • ldap-user-service:再明顯不過了吧,透過ldap取得帳密資料,在預設的情形下是沒意義的....

通常DB裡存的密碼不會是明碼(如果真的是明碼....),所以Spring Security也提供了基本的編碼方式,像sha、md5都有支援,我們可以透過<password-encoder hash="type">來設定這個authentication provider要用哪種編碼;如果我們的編碼方式不在Spring Security的支援範圍之內,也可以自行提供一個PasswordEncoder的實作。

如果可以完全接受SpringSecurity提供的Schema作為系統的帳密設定,那當然是很不錯的一件事,但我想這種事不太容易發生...所以修改的方式通常有二,一是自已提供UserDetailsService的實作將user service換掉,二是用自已的Authentication Provider實作,直接把authentication provider換掉囉。不過換Authentication Provider的原因通常不是因為帳密的DB Schema不同,通常是因為檢核的方式不同而更換,看看Spring Security提供的其他Authentication Provider名稱就知道,像JaasAuthenticationProvider、OpenIDAuthenticationProvider、CasAuthenticaionProvider等就知道需要更換Authentication Provider大約是在什麼情形才需要更動。

 

 

20 3月 2011

Spring Security (2) Basic Configuration of <http>

看過先前的說明應該會有些困惑,我們應該先稍為拆解一下<http>
<http auto-config='true'> 代表了三個預設定設定

  • <form-login>
  • <http-basic>
  • <logout>

<form-login>代表要使用基本的Form-based authentication,但由於沒有指定登入的頁面,所以SpringSecurity會直接採用內建的Servlet產生登入的頁面,如果只是展示一下系統,當然沒問題,但真實在使用的系統應該沒辦法接受,所以直接加上<form-login login-page="/your_login.jsp" />這樣的設定就可以改用目前系統使用的登入頁面,但由於其他設定都沒動,所以form的欄位及action url仍有一定的要求,action仍必需是“/j_spring_security_check“,account的欄位名稱必需是“j_username“,password欄位名稱必需是“j_password“。
form-login還可以設定其他的參數,像login-processing-url代表action要送出的url,default-target-url代表登入成功後要轉的url,authentication-failure-url代表登入失敗後要轉入的url,always-use-default-target設定為true則代表登入成功或失敗都要轉入default-target-url。
比較有趣的是authentication-success-handler-ref與 authentication-failure-handler-ref,這兩個參數不應該與default-target-url、authentication-failure-url共用,這兩個handler-rul的設定代表啟用AuthenticationSuccessHandler與AuthenticationFailureHandler,如果想實現轉入登入前的那一頁用這個設定會很容易達成。

<http-basic>則代表使用HTTP basic authentication header,主要是將帳密用冒號(:)組合,再以base64編碼後送出,這個設定讓系統可以很容易地支援REST的程式。

<logout>代表起用Logout Filter,就SpringSecurity認為,每個系統都應該要有一個Logout Filter才對,比較form-login,當然也可以設定logout-url、logout-success-url及success-handler-rel,另一項是invalidate-session,若設定為true,在登出的同時就會令session失效。

 

18 3月 2011

Spring Security (1) Basic Configuration

一個對外的系統通常有權限設定,主要的需求通常就是兩個

  • 判斷目前的操作人員是誰
  • 人員是否可以進行這個操作

Spring Security提供了一個快速而有彈性的方法可以處理上述兩個需求,當然,有彈性通常也代表較多的設定與較複雜的設計....

我們先假設一個基本的網頁系統權限需求,除index.jsp外,其他的頁面存取都必需要是登入後才能看到,但/admin.jsp則必需是具有admin角色的人員才能看到。

web.xml

第一步當然是載入Spring的設定檔....
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
再來就是利用Spring的filter來檢查url及使用者,設定welcome file主要是讓url為"/"的request直接送到index.jsp
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

接下來就是spring security的設定了,先用最基本的方式,由Spring提供登入的頁面,我們只要提供哪些url需要被檢查,登入的帳號密碼資料即可。

<http auto-config='true'>
    <intercept-url pattern="/" filters="none" />
    <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
    <intercept-url pattern="/**" access="ROLE_USER" />
</http>
<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
            <user name="user" password="user" authorities="ROLE_USER" />
        </user-service>
    </authentication-provider>
</authentication-manager>
<http>的<intercept-url>指出哪些url需要被檢查,像"/admin.jsp"就要是具有"ROLE_ADMIN"角色的人才能進人,而除了“/“之外的資源全部需要登入後才能使用。
<user-service>則是提供了帳密及角色的資料。

接下來在browser上試著要進入系統,你會發現除了"/"之外,都會跳出一個登入頁面 Spring Login
可以試著用admin或user的帳號進入。  

這就是最基本的Spring Security 體驗....

25 2月 2011

Startup MX4J HttpAdaptor with Spring

我用JMX用的並不多,多半而言就是拿來控制Logger 的Level,最多加個清Cache,其餘就沒什麼特別在意的。
控制的介面用jconsole多半還過得去,部份案子還是會使用mx4j的HttpAdaptor來建立Web的操作介面,只是在使用上,要自動帶起HttpAdaptor還是有些困擾。

先看看MX4J 的HttpAdaptor,

public void start() throws IOException {
    final Logger logger = getLogger();
    if (server != null) {
         serverSocket = createServerSocket();
         .....
    } else {
        if (logger.isEnabledFor(Logger.INFO)) logger.info("Start failed, no server target server has been set");
    }
}
呼叫start()後,HttpAdaptor就可以提供Web的操作介面。
不過HttpAdaptor一開始就會先確認MBeanServer "server" 的有無,如果沒有就會寫個Log然後結束。
而這個MBeanServer什麼時候會被加進來呢?
是依JMX規格MBeanRegistration,執行preRegister()後才會帶進來,所以如果想在Spring的設定檔裡,加個init-method="start",是無法讓這個Adaptor正常提供Web操作。

那就看看Spring MBeanExporter的原始碼是如何操作MBean的Lifecycle,看到MBeanExporterLinstener這Interface,所以我們可以透過這interface讓系統自動帶起HttpAdaptor;但實際上執行有點困難,因為Spring 2.5以後提供了一個context:mbean-server,可以直接取得系統中現有的MBeanServer或是自行建立一個,所以比較無從加入MBeanExporterLinstener到MBeanServer中。

最後想想,還是從MBeanRegistration這個介面下手,所以直接extend HttpAdaptor後override postRegister()....

 

public class AutostartHttpAdaptor extends HttpAdaptor {
 
 private static final Logger logger = LoggerFactory.getLogger(AutostartHttpAdaptor.class);
 @Override
 public void postRegister(Boolean registrationDone) {
  super.postRegister(registrationDone);
  
  if (!registrationDone) {
   logger.warn("HttpAdaptor should not be invoked start() without registration success.");
   return;
  }
  
  logger.info("Post Register HttpAdaptor('"+registrationDone+"'):");

  try {
   this.start();
  } catch (IOException e) {
   logger.error("HttpAdaptor startup has been failed.", e);
  }
 }

 @Override
 public void postDeregister() {
  super.postDeregister();
  logger.info("Post Deregister HttpAdaptor():");
  this.stop();
 }
 
}

19 1月 2011

Web Application Security

Web Application Security要做的通常不過就是確認目前使用者是誰,能不能做目前要做的動作,就算提到到SSO、OpenID這些東西,也不過是在確認使用者上多些手續而已。

近來面談的人,跟兩三年前不同,多半對Spring都有些瞭解,但再深入問些應用卻又讓我有點失望,Sercurity就是很常讓我不滿意的地方。
有些人還停留在Submit button的enable or disable,這種情形就比較糟糕,有些完全沒有意識到這樣做會有問題的地方,所以開個Firebug,將disabled拿掉,這些人才覺得這是個問題...再有些人認為用method="GET" or "POST"就能阻止這些問題,但要送一個http post request又有何難?

有些人會很快回覆說利用Filter控制,我個人也認為基本上沒有問題,但我不認為全部都在Filter裡處理完是個好方法,所以我通常會再問如何在Spring的AOP裡得知現在的使用者為何,部份的人會說要修改API,將使用者當做參數之一,或是說傳入HttpSession,但沒有人跟我說過:因為是Web Application,所以可以利用ThreadLocal,如果是其他類型的Applicaton,利用InheritableThreadLocal也可以達成。

將使用者資料透過Filter,自Session中取得放入ThreadLocal中,這做法已經用了好幾年,我想這不是一個很具獨特性的做法,因為像是Spring Security已經到了3.X版,而它最重要的存放使用者的方式就是在放ThreadLocal中;能夠取到使用者的資料,那要在Filter、Service Layer或AOP中要進行控管或記錄都不會是問題,端看設計者的需要。

寫到這又有些擔心,擔心自己是不是自我意識過於良好,不過管他的! 我就是覺得目前看來,不把使用者資料放在ThreadLocal中就是一種設計上的缺陷!

09 11月 2010

Spring MVC (1)

因為專案需要,但是專案沒有wiki的系統(說來Redmine真的不錯用!),就稍為利用這裡記錄關於SpringMVC想說明的部份

(1)library
要開始一個SpringMVC很容易,第一請先建立一個web project,要利用mvn eclipse:eclipse或直接在ide裡開都ok,pom.xml裡的dependency只需要

 <dependencies>
  <dependency>
   <groupid>org.springframework</groupid>
   <artifactid>spring-webmvc</artifactid>
   <version>3.0.5.RELEASE</version>
  </dependency>
  <dependency>
   <groupid>javax.servlet</groupid>
   <artifactid>jstl</artifactid>
   <version>1.2</version>
  </dependency>
 </dependencies> 

(2)web.xml
web.xml也很容易,只要將SpringMVC要用的front controller -- DispatcherServlet喚起就可以,而serlvet-mapping就看你高興,不喜歡以.do結尾也可用asp,php或html來混淆他人...下列的設定代表所有http://host/module/XXXX.do的url皆會由Spring的DispatcherServlet處理

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee">
 <servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>*.do</url-pattern>
 </servlet-mapping>
</web-app> 


(3)spring-servlet.xml
如果你有沒有在web.xml利用ContextLoaderListener來載入的spring configuration,SpringMVC則會自動載入/WEB-INF/spring-servlet.xml,如果沒用ContextLoaderListener也找不到該檔就會顯示錯誤

<?xml version="1.0" encoding="UTF-8"?>
<beans xsi:schemalocation="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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans">

 <context:component-scan base-package="idv.elliot.web.controller"/>

 <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="viewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView">
  </property><property name="prefix" value="/WEB-INF/jsp/">
  </property><property name="suffix" value=".jsp">
 </property></bean>
</beans> 

簡單來說,利用component-scan來找出利用annotation標示的Spring components,當中也包含了皆下來要提的Controller,然後建立一個viewResolver,這是用最基本的UrlBasedViewResolver
如此一來,當Controller的method回傳abc時,SpringMVC就會將其導向http://host/module/WEB-INF/jsp/abc.jsp
至於為什麼要把jsp放到/WEB-INF/下,則是因為這只要/WEB-INF/裡的所有東西必需是自系統內的servlet forward過去才能取得,一般人無法直接以url接觸到該resource

(4)建立Controller
先用個helloworld吧,Struts用Action,SpringMVC則是用Controller,
而要把Class當SpringMVC的Controller只要在Class前加上@Controller的annotation即可
基本的method則是return ModelAndView,然後在method前加上@RequestMapping的Annotation
下列這個Class說明當使用者輸入http://host/module/sayHello.do時即會呼叫HelloController.sayHello(),而回傳ModelAndView("hello")則是讓SpringMVC的ViewReslover找到對應的jsp。

package idv.elliot.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController {
 
 @RequestMapping("/sayHello")
 public ModelAndView sayHello() {
  return new ModelAndView("hello");
 }
}

(5)hello.jsp
重點是jsp存放的位置,而不是jsp,請記得之前viewResolver的設定,要放在/WEB-INF/jsp/下即可

只要輸入http://host/module/sayHello.do就可以看到結果,而所有專案資料的截圖如下


20 10月 2009

hibernate3-maven-plugin直接使用Spring AnnotationSessionFactoryBean

新公司有自己的一套Framework,並自行開發一套code gen的工具,稍為喚起了我曽經用過Hibernate Tools的記憶,一方面是因為用了JDK5的Generic後,要寫的Code變少,另一方面也是因為如果有修改,code gen出來的東西比較不易配合更動,所以也沒有納入我常用的工具之一。不過既然想起了Hibernate tools,那就再用用看有沒有什麼改變,或許會有驚喜吧。

不過似乎沒有…特別是不能直接用Spring Configuration Xml中已經訂好的設定,一定要另訂hibernate.cfg.xml,這就又讓我難過了一下,再發現居然連hibernate3-maven-plugin也無法直接使用Spring 裡訂好的AnnotationSessionFactoryBean或LocalSessionFactoryBean,只好試著改看看有沒有辦法可以讓我懶一點不要再去重訂hibernate.cfg.xml。


發現hibernate3-maven-plugin中有一個AnnotationComponentConfiguration,看來似是一個不錯的切入點,稍為改了一下還發現真能work!下面列的就是實做出來的東西。


1.pom.xml

改maven的plugin不用maven也太說不過去了吧,所以pom.xml是一定要的。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

 <modelVersion>4.0.0</modelVersion>

 <groupId>org.elliot.hibernate</groupId>
 <artifactId>maven-hibernate3-jdk15</artifactId>
 <version>1.0-SNAPSHOT</version>
 <name>Maven Hibernate3 Implementation - JDK15</name>
 <packaging>jar</packaging>
 <dependencies>
  <dependency>
   <groupId>org.apache.maven</groupId>
   <artifactId>maven-model</artifactId>
   <version>2.0.6</version>
  </dependency>
  <dependency>
   <groupId>org.apache.maven</groupId>
   <artifactId>maven-plugin-api</artifactId>
   <version>2.0.6</version>
  </dependency>
  <dependency>
   <groupId>org.apache.maven</groupId>
   <artifactId>maven-artifact</artifactId>
   <version>2.0.6</version>
  </dependency>
  <dependency>
   <groupId>org.apache.maven</groupId>
   <artifactId>maven-project</artifactId>
   <version>2.0.6</version>
  </dependency>
  <dependency>
   <groupId>org.codehaus.mojo.hibernate3</groupId>
   <artifactId>maven-hibernate3-jdk15</artifactId>
   <version>2.2</version>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-core</artifactId>
   <version>3.3.2.GA</version>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-entitymanager</artifactId>
   <version>3.4.0.GA</version>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>ejb3-persistence</artifactId>
   <version>1.0.2.GA</version>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-annotations</artifactId>
   <version>3.4.0.GA</version>
  </dependency>
  <dependency>
   <groupId>jboss</groupId>
   <artifactId>jboss-common</artifactId>
   <version>4.0.2</version>
  </dependency>
  <dependency>
   <groupId>javassist</groupId>
   <artifactId>javassist</artifactId>
   <version>3.4.GA</version>
  </dependency>
  <!--可視情形改用其他版本的Spring-->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>org.springframework.orm</artifactId>
   <version>3.0.0.RC1</version>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>1.5</source>
     <target>1.5</target>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>
2. SpringComponentConfiguration.java

我暫用org.codehaus.mojo.hibernate3.configuration這package,怕AnnotationComponentConfiguration會不會有什麼method不能用,後來看code發現沒問題,也可以改成你喜歡的名稱

package org.codehaus.mojo.hibernate3.configuration;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.mojo.hibernate3.ExporterMojo;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;
import org.springframework.util.StringUtils;

public class SpringComponentConfiguration extends
  AnnotationComponentConfiguration {
 private static final Log logger = LogFactory
   .getLog(SpringComponentConfiguration.class);

 private ExporterMojo exporterMojo;
 private ApplicationContext applicationContext;

 public String getName() {
  return "springconfiguration";
 }

 public ExporterMojo getExporterMojo() {
  return exporterMojo;
 }

 public void setExporterMojo(ExporterMojo exporterMojo) {
  this.exporterMojo = exporterMojo;
 }

 @Override
 protected Configuration createConfiguration() {
  String sessionFactoryBean = getExporterMojo().getComponentProperty(
    "sessionFactoryBean", "sessionFactory");
  String appContextLocations = getExporterMojo().getComponentProperty(
    "appContextLocations", "classpath*:spring*.xml");
  logger.info("Initial info: [sessionFactoryBean]=" + sessionFactoryBean
    + ", [appContextLocations]=" + appContextLocations);

  String[] locations = StringUtils.delimitedListToStringArray(
    appContextLocations, ",");
  // Initial ApplicationContext from spring configuration xml files.
  this.applicationContext = new FileSystemXmlApplicationContext(locations);

  // Get AnnotationSessionFactoryBean from spring
  AnnotationSessionFactoryBean asfb = (AnnotationSessionFactoryBean) applicationContext
    .getBean("&" + sessionFactoryBean);

  DataSource dataSource = asfb.getDataSource();
  ThreadLocalHolder.setDataSource(dataSource);

  Configuration configuration = asfb.getConfiguration();
  configuration.setProperty(Environment.CONNECTION_PROVIDER,
    ThreadLocalConnectionProvider.class.getName());

  return configuration;
 }

 @Override
 protected void validateParameters() throws MojoExecutionException {
  // don't validate
 }

}
3.ThreadLocalConnectionProvider.java

因為Configuration不能直接設DataSource,所以只好放到ThreadLocal

package org.codehaus.mojo.hibernate3.configuration;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.connection.ConnectionProvider;

public class ThreadLocalConnectionProvider implements ConnectionProvider {
 private static final Log logger = LogFactory.getLog(ThreadLocalConnectionProvider.class);
 public ThreadLocalConnectionProvider() {
 }
 public void close() throws HibernateException {

 }

 public void closeConnection(Connection conn) throws SQLException {
  conn.close();
 }

 public void configure(Properties props) throws HibernateException {
  //Do nothing

 }

 public Connection getConnection() throws SQLException {
  DataSource dataSource = ThreadLocalHolder.getDataSource();
  if (null == dataSource) {
   logger.error("Please check ThreadLocalHolder.setDataSource has been invocked.");
   throw new SQLException("No usable DataSource.");
  }
  return dataSource.getConnection();
 }

 public boolean supportsAggressiveRelease() {
  // TODO Auto-generated method stub
  return true;
 }

}
4.ThreadLocalHolder.java

package org.codehaus.mojo.hibernate3.configuration;

import javax.sql.DataSource;

public abstract class ThreadLocalHolder {
 private static ThreadLocal<DataSource> dataSourceHolder = new ThreadLocal<DataSource>();

 public static DataSource getDataSource() {
  return dataSourceHolder.get();
 }

 public static void setDataSource(DataSource dataSource) {
  dataSourceHolder.set(dataSource);
 }
 
}
5.components.xml

這檔案放在src/main/resources/META-INF/plexus下,是maven plugin的設定檔

<component-set>
  <components>
 <component>
      <role>org.codehaus.mojo.hibernate3.configuration.ComponentConfiguration</role>
      <role-hint>springconfiguration</role-hint>
      <implementation>org.codehaus.mojo.hibernate3.configuration.SpringComponentConfiguration</implementation>
    </component>
  </components>
</component-set>
完成之後就用mvn install裝到repository裡以供使用,使用方式就只是改變hibernate3-maven-plugin中的dependency。

<build>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>hibernate3-maven-plugin</artifactId>
    <version>2.2</version>
    <configuration>
     <componentProperties>
      <jdk5>true</jdk5>
      <implementation>springconfiguration</implementation>
      <appContextLocations>classpath*:applicationContext.xml</appContextLocations>
      <sessionFactoryBean>sessionFactory</sessionFactoryBean>
     </componentProperties>
    </configuration>
    <dependencies>
     <dependency>
      <groupId>org.elliot.hibernate</groupId>
      <artifactId>maven-hibernate3-jdk15</artifactId>
      <version>1.0-SNAPSHOT</version>
     </dependency>
     <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-ehcache</artifactId>
      <version>3.3.2.GA</version>
     </dependency>
     <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
      <version>3.4.0.GA</version>
     </dependency>
     <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>1.2.121</version>
     </dependency>
    </dependencies>
   </plugin>
  </plugins>
 </build>
加入configuration中列出的設定,appContextLocations是專案中spring設定檔的檔案位置,sessionFactoryBean則是spring中你定義的AnnotationSessionFactoryBean名稱。

再來就可以用mvn hibernate3:hbm2ddl 之類的goals來測試囉。

10 4月 2009

Compass Configurations of Spring

專案中要用到全文檢索的功能,  從Lucene開始看起, 後來發現Compass, Compass將Lucene底層封裝後來使用, 而且可以配合Hibernate跟Spring, 可以直接在Hibernate更新資料時一併處理index, 比直接使用Lucene要方便得多, 只是資料實在不算多, 而且不少資料是較舊的, 花了不少時間在試, 當然要記一下囉! 不過只記設定方式, 相關用法....懶, 有機會再整理筆記好了...

一、Maven Dependencies

<!--Search Engine-->
<dependency>
 <groupId>org.compass-project</groupId>
 <artifactId>compass</artifactId>
 <version>2.1.3</version>
</dependency>
<dependency>
 <groupId>org.apache.lucene</groupId>
 <artifactId>lucene-core</artifactId>
 <version>2.4.1</version>
</dependency>
<dependency>
 <groupId>org.apache.lucene</groupId>
 <artifactId>lucene-highlighter</artifactId>
 <version>2.4.1</version>
</dependency>
<dependency>
 <groupId>org.apache.lucene</groupId>
 <artifactId>lucene-analyzers</artifactId>
 <version>2.4.1</version>
</dependency>
<dependency>
 <groupId>org.apache.lucene</groupId>
 <artifactId>lucene-queries</artifactId>
 <version>2.4.1</version>
</dependency>

更新其實還蠻快的, Lucene一更新, Compass也隨即有新的對應版本, Maven中的dependency會用到大概就是這些.

二、Spring Beans Configuration

<bean id="annotationConfiguration"
  class="org.compass.annotations.config.CompassAnnotationsConfiguration" />
<bean id="compass" class="org.compass.spring.LocalCompassBean">
 <!-- xml configuration mode 
 <property name="resourceLocations">
  <list>
   <value>classpath:your/domain/Entity.cmd.xml</value>
  </list>
 </property>
 -->
 <!-- anontaition mode -->
 <property name="classMappings">
  <list>
   <!--<value>your.domain.Entity</value>-->
  </list>
 </property>
 <property name="compassConfiguration" ref="annotationConfiguration" />
 <property name="compassSettings">
  <props>
   <prop key="compass.engine.connection">
    ${compass.engine.connection}</prop>
   <prop key="compass.transaction.factory">
    org.compass.spring.transaction.SpringSyncTransactionFactory</prop>
   <prop key="compass.engine.optimizer.aggressive.mergeFactor">0</prop>
   <prop key="compass.engine.analyzer.default.type">
    org.apache.lucene.analysis.cjk.CJKAnalyzer</prop>
  </props>
 </property>
 <property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="hibernateGpsDevice" class="org.compass.gps.device.hibernate.HibernateGpsDevice">
 <property name="name" value="hibernateDevice" />
 <property name="sessionFactory" ref="sessionFactory" />
 <property name="nativeExtractor">
  <bean class="org.compass.spring.device.hibernate.SpringNativeHibernateExtractor" />
 </property>
</bean>

<bean id="compassGps" class="org.compass.gps.impl.SingleCompassGps"
 init-method="start" destroy-method="stop">
 <property name="compass" ref="compass" />
 <property name="gpsDevices">
  <list>
   <!--
   When using {SpringSyncTransactionFactory}, this gps device
   wrapper(SpringSyncTransactionGpsDeviceWrapper) should be used to 
   wrap all the devices
   -->
   <bean
    class="org.compass.spring.device.SpringSyncTransactionGpsDeviceWrapper">
    <property name="transactionManager" ref="transactionManager" />
    <property name="gpsDevice" ref="hibernateGpsDevice" />
   </bean>
  </list>
 </property>
</bean>

大部份都是制定的, 只有少數是可以讓你變動, 我的設定跟網路上其他可以找到的資料來比, 有差別的是hibernateGpsDevice跟compassGps.gpsDevices, hibernateGpsDevice用org.compass.gps.device.hibernate.HibernateGpsDevice是因為org.compass.spring.device.hibernate.SpringHibernate3GpsDevice在2.0M1時就設定為deprecated, 必需改用HibernateGpsDevice, 而gpsDevices使用org.compass.spring.device.SpringSyncTransactionGpsDeviceWrappe的原因在這Class的Javadoc中, 當使用SpringSyncTransactionFactory來管transaction時, 就要使用SpringSyncTransactionGpsDeviceWrapper將其他GPS Devices封裝.

接下再透過cmd.xml或annotation來設定Searchable的Class跟Index properties後就可以很容易的操作了, Compass的reference文件相當清楚, 仔細讀過的話大部份問題都可以找到答案, 配合PDFBox跟POI等OSS, 還可以將PDF, Word, Excel, PowerPoint中的內容取出做為檢索資料, 完成一個簡單的全文檢索系統實在不費什麼力氣...

02 3月 2007

Struts2 Note -- (2)整合Spring2

2.0.6與之前版本整合的設定 2.0.6與之前版本的整合方式有很大不同,而整合的Spring版本也從1.2.8到了2.0.1 2.0.6將org.apache.struts2.spring.StrutsSpringObjectFactory自struts2-core移至struts2-spring-plugin, 所以必需要加入struts2-spring-plugin的Library.使用Maven2的可以這樣設定 pom.xml
<dependency>
 <groupId>org.apache.struts</groupId>
 <artifactId>struts2-spring-plugin</artifactId>
 <version>2.0.6</version>
</dependency>
2.0.6與之前的版本皆需在struts.propertis中設定
struts.objectFactory = spring
struts2-spring-plugin中除了org.apache.struts2.spring.StrutsSpringObjectFactory這個主要java外, 另外有的就是一個struts-plugin.xml,設定了struts.objectFactory真正使用的Class struts-plugin.xml
<struts>
    <bean type="com.opensymphony.xwork2.ObjectFactory" name="spring" class="org.apache.struts2.spring.StrutsSpringObjectFactory" />
    
    <!--  Make the Spring object factory the automatic default -->
    <constant name="struts.objectFactory" value="spring" />

    <package name="spring-default">
        <interceptors>
            <interceptor name="autowiring" class="com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor"/>
            <interceptor name="sessionAutowiring" class="org.apache.struts2.spring.interceptor.SessionContextAutowiringInterceptor"/>
        </interceptors>
    </package>    
</struts>
2.0.6與之前版本整合的差異 之前的版本是這樣整合的
org.apache.struts2.dispatcher.Dispatcher
 private void init(ServletContext servletContext) {
  .......................
  if (Settings.isSet(StrutsConstants.STRUTS_OBJECTFACTORY)) {
   String className = (String) Settings.get(StrutsConstants.STRUTS_OBJECTFACTORY);
   if (className.equals("spring")) {
    className = "org.apache.struts2.spring.StrutsSpringObjectFactory";
   }
  }
  .......................
 }
很明顯的有硬來的嫌疑 2.0.6則是分為了幾個部份
org.apache.struts2.dispatcher.Dispatcher
 public void init() {
  init_DefaultProperties(); // [1]
  init_TraditionalXmlConfigurations(); // [2]
  init_LegacyStrutsProperties(); // [3]
  .......................
  init_AliasStandardObjects() ; // [4]
  .......................
 }
[1]載入了org/apache/struts2/default.properties [2]利用了StrutsXmlConfigurationProvider來載入struts-default.xml,struts-plugin.xml,struts.xml三個xml, 其中就包含了struts2-spring-plugin的struts-plugin.xml,這樣就能取得 <constant name="struts.objectFactory" value="spring" /> <bean ... name="spring" class="org.apache.struts2.spring.StrutsSpringObjectFactory" /> 這兩項設定 [3]載入struts.properties,透過手動設定的struts.objectFactory = spring, 就可以明白要載入的ObjectFactory Class是哪一個了 [4]此時才真正載入所有指定的Class 這樣做得確是較之前的方式好上不少,但是xml的設定檔就只能有這三個struts-default.xml,struts-plugin.xml,struts.xml 特別是struts-plugin.xml,如果自己還要寫Plugin或是希望同時能使用兩種不同的Plugin,都會遇到設定的問題.