'2009/06'에 해당되는 글 11건
- 개발 역량 평가표 | 2009/06/20
- Commons HttpClient로 SSL 통신하기 (1) | 2009/06/18
- Database Engine Comparison | 2009/06/17
- Hudson을 이용한 지속적 통합 | 2009/06/12
- Maven Directory Layout | 2009/06/11
- Maven Build Lifecycle | 2009/06/11
- Commons Configuration Wrapper | 2009/06/03
- Tomcat Servlet Configuration | 2009/06/03
- JSTL 1.0, 1.1, 1.2 | 2009/06/02
- Message Digest 유틸리티 | 2009/06/01
개발 역량 평가표 :: 2009/06/20 23:11
소프트웨어 회사의 개발 역량 평가표
출처: http://www.abcswcon.com/services.htm
아래 각 해당되는 항목을 1점씩 계산하여 모두 합산합니다.
| 1. 전사적으로 하나의 소스코드관리시스템을 사용하고 있다. 2. 모든 소스코드 및 개발문서는 소스코드관리시스템에 저장되어 있다. 3. 각 마일스톤마다 Baseline을 설정하고 있다. 4. 소스코드관리시스템에 체크인 시 메시지를 작성하는 규칙이 있고, 모든 개발자가 이를 지키고 있다. 5. 모든 소스코드는 리뷰를 하고 있다. 6. 자동으로 일일빌드를 하고 있다. 7. 전사적으로 하나의 버그관리시스템을 사용하고 있다. 8. 모든 버그를 버그관리시스템에 등록하고 있으며 다른 곳에 별도로 관리하지 않는다. 9. 모든 직원이 버그관리시스템에 스스로 이슈를 등록한다. 10. 프로젝트의 스펙문서를 가지고 있다. 11. 스펙문서를 모든 관련자가 충분히 리뷰한다. 12. 스펙이 바뀜에 따라 스펙문서가 업데이트되고 있다. 13. 스펙 변경이 통제 관리되고 있다. 14. 1,2일 단위의 상세한 일정을 가지고 있다. 15. 일정은 개발자가 산정한다. 16. 일정은 지속적으로 업데이트되고 있다. 17. 별도의 테스트 팀이나 테스터가 있다. 18. 테스트 케이스를 가지고 있다. 19. 프로젝트 리스크 관리를 하고 있다. 20. 리스크에 대한 백업 플랜이 있으며 리스크 관리계획이 주기적으로 갱신된다. |
평가 결과 분석
- 20점 - 소프트웨어 개발의 기초가 아주 잘 되어 있습니다. 소프트웨어 프로젝트를 수행하는데 별 문제가 없습니다.
- 15점 이상 - 이 정도면 기초가 매우 양호합니다. 지금까지도 프로젝트를 꽤 잘 수행하고 있었을 것입니다. 몇 가지만 보완하면 될 것 같습니다. 수행 능력 향상을 위한 현실적인 방법을 보강할 필요가 있습니다..
- 10점 이상 - 프로젝트 진행을 개선하기 위해 여러 시도를 했겠지만, 여전히 많은 개선 욕구를 느끼고 있을 것입니다. 현실적인 많은 부분을 개선하는데 컨설팅이 도움이 될 수 있습니다..
- 10점 미만 - 만약 프로젝트에 성공했다면 기적이나 운이라고 여겨야 합니다. 지금 당장 조치를 취해야 합니다. 효율적인 개선을 위해서는 컨설팅이 꼭 필요합니다
출처: http://www.abcswcon.com/services.htm
Commons HttpClient로 SSL 통신하기 :: 2009/06/18 23:37
Apache Commons HttpClient는 JDK 1.4부터 등장한 Java Secure Socket Extension (JSSE)를 기반으로 SSL (또는 TLS) 상의 HTTP (HTTP/S) 통신에 대한 지원을 제공한다. Commons HttpClient를 이용한 HTTP/S 통신 방법을 살펴보자.
1. Commons HttpClient 사용하기
일반적으로 Commons HttpClient의 HTTP 통신은 아래와 같다.
정상적인 경우 결과는 아래와 같다.
실패한 경우 결과는 아래와 같다.
2. SSL 통신과 Trusted CA 인증서 등록하기
JSSE가 올바르게 설치되었다면, 기본적으로 HTTP/S 통신도 일반 HTTP 통신과 같이 위와 같은 코드를 그대로 사용할 수 있다. 단, 이 경우에 서버 싸이트 인증서가 클라이언트쪽에 신뢰하는 인증서로서 인식될 수 있어야 한다. 그렇지 않으면 아래와 같은 SSL handshake 오류가 발생한다.
JDK에 의해 제공되어지는 Java Standard Trust Keystore는 ${JAVA_HOME}/jre/lib/security/cacerts에 위치한다. 이 cacerts 키스토어 파일에 대상 서버의 SSL 싸이트 인증서를 발행한 기관의 CA 인증서가 신뢰하는 인증서로 등록되어 있어야 한다. 다음과 같이 keytool.exe를 사용하여 키스토어에 등록된 신뢰하는 인증서 목록을 조회할 수 있다.
다음과 같은 방법으로 키스토어 파일에 Trusted CA 인증서를 추가로 등록할 수 있다. CA 인증서는 웹브라우저에서 열쇠모양의 아이콘을 누르면 해당 싸이트 인증서를 볼 수 있고, 거기에서 인증서를 복사할 수 있다. 아래 예시는 Trusted CA 인증서를 ${JAVA_HOME}\jre\lib\secutiry\cacerts에 등록을 하는 방법이다.
이렇게 서버 CA 인증서가 신뢰하는 인증서로 등록이 되면, 일반 HTTP 통신과 같이 URL이 https://인 주소로 SSL 통신을 정상적으로 할 수 있다. 키스토어 파일에서 인증서를 제거하는 방법은 아래와 같다.
아래처럼 서버 싸이트 인증서를 바로 등록할 수도 있다. 그러나 싸이트 인증서는 보통 Trusted CA 인증서보다 유효기간이 짧아 매번 갱신을 해줘야 할 것이다.
3. Commons HttpClient의 SSL 커스터마이징
기본적인 사용법 이외에 자기서명(self-signed)되었거나 또는 신뢰되지 않은(untrusted) SSL 인증서를 사용하는 경우처럼 SSL 통신을 커스터마이징할 필요가 있을 수 있다.
기본 커스터마이징 방법은 org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory을 구현해서 org.apache.commons.httpclient.protocol.Protocol를 생성하여 등록해주면 된다. 다음과 같은 코드 한 줄을 추가해 주면 된다. 자세한 내용은 이곳을 참조한다.
Commons HttpClient의 contribution 패키지에서 사용할 수 있는 EasySSLProtocolSocketFactory를 사용하면 신뢰되지 않은 자기서명(self-signed)된 인증서를 가진 서버와도 바로 SSL 통신을 할 수 있다. 즉, cacerts 키스토어에 서버 인증서를 별도로 등록할 필요가 없다. 다음과 같이 사용할 수 있다.
호출 로그
참조: http://hc.apache.org/httpclient-3.x/sslguide.html
1. Commons HttpClient 사용하기
일반적으로 Commons HttpClient의 HTTP 통신은 아래와 같다.
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
// import org.apache.commons.httpclient.methods.PostMethod;
public class HttpClientSample {
public static void main(String[] args) {
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("http://www.java2go.net/");
// PostMethod httppost = new
// PostMethod("https://www.java2go.net/nopage.html");
try {
int statusCode = httpclient.executeMethod(httpget);
System.out.println("Response Status Code: " + statusCode);
System.out.println("Response Status Line: " + httpget.getStatusLine());
System.out.println("Response Body: \n"
+ httpget.getResponseBodyAsString());
if (statusCode == HttpStatus.SC_OK) {
// if (statusCode >= 200 && statusCode < 300) {
System.out.println("Success!");
} else {
System.out.println("Fail!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
httpget.releaseConnection();
}
}
} |
정상적인 경우 결과는 아래와 같다.
Response Status Code: 200 Response Status Line: HTTP/1.1 200 OK Response Body: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Java2go.net</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> ... 생략 ... </body> </html> Success! |
실패한 경우 결과는 아래와 같다.
Response Status Code: 404 Response Status Line: HTTP/1.1 404 Not Found Response Body: <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <HTML><HEAD> <TITLE>404 Not Found</TITLE> </HEAD><BODY> <H1>Not Found</H1> The requested URL /nopage.html was not found on this server.<P> <HR> <ADDRESS>Apache/1.3.37p3 Server at java2go.net Port 80</ADDRESS> </BODY></HTML> Fail! |
2. SSL 통신과 Trusted CA 인증서 등록하기
JSSE가 올바르게 설치되었다면, 기본적으로 HTTP/S 통신도 일반 HTTP 통신과 같이 위와 같은 코드를 그대로 사용할 수 있다. 단, 이 경우에 서버 싸이트 인증서가 클라이언트쪽에 신뢰하는 인증서로서 인식될 수 있어야 한다. 그렇지 않으면 아래와 같은 SSL handshake 오류가 발생한다.
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SunJSSE_az.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SunJSSE_az.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SunJSSE_ax.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.j(DashoA12275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275) at com.sun.net.ssl.internal.ssl.AppOutputStream.write(DashoA12275) ... 생략 ... Caused by: sun.security.validator.ValidatorException: No trusted certificate found at sun.security.validator.SimpleValidator.buildTrustedChain(SimpleValidator.java:304) at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:107) at sun.security.validator.Validator.validate(Validator.java:202) at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(DashoA12275) at com.sun.net.ssl.internal.ssl.JsseX509TrustManager.checkServerTrusted(DashoA12275) ... 17 more |
JDK에 의해 제공되어지는 Java Standard Trust Keystore는 ${JAVA_HOME}/jre/lib/security/cacerts에 위치한다. 이 cacerts 키스토어 파일에 대상 서버의 SSL 싸이트 인증서를 발행한 기관의 CA 인증서가 신뢰하는 인증서로 등록되어 있어야 한다. 다음과 같이 keytool.exe를 사용하여 키스토어에 등록된 신뢰하는 인증서 목록을 조회할 수 있다.
C:\jdk1.4.2\jre\lib\security>keytool -list -v -keystore cacerts Enter keystore password: changeit Keystore type: jks Keystore provider: SUN Your keystore contains 52 entries Alias name: verisignclass3g2ca Creation date: Jun 16, 2004 Entry type: trustedCertEntry Owner: OU=VeriSign Trust Network, OU="(c) 1998 VeriSign, Inc. - For authorized use only", OU=Class 3 Public Primary Certification Authority - G2, O="VeriSign, Inc.", C=US Issuer: OU=VeriSign Trust Network, OU="(c) 1998 VeriSign, Inc. - For authorized use only", OU=Class 3 Public Primary Certification Authority - G2, O="VeriSign, Inc.", C=US Serial number: 7dd9fe07cfa81eb7107967fba78934c6 Valid from: Mon May 18 09:00:00 KST 1998 until: Wed Aug 02 08:59:59 KST 2028 Certificate fingerprints: MD5: A2:33:9B:4C:74:78:73:D4:6C:E7:C1:F3:8D:CB:5C:E9 SHA1: 85:37:1C:A6:E5:50:14:3D:CE:28:03:47:1B:DE:3A:09:E8:F8:77:0F ******************************************* ******************************************* Alias name: entrustclientca Creation date: Jan 10, 2003 Entry type: trustedCertEntry Owner: CN=Entrust.net Client Certification Authority, OU=(c) 1999 Entrust.net Limited, OU=www.entrust.net/Client_CA_Info/CPS incorp. by ref. limits liab., O=Entrust.net, C=US Issuer: CN=Entrust.net Client Certification Authority, OU=(c) 1999 Entrust.net Limited, OU=www.entrust.net/Client_CA_Info/CPS incorp. by ref. limits liab., O=Entrust.net, C=US Serial number: 380391ee Valid from: Wed Oct 13 04:24:30 KST 1999 until: Sun Oct 13 04:54:30 KST 2019 Certificate fingerprints: MD5: 0C:41:2F:13:5B:A0:54:F5:96:66:2D:7E:CD:0E:03:F4 SHA1: DA:79:C1:71:11:50:C2:34:39:AA:2B:0B:0C:62:FD:55:B2:F9:F5:80 ... 생략 ... |
다음과 같은 방법으로 키스토어 파일에 Trusted CA 인증서를 추가로 등록할 수 있다. CA 인증서는 웹브라우저에서 열쇠모양의 아이콘을 누르면 해당 싸이트 인증서를 볼 수 있고, 거기에서 인증서를 복사할 수 있다. 아래 예시는 Trusted CA 인증서를 ${JAVA_HOME}\jre\lib\secutiry\cacerts에 등록을 하는 방법이다.
C:\j2sdk1.4.2\jre\lib\security>keytool -import -keystore cacerts -file
c:\certs\TradeSignCA.cer -alias tradesignca
Enter keystore password: changeit
Owner: CN=TradeSignCA, OU=AccreditedCA, O=TradeSign, C=KR
Issuer: CN=KISA RootCA 1, OU=Korea Certification Authority Central, O=KISA, C=KR
Serial number: 2764
Valid from: Tue Nov 15 11:14:59 KST 2005 until: Sun Nov 15 11:14:59 KST 2015
Certificate fingerprints:
MD5: C2:E0:27:3D:36:4B:86:29:74:4D:6B:9F:5A:B5:01:26
SHA1: A0:CD:6A:6D:A4:7B:73:15:F5:8A:CB:1F:C6:FD:C2:14:C9:3B:5D:BE
Trust this certificate? [no]: y
Certificate was added to keystore |
이렇게 서버 CA 인증서가 신뢰하는 인증서로 등록이 되면, 일반 HTTP 통신과 같이 URL이 https://인 주소로 SSL 통신을 정상적으로 할 수 있다. 키스토어 파일에서 인증서를 제거하는 방법은 아래와 같다.
C:\jdk1.4.2\jre\lib\security>keytool -delete -keystore cacerts -alias tradesignca Enter keystore password: changeit |
아래처럼 서버 싸이트 인증서를 바로 등록할 수도 있다. 그러나 싸이트 인증서는 보통 Trusted CA 인증서보다 유효기간이 짧아 매번 갱신을 해줘야 할 것이다.
C:\jdk1.4.2\jre\lib\security>keytool -import -keystore cacerts -file
c:\certs\www.java2go.net.cer -alias mykey
Enter keystore password: changeit
Owner: CN=www.java2go.net, OU=KTNET, OU=AccreditedCA, O=TradeSign, C=KR
Issuer: CN=TradeSignCA, OU=AccreditedCA, O=TradeSign, C=KR
Serial number: 596e9cf0
Valid from: Tue May 12 13:37:20 KST 2009 until: Wed May 12 14:07:20 KST 2010
Certificate fingerprints:
MD5: EF:EB:11:66:BD:CC:B1:D4:88:35:AB:25:9F:2F:79:8B
SHA1: DC:C4:31:20:46:25:72:68:8B:96:AC:92:EE:F3:8D:15:EF:A7:46:2D
Trust this certificate? [no]: y
Certificate was added to keystore |
3. Commons HttpClient의 SSL 커스터마이징
기본적인 사용법 이외에 자기서명(self-signed)되었거나 또는 신뢰되지 않은(untrusted) SSL 인증서를 사용하는 경우처럼 SSL 통신을 커스터마이징할 필요가 있을 수 있다.
기본 커스터마이징 방법은 org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory을 구현해서 org.apache.commons.httpclient.protocol.Protocol를 생성하여 등록해주면 된다. 다음과 같은 코드 한 줄을 추가해 주면 된다. 자세한 내용은 이곳을 참조한다.
Protocol.registerProtocol("https",
new Protocol("https", new MySSLSocketFactory(), 443)); |
Commons HttpClient의 contribution 패키지에서 사용할 수 있는 EasySSLProtocolSocketFactory를 사용하면 신뢰되지 않은 자기서명(self-signed)된 인증서를 가진 서버와도 바로 SSL 통신을 할 수 있다. 즉, cacerts 키스토어에 서버 인증서를 별도로 등록할 필요가 없다. 다음과 같이 사용할 수 있다.
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.protocol.Protocol;
public class HttpClientSample2 {
public static void main(String[] args) {
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.java2go.net/");
try {
Protocol.registerProtocol("https", new Protocol("https",
new EasySSLProtocolSocketFactory(), 443));
int statusCode = httpclient.executeMethod(httpget);
System.out.println("Response Status Code: " + statusCode);
System.out.println("Response Status Line: " + httpget.getStatusLine());
System.out.println("Response Body: \n"
+ httpget.getResponseBodyAsString());
if (statusCode == HttpStatus.SC_OK) {
System.out.println("Success!");
} else {
System.out.println("Fail!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
httpget.releaseConnection();
}
}
} |
호출 로그
{DEBUG} [2009-06-18 23:29:31,062] <org.apache.commons.httpclient.HttpConnection> ()
: Open connection to www.java2go.net:443 |
참조: http://hc.apache.org/httpclient-3.x/sslguide.html
Database Engine Comparison :: 2009/06/17 23:32
/DBMS
Java DB(Derby, H2, HSQLDB), MySQL, PostgreSQL 데이터베이스 엔진 비교표이다.
| Feature | H2 | Derby | HSQLDB | MySQL | PostgreSQL |
|---|---|---|---|---|---|
| Pure Java | Yes | Yes | Yes | No | No |
| Embedded Mode (Java) | Yes | Yes | Yes | No | No |
| Performance (Embedded) | Fast | Slow | Fast | N/A | N/A |
| In-Memory Mode | Yes | No | Yes | No | No |
| Transaction Isolation | Yes | Yes | No | Yes | Yes |
| Cost Based Optimizer | Yes | Yes | No | Yes | Yes |
| Explain Plan | Yes | No | Yes | Yes | Yes |
| Clustering | Yes | No | No | Yes | Yes |
| Encrypted Database | Yes | Yes | No | No | No |
| Linked Tables | Yes | No | Partially *1 | Partially *2 | No |
| ODBC Driver | Yes | No | No | Yes | Yes |
| Fulltext Search | Yes | No | No | Yes | Yes |
| User-Defined Datatypes | Yes | No | No | Yes | Yes |
| Files per Database | Few | Many | Few | Many | Many |
| Table Level Locking | Yes | Yes | No | Yes | Yes |
| Row Level Locking | Yes *9 | Yes | No | Yes | Yes |
| Multi Version Concurrency | Yes | No | No | No | Yes |
| Role Based Security | Yes | Yes *3 | Yes | Yes | Yes |
| Updatable Result Sets | Yes | Yes *7 | No | Yes | Yes |
| Sequences | Yes | No | Yes | No | Yes |
| Limit and Offset | Yes | No | Yes | Yes | Yes |
| Temporary Tables | Yes | Yes *4 | Yes | Yes | Yes |
| Information Schema | Yes | No *8 | No *8 | Yes | Yes |
| Computed Columns | Yes | No | No | No | Yes *6 |
| Case Insensitive Columns | Yes | No | Yes | Yes | Yes *6 |
| Custom Aggregate Functions | Yes | No | No | Yes | Yes |
| Footprint (jar/dll size) | ~1 MB *5 | ~2 MB | ~600 KB | ~4 MB | ~6 MB |
*1 HSQLDB supports text tables.
*2 MySQL supports linked MySQL tables under the name 'federated tables'.
*3 Derby support for roles based security and password checking as an option.
*4 Derby only supports global temporary tables.
*5 The default H2 jar file contains debug information, jar files for other databases do not.
*6 PostgreSQL supports functional indexes.
*7 Derby only supports updatable result sets if the query is not sorted.
*8 Derby and HSQLDB don't support standard compliant information schema tables.
*9 H2 supports row level locks when using multi version concurrency.
출처: http://www.h2database.com/html/features.html#comparison
Hudson을 이용한 지속적 통합 :: 2009/06/12 22:20

Hudson의 설치 방법 및 기본 사용법을 정리해 본다.
1. Hudson 설치 및 실행하기
- Hudson 배포 프로그램은 하나의 WAR 형태의 웹어플리케이션으로 이곳에서 최신 배포본을 다운받을 수 있다.
- JDK 1.5 이상을 필요로 하며, WAR 파일이므로 Tomcat 등 서블릿 컨테이너에 배포하면 바로 사용할 수 있다. Tomcat의 webapps에 파일을 복사 후 서버를 구동하면 http://localhost:8080/hudson/ 주소에서 바로 확인해 볼 수 있다.
- 또한 Hudson은 Winstone이라는 서블릿 컨테이너를 내장하고 있어 Tomcat 등의 서버에 배포하지 않고서도 곧바로 실행할 수 있다. 개인적으로는 이 실행 방법을 선호한다. 기본적으로 콘솔에서 다음과 같이 실행하면 된다. 디폴트로 ~(사용자)/.hudson을 홈디렉토리로 사용하고, 8080 포트를 HTTP 리스너 포트로 사용한다.
java -jar hudson.war
- Hudson을 직접 실행할 경우 다음처럼 옵션을 지정하여 홈디렉토리 위치 및 서비스 포트를 지정할 수 있다.
java -DHUDSON_HOME=/tools/hudson -jar hudson.war --httpPort=8088 --ajp13Port=8019
- HUDSON_HOME이 설정되어 있지 않으면 디폴트로 ~(사용자)/.hudson 밑에 war 파일을 풀고, 이곳에 각종 데이터 파일을 저장한다. HUDSON_HOME을 시스템 프로퍼티로 셋팅하여 생성되는 데이터 파일들을 설정된 해당 디렉토리에서 관리할 수 있다.
- --httpPort 옵션을 사용하여 HTTP 리스너 포트 설정하고, --ajp13Port 옵션을 사용해서 AJP13 리스너 포트를 변경할 수 있다. 디폴트로 사용하는 8080 포트는 개발 중 Tomcat 등 다른 서버와 충돌이 있을 수 있으므로 바꾸는 것이 좋다.
- 정상적으로 구동되었을 경우 브라우저를 통해 다음과 같은 초기화면을 볼 수 있다.

2. Hudson 윈도우 서비스 등록
Hudson을 실행할 때 위와 같은 스크립트를 작성하여 실행할 수 있지만, 윈도우 시스템인 경우 서비스로 등록하여 서버 시작시 자동으로 실행되게 할 수 있다. 다음과 같은 절차로 Hudson을 윈도우 서비스로 등록한다.
- Manage Hudson 관리 화면에서 하단에 있는 "Install as Windows Service"를 클릭한다.

- HUDSON_HOME으로 사용하는 인스톨 디렉토리를 지정한다.

- 인스톨이 완료되면 Hudson을 중지하고 새로운 윈도우 서비스를 시작한다.

- Hudson 윈도우 서비스를 재시작한다. 이때 HTTP 리스너 포트는 8080 기본 포트로 시작된다.

- 다음과 같이 hudson으로 윈도우 서비스가 등록이 되어 있다.

- Hudson을 윈도우 서비스로 등록하면 설치 디렉토리에 hudson.exe와 hudson.xml 파일이 생성된다. hudson.xml 파일의 내용을 다음과 같이 수정하여 Hudson 실행 옵션을 변경할 수 있다. 수정 후 윈도우 서비스를 재시작해야 한다.
-Xrs -Xmx256m -Dhudson.lifecycle=hudson.lifecycle.WindowsServiceLifecycle -jar "%BASE%\hudson.war" --httpPort=8088 --ajp13Port=8019
- 윈도우 서비스를 재거하려면 설치 디렉토리에서 다음 명령을 사용한다.
hudson.exe uninstall
3. Hudson 시스템 설정
Hudson에서 프로젝트를 등록하고 사용하기 위해서는 먼저 JDK, Mavan, Ant 등을 설정해야 한다. 또한 필요에 따라 시스템 보안 설정이나 관리자 이메일 등도 설정할 수 있다.
- 좌측 메뉴 Manage Hudson의 "Confiure System"에서 시스템 설정을 한다. 아래와 같이 JDK, Maven, Ant 등을 시스템 환경에 맞게 설정한다.

- Hudson은 설치 후 기본으로 보안설정이 되어 있지 않다. 지정된 사용자에게 권한을 부여하려면 "Enable security" 체크박스를 선택 한 후 적절한 보안 설정을 해준다.

- 보안 설정을 하고 나면 사용자는 계정 생성 화면과 로그인 화면을 볼 수 있다. 설정 권한에 따라 로그인 후에 이용할 수 있다.

4. 프로젝트 등록하기
- 화면 왼쪽 New Job 메뉴에서 새로운 프로젝트를 등록한다. Ant로 빌드하는 프로젝트인 경우는 "Build a free-style software project"를 선택하고, Maven2로 빌드하는 프로젝트는 "Build a maven2 project"를 선택한다.

- 프로젝트 빌드에 관한 세부 설정을 한다. 소스코드 관리 (SCM), Build Trigger, Build 도구 등을 설정한다. 기타 프로젝트 빌드 후 테스트 리포트나 관리자에 대한 이메일 공지 등의 옵션을 지정할 수 있다.

- 프로젝트를 등록하고 나면 초기화면에 아래와 같은 프로젝트 목록을 볼 수 있다.

5. 프로젝트 빌드 결과
Hudson은 등록된 프로젝트에 대해서 주기적으로 자동 빌드를 수행하며 빌드 및 테스트 결과를 만들어 낸다. 기본적으로 테스트 결과 트랜드를 챠트로 보여주며, 프로젝트 소스 및 파일을 볼 수 있는 Wrokspace와 최근 소스의 변경 내역, 그리고 최근 테스트 결과에 대한 상세 내용을 볼 수 있다.

참조:
Maven Directory Layout :: 2009/06/11 23:26
Maven의 표준 디렉토리 레이아웃이다.
| Directory | Description |
|---|---|
| src/main/java | Application/Library sources |
| src/main/resources | Application/Library resources |
| src/main/filters | Resource filter files |
| src/main/assembly | Assembly descriptors |
| src/main/config | Configuration files |
| src/main/webapp | Web application sources |
| src/test/java | Test sources |
| src/test/resources | Test resources |
| src/test/filters | Test resource filter files |
| src/site | Site |
| LICENSE.txt | Project's license |
| README.txt | Project's readme |
Maven Build Lifecycle :: 2009/06/11 23:05
Maven에서 빌드 과정의 하나의 단계를 phase라고 한다. 그리고 프로젝트 빌드에 관여하는 일련의 순차적인 phase들 집합을 Maven의 빌드 생명주기(build lifecycle)이라고 한다.
한편 플러그인 골(goal)은 하나의 phase에 딸려있게 되며, 각 phase들은 0개 이상의 goal들을 가진다. Maven이 빌드 과정에서 각 phase들을 이동함에 따라, 각 특정 phase에 딸려있는 goal들을 수행하게 된다.
Default Build Lifecycle
참조:
http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
http://www.sonatype.com/books/maven-book/reference/simple-project-sect-lifecycle.html
한편 플러그인 골(goal)은 하나의 phase에 딸려있게 되며, 각 phase들은 0개 이상의 goal들을 가진다. Maven이 빌드 과정에서 각 phase들을 이동함에 따라, 각 특정 phase에 딸려있는 goal들을 수행하게 된다.
![]() |
IDEA's Maven Lifecycle ![]() |
Default Build Lifecycle
| Phase | Description |
|---|---|
| validate | validate the project is correct and all necessary information is available. |
| initialize | initialize build state, e.g. set properties or create directories. |
| generate-sources | generate any source code for inclusion in compilation. |
| process-sources | process the source code, for example to filter any values. |
| generate-resources | generate resources for inclusion in the package. |
| process-resources | copy and process the resources into the destination directory, ready for packaging. |
| compile | compile the source code of the project. |
| process-classes | post-process the generated files from compilation, for example to do bytecode enhancement on Java classes. |
| generate-test-sources | generate any test source code for inclusion in compilation. |
| process-test-sources | process the test source code, for example to filter any values. |
| generate-test-resources | create resources for testing. |
| process-test-resources | copy and process the resources into the test destination directory. |
| test-compile | compile the test source code into the test destination directory |
| process-test-classes | post-process the generated files from test compilation, for example to do bytecode enhancement on Java classes. For Maven 2.0.5 and above. |
| test | run tests using a suitable unit testing framework. These tests should not require the code be packaged or deployed. |
| prepare-package | perform any operations necessary to prepare a package before the actual packaging. This often results in an unpacked, processed version of the package. (Maven 2.1 and above) |
| package | take the compiled code and package it in its distributable format, such as a JAR. |
| pre-integration-test | perform actions required before integration tests are executed. This may involve things such as setting up the required environment. |
| integration-test | process and deploy the package if necessary into an environment where integration tests can be run. |
| post-integration-test | perform actions required after integration tests have been executed. This may including cleaning up the environment. |
| verify | run any checks to verify the package is valid and meets quality criteria. |
| install | install the package into the local repository, for use as a dependency in other projects locally. |
| deploy | done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects. |
참조:
http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
http://www.sonatype.com/books/maven-book/reference/simple-project-sect-lifecycle.html
Commons Configuration Wrapper :: 2009/06/03 22:47
Apache Commons Configuration은 Java 어플리케이션에서 System Properties를 비롯해 Propreties, XML, INI 파일 등 다양한 소스로부터 설정 데이터를 읽어올 수 있는 인터페이스를 제공한다. 또한 이러한 다양한 소스들은 ConfigurationFactory 또는 CompositeConfiguration을 사용하여 하나로 혼합되어 질 수 있다.
코드의 편의성을 위해 Configuraion을 Wrapping한 유틸리티 클래스를 만들어 보자.
1. Configuration Manager (ConfigManager.java)
- Singleton 패턴으로 ConfigurationFactory를 사용하여 디폴트 설정 디스크립터(config.xml)에 명시된 설정 데이터를 로딩한다.
2. Configuration Wrapper Utility (Config.java)
- 각 데이터 타입별로 설정 값을 read-only로 읽어오는 static 메소드들을 가지고 있다.
3. Configuration Descriptor (config.xml)
- ConfigurationFactory에서 사용되는 설정 디스크립터로 로딩할 설정 객체들을 명시한다.
- 이 파일은 지정된 파일 시스템에서 먼저 찾고, 없을 경우 클래스패스에서 찾는다.
- 아래 예에서 system properties와 config.properties 파일에 있는 모든 프로퍼티들을 로딩한다.
- 이 파일안에 지정된 순서를 우선순으로 하여 설정 값을 찾는다. 즉 system properties에서 값이 있으면 그 값을 사용하고, 만약 없으면 config.properties 파일에서 설정 값을 찾는다. (system properties가 config.properties를 override 함).
4. Configuration Properties (config.properties)
- 설정 디스크립터와 마찬가지로 파일 시스템 위치에서 파일을 먼저 찾고, 없을 경우 클래스패스에서 찾는다.
5. TestCase (ConfigTest.java)
- 아래와 같이 Config의 설정 값에 맞는 데이터 타입별 메소드를 호출하여 사용한다.
코드의 편의성을 위해 Configuraion을 Wrapping한 유틸리티 클래스를 만들어 보자.
1. Configuration Manager (ConfigManager.java)
- Singleton 패턴으로 ConfigurationFactory를 사용하여 디폴트 설정 디스크립터(config.xml)에 명시된 설정 데이터를 로딩한다.
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class ConfigManager {
private static final Log log = LogFactory.getLog(ConfigManager.class);
private static Configuration config;
private static ConfigManager instance;
private ConfigManager() {
loadConfig();
}
public static ConfigManager getInstance() {
if (instance == null) {
synchronized (ConfigManager.class) {
instance = new ConfigManager();
}
}
return instance;
}
public void loadConfig() {
loadConfig("config.xml");
}
public void loadConfig(String configFileName) {
try {
ConfigurationFactory factory = new ConfigurationFactory(
configFileName);
config = factory.getConfiguration();
log.info("Configuration loaded: " + configFileName);
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
throw new RuntimeException("Configuration loading error: "
+ configFileName, ex);
}
}
public Configuration getConfig() {
return config;
}
public boolean isEmpty() {
return config.isEmpty();
}
public void clear() {
config.clear();
}
} |
2. Configuration Wrapper Utility (Config.java)
- 각 데이터 타입별로 설정 값을 read-only로 읽어오는 static 메소드들을 가지고 있다.
public class Config {
public static String getString(String key) {
return ConfigManager.getInstance().getConfig().getString(key);
}
public static String getString(String key, String def) {
return ConfigManager.getInstance().getConfig().getString(key, def);
}
public static int getInt(String key) {
return ConfigManager.getInstance().getConfig().getInt(key);
}
public static int getInt(String key, int def) {
return ConfigManager.getInstance().getConfig().getInt(key, def);
}
public static long getLong(String key) {
return ConfigManager.getInstance().getConfig().getLong(key);
}
public static long getLong(String key, long def) {
return ConfigManager.getInstance().getConfig().getLong(key, def);
}
public static float getFloat(String key) {
return ConfigManager.getInstance().getConfig().getFloat(key);
}
public static float getFloat(String key, float def) {
return ConfigManager.getInstance().getConfig().getFloat(key, def);
}
public static double getDouble(String key) {
return ConfigManager.getInstance().getConfig().getDouble(key);
}
public static double getDouble(String key, double def) {
return ConfigManager.getInstance().getConfig().getDouble(key, def);
}
public static boolean getBoolean(String key) {
return ConfigManager.getInstance().getConfig().getBoolean(key);
}
public static boolean getBoolean(String key, boolean def) {
return ConfigManager.getInstance().getConfig().getBoolean(key, def);
}
public static String[] getStringArray(String key) {
return ConfigManager.getInstance().getConfig().getStringArray(key);
}
}
|
3. Configuration Descriptor (config.xml)
- ConfigurationFactory에서 사용되는 설정 디스크립터로 로딩할 설정 객체들을 명시한다.
- 이 파일은 지정된 파일 시스템에서 먼저 찾고, 없을 경우 클래스패스에서 찾는다.
- 아래 예에서 system properties와 config.properties 파일에 있는 모든 프로퍼티들을 로딩한다.
- 이 파일안에 지정된 순서를 우선순으로 하여 설정 값을 찾는다. 즉 system properties에서 값이 있으면 그 값을 사용하고, 만약 없으면 config.properties 파일에서 설정 값을 찾는다. (system properties가 config.properties를 override 함).
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<system />
<properties fileName="config.properties" />
</configuration> |
4. Configuration Properties (config.properties)
- 설정 디스크립터와 마찬가지로 파일 시스템 위치에서 파일을 먼저 찾고, 없을 경우 클래스패스에서 찾는다.
# For Test test.string=Hello test.int=1234 test.long=12345678 test.float=1234.56 test.double=12345678.99 test.boolean=true test.stringArray=qwer,asdf,zxcv |
5. TestCase (ConfigTest.java)
- 아래와 같이 Config의 설정 값에 맞는 데이터 타입별 메소드를 호출하여 사용한다.
import junit.framework.TestCase;
public class ConfigTest extends TestCase {
public void testGetString() {
assertEquals("Hello", Config.getString("test.string"));
assertEquals("Hi", Config.getString("test.string2", "Hi"));
}
public void testGetInt() {
assertEquals(1234, Config.getInt("test.int"));
assertEquals(100, Config.getInt("test.int2", 100));
}
public void testGetLong() {
assertEquals(12345678, Config.getLong("test.long"));
assertEquals(100, Config.getLong("test.long2", 100));
}
public void testGetFloat() {
assertEquals(1234.56F, Config.getFloat("test.float"), 0);
assertEquals(100.22F, Config.getFloat("test.float2", 100.22F), 0);
}
public void testGetDouble() {
assertEquals(12345678.99, Config.getDouble("test.double"), 0);
assertEquals(100.22, Config.getDouble("test.double2", 100.22), 0);
}
public void testGetBoolean() {
assertTrue(Config.getBoolean("test.boolean"));
assertFalse(Config.getBoolean("test.boolean2", false));
}
public void testGetStringArray() {
String[] values = Config.getStringArray("test.stringArray");
assertEquals(3, values.length);
assertEquals("qwer", values[0]);
assertEquals("asdf", values[1]);
assertEquals("zxcv", values[2]);
}
} |
Tomcat Servlet Configuration :: 2009/06/03 00:15
이제는 프레임워크에 묻혀 좀처럼 만져볼 일이 없는 서블릿... Apache Tomcat의 기본 서블릿 설정 방법이다. 아래처럼 톰캣의 ContainerServlet인 InvokerServlet을 설정하면 개별 서블릿 설정을 하지 않아도 URL 주소로 서블릿을 호출할 수 있다.
1. web.xml 에 다음과 같이 InvokerServlet을 설정한다.
- <url-pattern> 값은 필요에 따라 적절히 수정할 것
- Tomcat 4.1에서 <servlet-name> 오류 시 이름을 invoker-servlet 등 다른 값으로 변경할 것
2. Tomcat Context 설정에 서블리 리로딩 속성을 추가한다.
- ${TOMCAT_HOME}/conf/context.xml (또는 server.xml)의 Context 요소에 reloadable="true" 속성을 추가한다. (Tomcat 6.0인 경우는 privileged="true" 속성도 추가해야 함).
- /WEB-INF/classes와 /WEB-INF/lib를 모니터링하여 변경이 발생하면 자동으로 웹어플리케이션을 리로딩하는 속성이다. (성능이슈가 있음).
3. 서블릿 샘플 소스

1. web.xml 에 다음과 같이 InvokerServlet을 설정한다.
- <url-pattern> 값은 필요에 따라 적절히 수정할 것
- Tomcat 4.1에서 <servlet-name> 오류 시 이름을 invoker-servlet 등 다른 값으로 변경할 것
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>org.apache.catalina.servlets.InvokerServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
...
<servlet-mapping>
<servlet-name>invoker</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping> |
2. Tomcat Context 설정에 서블리 리로딩 속성을 추가한다.
- ${TOMCAT_HOME}/conf/context.xml (또는 server.xml)의 Context 요소에 reloadable="true" 속성을 추가한다. (Tomcat 6.0인 경우는 privileged="true" 속성도 추가해야 함).
- /WEB-INF/classes와 /WEB-INF/lib를 모니터링하여 변경이 발생하면 자동으로 웹어플리케이션을 리로딩하는 속성이다. (성능이슈가 있음).
- Tomcat 4.1 인 경우: server.xml 에 <Context ... reloadable="true">
- Tomcat 5.5 인 경우: context.xml 에 <Context ... reloadable="true">
- Tomcat 6.0 인 경우: context.xml 에 <Context ... reloadable="true" privileged="true">
3. 서블릿 샘플 소스
- HelloServlet.java
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<body>Hello, Servlet!</body>");
out.println("</html>");
out.close();
}
} |
- web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app id="WebApp">
-->
<web-app id="WebApp" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Test Web</display-name>
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>org.apache.catalina.servlets.InvokerServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>hello-servlet</servlet-name>
<servlet-class>servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>invoker</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello-servlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app> |
- 실행결과1: http://localhost:8080/testweb/servlet/servlet.HelloServlet 주소로 접근한 경우

- 실행결과2: http://localhost:8080/testweb/HelloServlet 주소로 접근한 경우

JSTL 1.0, 1.1, 1.2 :: 2009/06/02 12:34
JavaSerer Pages Standard Tag Library (JSTL)의 버전별 Servlet, JSP 표준 스펙 및 지원되는 Tomcat 서버 버전은 다음과 같다.
JSTL 1.0 태그 선언 방법
JSTL 1.1 태그 선언 방법
참고: JSTL Quick Reference
| JSTL | Servlet | JSP | Tomcat | Jakarta Taglibs |
|---|---|---|---|---|
| JSTL 1.0 | 2.3 | 1.2 | 4.1 | standard-1.0 |
| JSTL 1.1 | 2.4 | 2.0 | 5.5 | standard-1.1 |
| JSTL 1.2 | 2.5 | 2.1 | 6.0 | - |
JSTL 1.0 태그 선언 방법
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jstl/fmt" prefix="fmt"%> <%@ taglib uri="http://java.sun.com/jstl/sql" prefix="sql"%> |
JSTL 1.1 태그 선언 방법
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%> |
참고: JSTL Quick Reference
Message Digest 유틸리티 :: 2009/06/01 23:22
암호화 해쉬 함수(cryptographic hash function)는 임의의 데이터 블럭을 입력받아 하나의 고정된 길이의 비트 스트링(해쉬 값)을 반환하는 알고리즘이다. 여기서 인코딩된 입력 데이터는 종종 "message"라고 불리우며, 반환된 해쉬 값은 "message digest" (또는 "digest")라고 불리운다.
다음은 특정 파일 및 데이터에 대한 해쉬 값을 얻는 Java 소스이다.
실행 결과:
다음은 특정 파일 및 데이터에 대한 해쉬 값을 얻는 Java 소스이다.
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
public class DigestUtil {
// 128-bit (16-byte) digest: MD5, md5
public static final String ALGORITHM_MD5 = "MD5";
// 160-bit (20-byte) digest: SHA-1, sha-1, SHA1, sha1, SHA, sha
public static final String ALGORITHM_SHA1 = "SHA-1";
// 256-bit (32-byte) digest: SHA-256, sha-256
public static final String ALGORITHM_SHA256 = "SHA-256";
// 512-bit (64-byte) digest: SHA-512, sha-512
public static final String ALGORITHM_SHA512 = "SHA-512";
public static byte[] digest(File file, String algorithm) {
try {
final int BUFF_SIZE = 8192;
BufferedInputStream bis =
new BufferedInputStream(new FileInputStream(file));
MessageDigest md = MessageDigest.getInstance(algorithm);
DigestInputStream in = new DigestInputStream(bis, md);
int read;
byte[] buff = new byte[BUFF_SIZE];
do {
read = in.read(buff, 0, BUFF_SIZE);
} while (read == BUFF_SIZE);
md = in.getMessageDigest();
in.close();
return md.digest();
} catch (Exception ex) {
throw new RuntimeException("Message digest error: "
+ file.getAbsolutePath(), ex);
}
}
public static String digestToBase64(File file, String algorithm) {
return Base64.encode(digest(file, algorithm));
}
public static byte[] digest(byte[] data, String algorithm) {
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(data);
return md.digest();
} catch (Exception ex) {
throw new RuntimeException("Message digest error", ex);
}
}
public static String digestToBase64(byte[] data, String algorithm) {
return Base64.encode(digest(data, algorithm));
}
public static void main(String args[]) throws Exception {
byte[] result = digest("abc123".getBytes(), ALGORITHM_MD5);
System.out.println(result.length);
System.out.println(Base64.encode(result));
result = digest("abc123".getBytes(), ALGORITHM_SHA1);
System.out.println(result.length);
System.out.println(Base64.encode(result));
result = digest("abc123".getBytes(), ALGORITHM_SHA256);
System.out.println(result.length);
System.out.println(Base64.encode(result));
result = digest("abc123".getBytes(), ALGORITHM_SHA512);
System.out.println(result.length);
System.out.println(Base64.encode(result));
System.out.println("----- file ----");
result = digest(new File("test.txt"), ALGORITHM_SHA1);
System.out.println(result.length);
System.out.println(Base64.encode(result));
}
} |
실행 결과:
16 6ZoYxCjLONXyYIU2eJIuAw== 20 Y2fEjdGT1W6nsLqtJbGUVeUp9e4= 32 bKE9UspwyIPg8LsQHkJaiehiTeUdstI5JZOvaoQRgJA= 64 xwtd2ev7b1HQnUEytxcMnSB1CnhS8AaA9lZY8DEOgQBW5nY8NMmgCw6UAHb1RJXBafwjAszrMSA5JxxDRp UH3A== ----- file ---- 20 Y2fEjdGT1W6nsLqtJbGUVeUp9e4= |



