'개발/자바'에 해당되는 글 41건

  1. 2009.09.15 자바 암호화
  2. 2009.09.14 자바 정리 참고 1 1
  3. 2009.09.08 Java DES/TripleDES 암호화 2
  4. 2009.05.22 JMeter
  5. 2009.04.14 JAAS(Java Authentication and Authorization Service) 개요
  6. 2009.03.17 [java][성능] JVM GC와 메모리 튜닝
  7. 2009.03.17 미리 보는 미래 개발환경「자바SE 6
  8. 2009.03.17 JDK 5.0 에서의 새로운 변화
  9. 2009.02.05 주민등록 검사의 원리
  10. 2009.02.04 jaxp를 이용한 xmlutil
2009. 9. 15. 13:39

자바 암호화




자바는 친절하게도 암호화 및 메시지 검증 코드를 구현해주는 클래스를 제공해준다.
JCE(Java Cryptography Extension)란 이름의 프레임워크가 바로 그놈이다.
J2SE 1.4 이후부터는 JCE 1.2.2가 기본적으로 포함되어 있어서 별다른 라이브러리를 추가해주지 않아도 사용할 수 있다.
초기 버젼인 JCE 1.2는 미국 보안법(?)인가에 걸려서 국내에서는 사용할 수가 없었다. (무기밀매와 똑같은 처벌을 한다는 소문이..)
JCE 1.2.1이 나오면서 제한이 풀어져서, 세계적으로 많이(?) 사용하게 되었다.
그런데 이 JCE 1.2.1 버전의 자체 디지털 서명이 2005년 7월 27일쯤인가 만료가 되서, 2005년도에 파란을 한번 일으킨적도 있다.
(그럼 만료기간을 어떻게 알수 있을까? 쉽게 알려면, 자신이 사용하는 파일의 정보를 보면 된다.
자신이 사용하는 자바 관련 디렉토리에서 jce.jar를 풀어보면 META-INF에 JCE_RSA.RSA 파일이 있을것이다. 이 파일을 보면 알 수 있는데, 윈도우 환경에서 이 파일의 확장자를 "p7s"로 변경하면 열어볼 수 있다.)


이 JCE를 사용하지 않아도, 자체적으로 구현해서 마음껏 암호학의 세계를 여행할 수 있지만, 시간에 쫓기는 분들을 위해서 간단히 사용법을 알아보도록 하자.

암호화에는 크게 블럭 암호화(block encryption)와 스트림 암호화(stream encryption)가 있는데, 여기서는 가장(?) 많이 쓰이는 블럭 암호화에 대해서 알아보도록 하겠다.
블럭 암호화는 말 그대로 데이터를 정해진 블럭으로 나눈후 해당 블럭을 암호화하는것이다.
대표적으로 DES/3DES/AES/SEED 등이 있다.

DES(Data Encryption Standard)는 Lucifer를 보완하여 IBM에서 개발한 블럭암호 알고리즘이다.
64비트 입력 블럭을 56비트 비밀키를 사용하여 암호화하는 알고리즘이다.
전세계적으로 널리 사용되었다가, 56비트라는 짧은 키(key)로 인해 안전하지 않다고 보는 견해가 많아져서, 요즘은  AES한테 밀리는 추세이다.

3DES(Triple Data Encryption Standard)는 DES의 단점을 보완하기 위해서 기존의 DES 방식을 3번 적용(암호화->복호화->암호화)시킨것으로 그 과정에 따라서 56비트의 배수로 암호화 복잡도가 증가한다고 한다.
이 3번의 암복호화 즉, Encryption->Decryption->Encryption을 첫글자를 따서 DESede라 명칭하기도한다.

AES(Advanced Encryption Standard)는 현재 미국 정부 표준으로 지정된 블럼 암호화 알고리즘으로서, DES를 대체하고 있다.
키(key)의 크기는 128, 160, 190, 224, 256비트를 사용할 수 있으며, 현재 미국 표준으로 인정받은 것은 128비트이다.
(JCE에서 제공하는것도 128비트밖에 안될지도...)

SEED는 한국정보보호진흥원을 중심으로 국내 암호 전문가들이 참여하여 만든, 순수 국내기술 블럭암호화 알고리즘이다.
(SEED는 다음에 한번 구현에해보기도 하고 오늘은 다루지 않겠다.)

블럭 암호화를 하기 위해서는 당연히 원문(plain text)이 있어야하고, 암화하 하기 위한 키(key)가 있어야한다.
그리고 블럭 암호화 운용모드에 따라서 IV(Initialization Vector)가 필요하기도 하다.
이 키(key)에 따라서 대칭키 암호화와 비대칭키 암호화로 나눌 수 있다.
대칭키 암호화는 암호화키와 복호화키가 동일하다. 속도가 빠른 장점이 있으니 키(key)를 분배하기 어렵다.(키가 누출되면 암호화 자체가 의미가 없어진다.)
비대칭키 암호화는 키(key) 분배 문제때문에 개발되었는데, 암호화와 복호화키가 다르다. 즉, 공개키(PublicKey), 개인키(PrivateKey)가 따로 생성된다.
간단히 설명하자면, 멀리 떨어진 시스템에서 사용할때에, 이 2가지를 결합하여 사용하고 있다.
대칭키 암호화에 사용할 키(key)를 생성한다음, 비대칭키 암호화를 이용해서 그 키를 분배한다.
키가 안전하게 분배되면 대칭키 암호화를 이용해서 서로 암호화된 문서를 주고 받고 하는것이다.
(비대칭키 암호화가 대칭키 암호화보다 느리기 때문에 키분배에만 사용한다.)
앞에 설명한 DEs/3DES/AES/SEED는 대칭키 암호화 알고리즘이다. 비대칭키 암호화 알고리즘은 RSA(Rivest Shamir Adleman)가 있다.(사람 이름 첫글자를 딴것임)

이제 한번 구현해보도록 하자.
(바이트들을 화면에 출력하기(?) 위해 ByteUtils 클래스를 사용하겠다. 첨부파일을 참조하도록 하자)

1. 암호화에 사용할 키(key) 만들기
 - 키를 만드는 방법은 랜덤하게 동적으로 만드는 방법과, 정해진 키를 읽어와서 만드는 방법이 있다. 두 기능을 하는 메소드를 만들어보자.
01.package test.cipher;
02.  
03.import java.security.InvalidKeyException;
04.import java.security.Key;
05.import java.security.NoSuchAlgorithmException;
06.import java.security.spec.InvalidKeySpecException;
07.import java.security.spec.KeySpec;
08.  
09.import javax.crypto.Cipher;
10.import javax.crypto.KeyGenerator;
11.import javax.crypto.SecretKey;
12.import javax.crypto.SecretKeyFactory;
13.import javax.crypto.spec.DESKeySpec;
14.import javax.crypto.spec.DESedeKeySpec;
15.import javax.crypto.spec.IvParameterSpec;
16.import javax.crypto.spec.SecretKeySpec;
17.  
18.import kr.kangwoo.util.ByteUtils;
19.import kr.kangwoo.util.StringUtils;
20.  
21.public class CipherTest {
22.      
23.    /**
24.     * <P>해당 알고리즘에 사용할 비밀키(SecretKey)를 생성한다.</P>
25.     
26.     * @return 비밀키(SecretKey)
27.     */
28.    public static Key generateKey(String algorithm) throws NoSuchAlgorithmException {
29.        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
30.        SecretKey key = keyGenerator.generateKey();
31.        return key;
32.    }
33.      
34.    /**
35.     * <P>주어진 데이터로, 해당 알고리즘에 사용할 비밀키(SecretKey)를 생성한다.</P>
36.     
37.     * @param algorithm DES/DESede/TripleDES/AES
38.     * @param keyData
39.     * @return
40.     */
41.    public static Key generateKey(String algorithm, byte[] keyData) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
42.        String upper = StringUtils.toUpperCase(algorithm);
43.        if ("DES".equals(upper)) {
44.            KeySpec keySpec = new DESKeySpec(keyData);
45.            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
46.            SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
47.            return secretKey;
48.        } else if ("DESede".equals(upper) || "TripleDES".equals(upper)) {
49.            KeySpec keySpec = new DESedeKeySpec(keyData);
50.            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
51.            SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
52.            return secretKey;
53.        } else {
54.            SecretKeySpec keySpec = new SecretKeySpec(keyData, algorithm);
55.            return keySpec;
56.        }
57.    }
58.}

 위 코드는 일단 정상(?) 작동하는데, 맞게 구현하지는 아직 잘 모르겠다. (내공이 부족하여 혹시 틀린부분이 있으면 지적바란다.)

2. DES 암호화/복호화 해보기
 - DES 암호화/복화화를 하기위해선 Cipher 클래스를 사용하면 된다.
 - Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding"); 이런씩으로 "암호화알고리즘/운용모드/패딩"으로 사용하면 된다.
 - DES는 암호화키가 64비트이므로 64비트 키를 생성하자
   (예리한 분은 여기서 의문을 가질것이다. 위~에서 설명할때 DES는 56비트 키라고 했는데, 왜 갑자기 여기서는 64비트라고 우기는가에 대해서...
    설명하자면, 64비트의 키(외부키) 중 56비트는 실제의 키(내부키)가 되고 나머지 8비트는 거사용(?) 비트로 사용된다고 한다.)
01.public static void main(String[] args) throws Exception {
02.    Key key = generateKey("DES", ByteUtils.toBytes("68616e6765656e61", 16));
03.    String transformation = "DES/ECB/NoPadding";
04.    Cipher cipher = Cipher.getInstance(transformation);
05.    cipher.init(Cipher.ENCRYPT_MODE, key);
06.      
07.    String str = "hello123";
08.    byte[] plain = str.getBytes();
09.    byte[] encrypt = cipher.doFinal(plain);
10.    System.out.println("원문 : " + ByteUtils.toHexString(plain));
11.    System.out.println("암호 : " + ByteUtils.toHexString(encrypt));
12.      
13.    cipher.init(Cipher.DECRYPT_MODE, key);
14.    byte[] decrypt = cipher.doFinal(encrypt);
15.    System.out.println("복호 : " + ByteUtils.toHexString(decrypt));
16.}

* 실행 결과
원문 : 68656c6c6f313233
암호 : 51d6aa8bcc176819
복호 : 68656c6c6f313233

 실행해보면 암호화/복호화가 잘되는것을 알 수 있다.
 DES는 암호화 알고리즘이고, ECB는 뭘까? 운용모드라고 했는데,  http://blog.kangwoo.kr/13 여기에 퍼온글이 있으니 참조하기 바란다.
 패딩(padding)은 말 그대로 패딩인데, 번역하면 채워넣기, 모자란만큼 채워넣는 역할을 한다. DES는 블럭 암호화 알고리즘이다.
 그래서 암호화 할라면 블럭이 필요한데, DES 경우 64비트가 한 블럭을 형성한다. 그런데 입력한 데이터가 64비트가 안된다면 어떻게 될까?
 궁금하면 "hello123" 을 "hello"으로 바꾼다음 실행해보자. 아마 다음과 같은 에러가 발생할 것이다.
 
* 실행 결과
 Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length not multiple of 8 bytes

 입력한 데이터가 8 bytes 즉, 64비트가 아니니 배째라는것이다.
 그러면 정상작동을 하기위해서는 블럭크기의 배수만큼 나머지 데이터를 채워줘야하는데 그 역할을 하는게 패딩이다.
 "DES/ECB/NoPadding"을 "DES/ECB/PKCS5Padding"을 바꾼다음 실행해보자.
 이제 데이터가 64비트가 아니어도 정상작동한다. 그래서 귀찮으면 가능한한 패딩을 해주게 하면 좋다.
 (운용 모드가 OFB/CFB일 경우 NoPadding을 사용해도 상관없다. IV를 이용해서 처리할때 필요가 없어지기때문이다. 믿거나 말거나)


3. DESede 암호화/복호화 해보기
 - DES와 별반 차이 없다. 키 크기가 64비트 * 3 = 192비트로 늘어난것밖에 없다.
01.public static void main(String[] args) throws Exception {
02.    Key key = generateKey("DESede", ByteUtils.toBytes("696d697373796f7568616e6765656e61696d697373796f75", 16));
03.    String transformation = "DESede/ECB/PKCS5Padding";
04.    Cipher cipher = Cipher.getInstance(transformation);
05.    cipher.init(Cipher.ENCRYPT_MODE, key);
06.      
07.    String str = "hello123";
08.    byte[] plain = str.getBytes();
09.    byte[] encrypt = cipher.doFinal(plain);
10.    System.out.println("원문 : " + ByteUtils.toHexString(plain));
11.    System.out.println("암호 : " + ByteUtils.toHexString(encrypt));
12.      
13.    cipher.init(Cipher.DECRYPT_MODE, key);
14.    byte[] decrypt = cipher.doFinal(encrypt);
15.    System.out.println("복호 : " + ByteUtils.toHexString(decrypt));
16.}

* 실행 결과
원문 : 68656c6c6f313233
암호 : ad44691609cb1e017378631303581279
복호 : 68656c6c6f313233

4. AES 암호화/복호화 해보기
 - DES와 별반 차이 없다. 키 크기가 128비트인것을 제외하면 말이다.
01.public static void main(String[] args) throws Exception {
02.    Key key = generateKey("AES", ByteUtils.toBytes("696d697373796f7568616e6765656e61", 16));
03.    String transformation = "AES/ECB/PKCS5Padding";
04.    Cipher cipher = Cipher.getInstance(transformation);
05.    cipher.init(Cipher.ENCRYPT_MODE, key);
06.      
07.    String str = "hello123";
08.    byte[] plain = str.getBytes();
09.    byte[] encrypt = cipher.doFinal(plain);
10.    System.out.println("원문 : " + ByteUtils.toHexString(plain));
11.    System.out.println("암호 : " + ByteUtils.toHexString(encrypt));
12.      
13.    cipher.init(Cipher.DECRYPT_MODE, key);
14.    byte[] decrypt = cipher.doFinal(encrypt);
15.    System.out.println("복호 : " + ByteUtils.toHexString(decrypt));
16.}

* 실행 결과
원문 : 68656c6c6f313233
암호 : d5c5e1ffb734b610679f36c0e535fe39
복호 : 68656c6c6f313233
5. IV 사용하기.
- 블록 암호의 운용 모드(Block engine modes of operation)가 CBC/OFB/CFB를 사용할 경우에는 Initialization Vector(IV)를 설정해줘야한다. 왜냐면 사용하기 때문이다. AES 암호화/복호화 코드에서 운용 모드가 CBC로 변경해보자.
01.public static void main(String[] args) throws Exception {
02.    Key key = generateKey("AES", ByteUtils.toBytes("696d697373796f7568616e6765656e61", 16));
03.    String transformation = "AES/CBC/PKCS5Padding";
04.    Cipher cipher = Cipher.getInstance(transformation);
05.    cipher.init(Cipher.ENCRYPT_MODE, key);
06.      
07.    String str = "hello123";
08.    byte[] plain = str.getBytes();
09.    byte[] encrypt = cipher.doFinal(plain);
10.    System.out.println("원문 : " + ByteUtils.toHexString(plain));
11.    System.out.println("암호 : " + ByteUtils.toHexString(encrypt));
12.      
13.    cipher.init(Cipher.DECRYPT_MODE, key);
14.    byte[] decrypt = cipher.doFinal(encrypt);
15.    System.out.println("복호 : " + ByteUtils.toHexString(decrypt));
16.}

* 실행 결과 원문 : 68656c6c6f313233
암호 : 92e4fa9add0d4d5a07954207890e5b5c
Exception in thread "main" java.security.InvalidAlgorithmParameterException: Parameters missing
...생략...

실행해보면 암호화는 되는데, 복호화도중 파라메터가 없다고 에러가 발생한다. 예상한데로라면 암호화할때도 에러가 나야하는데, 불행히도 암호화는 정상적으로 작동한다. 왜냐면 암호화할때는 IV를 지정해주지 않으면, 자동적으로 랜덤 IV를 만들어서 사용해버린다.
정말인지 테스트해보자.
01.public static void main(String[] args) throws Exception {
02.    Key key = generateKey("AES", ByteUtils.toBytes("696d697373796f7568616e6765656e61", 16));
03.    String transformation = "AES/CBC/PKCS5Padding";
04.    Cipher cipher = Cipher.getInstance(transformation);
05.    cipher.init(Cipher.ENCRYPT_MODE, key);
06.      
07.    String str = "hello123";
08.    byte[] plain = str.getBytes();
09.    byte[] encrypt = cipher.doFinal(plain);
10.    System.out.println("원문 : " + ByteUtils.toHexString(plain));
11.    System.out.println("암호 : " + ByteUtils.toHexString(encrypt));
12.      
13.    cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(cipher.getIV()));
14.    byte[] decrypt = cipher.doFinal(encrypt);
15.    System.out.println("복호 : " + ByteUtils.toHexString(decrypt));
16.}

* 실행 결과
원문 : 68656c6c6f313233
암호 : 26c7d1d26c142de0a3b82f7e8f90860a
복호 : 68656c6c6f313233
new IvParameterSpec(cipher.getIV())을 이용해서 자동 생성된 IV를 초기화 파라메터를 넘겨주니 정상 작동한다.
실제 사용할때는 암호화 복호화가 따로(?) 작동하니 이렇게 구현하는건 불가능할것이다. IV의 크기는 블럭크기와 동일하므로 키처럼 생성해서 잘 관리하면 되겠다.
01.public static void main(String[] args) throws Exception {
02.    Key key = generateKey("AES", ByteUtils.toBytes("696d697373796f7568616e6765656e61", 16));
03.    byte[] iv = ByteUtils.toBytes("26c7d1d26c142de0a3b82f7e8f90860a", 16);
04.    String transformation = "AES/CBC/PKCS5Padding";
05.    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
06.    Cipher cipher = Cipher.getInstance(transformation);
07.    cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
08.      
09.    String str = "hello123";
10.    byte[] plain = str.getBytes();
11.    byte[] encrypt = cipher.doFinal(plain);
12.    System.out.println("원문 : " + ByteUtils.toHexString(plain));
13.    System.out.println("암호 : " + ByteUtils.toHexString(encrypt));
14.      
15.    cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
16.    byte[] decrypt = cipher.doFinal(encrypt);
17.    System.out.println("복호 : " + ByteUtils.toHexString(decrypt));
18.}

* 실행 결과
원문 : 68656c6c6f313233
암호 : 1876c3ae98760ccf1821ea46fc9ce761
복호 : 68656c6c6f313233
6. 파일을 AES 암호화/복호화 해보기
 - 이번에 파일을 읽어와서 암호화/복호화를 해보자. JavaIO를 다룰 수 있으면 간단히 구현할 수 있을것이다.
01.public static void main(String[] args) throws Exception {
02.      
03.    Key key = generateKey("AES", ByteUtils.toBytes("696d697373796f7568616e6765656e61", 16));
04.    String transformation = "AES/ECB/PKCS5Padding";
05.    Cipher cipher = Cipher.getInstance(transformation);
06.    cipher.init(Cipher.ENCRYPT_MODE, key);
07.    File plainFile = new File("c:/plain.txt");
08.    File encryptFile = new File("c:/encrypt.txt");
09.    File decryptFile = new File("c:/decrypt.txt");
10.      
11.    BufferedInputStream input = null;
12.    BufferedOutputStream output = null;
13.    try {
14.        input = new BufferedInputStream(new FileInputStream(plainFile));
15.        output = new BufferedOutputStream(new FileOutputStream(encryptFile));
16.          
17.        int read = 0;
18.        byte[] inBuf = new byte[1024];
19.        byte[] outBuf = null;
20.        while ((read = input.read(inBuf)) != -1) {
21.            outBuf = cipher.update(inBuf, 0, read);
22.            if (outBuf != null) {
23.                output.write(outBuf);
24.            }
25.        }
26.        outBuf = cipher.doFinal();
27.        if (outBuf != null) {
28.            output.write(outBuf);
29.        }
30.    } finally {
31.        if (output != null) try {output.close();} catch(IOException ie) {}
32.        if (input != null) try {input.close();} catch(IOException ie) {}
33.    }
34.              
35.    cipher.init(Cipher.DECRYPT_MODE, key);
36.    try {
37.        input = new BufferedInputStream(new FileInputStream(encryptFile));
38.        output = new BufferedOutputStream(new FileOutputStream(decryptFile));
39.          
40.        int read = 0;
41.        byte[] inBuf = new byte[1024];
42.        byte[] outBuf = null;
43.        while ((read = input.read(inBuf)) != -1) {
44.            outBuf = cipher.update(inBuf, 0, read);
45.            if (outBuf != null) {
46.                output.write(outBuf);
47.            }
48.        }
49.        outBuf = cipher.doFinal();
50.        if (outBuf != null) {
51.            output.write(outBuf);
52.        }
53.    } finally {
54.        if (output != null) try {output.close();} catch(IOException ie) {}
55.        if (input != null) try {input.close();} catch(IOException ie) {}
56.    }
57.}

"plain.txt"란 파일을 만들어 놓고 실행하면, "encrypt.txt"와 "decrypt.txt" 파일이 생성될것이다. 제대로 암호화 복호화되었는지 비교해보자.
7. 비밀번호 암호화
 - 대부분의 사이트에서 비밀번호를 암호화한다.(불행히도 안하는곳도 많다.) 위에서 배운 블럭암호화 알고리즘으로 암호화를 한다면, 사이트 관리자들은 비밀번호를 다 알 수 있을것이다. (키를 해당 사이트에서 관리하므로, 키를 알면 복호화가 가능하다.) 정말 기분 나쁘다. 타인이 나의 비밀번호를 안다는것은... 그러면 관리자들도 모르게 암호화를 할려면 어떻게 해야할까?
아주 간단하다. 암호화에 사용하는 키 자체를 평문 즉, 비밀번호로 사용하면 된다. 아래 처럼 구현하면 본인만 알 수가 있는것이다. 암호화 할려는 비밀번호 자체가 두 역활을 동시에 하는것이다. 한번 구현해보자.
01.public static void main(String[] args) throws Exception {
02.    String password = "mypassword";
03.      
04.    byte[] passwordBytes = password.getBytes();
05.    int len = passwordBytes.length;
06.    byte[] keyBytes = new byte[16];
07.    if (len >= 16) {
08.        System.arraycopy(passwordBytes, 0, keyBytes, 0, 16);
09.    } else {
10.        System.arraycopy(passwordBytes, 0, keyBytes, 0, len);
11.        for (int i = 0; i < (16 - len); i++) {
12.            keyBytes[len + i] = passwordBytes[i % len];
13.        }
14.    }
15.      
16.    Key key = generateKey("AES", keyBytes);
17.    String transformation = "AES/ECB/PKCS5Padding";
18.    Cipher cipher = Cipher.getInstance(transformation);
19.    cipher.init(Cipher.ENCRYPT_MODE, key);
20.      
21.      
22.    byte[] plain = password.getBytes();
23.    byte[] encrypt = cipher.doFinal(plain);
24.    System.out.println("원문 : " + ByteUtils.toHexString(plain));
25.    System.out.println("암호 : " + ByteUtils.toHexString(encrypt));
26.      
27.    cipher.init(Cipher.DECRYPT_MODE, key);
28.    byte[] decrypt = cipher.doFinal(encrypt);
29.    System.out.println("복호 : " + ByteUtils.toHexString(decrypt));
30.}

  키는 패딩이 안되기 때문에 자체적으로 길이를 맞춰줘야한다. 여기서는 16바이트보다 크면 자르고, 16바이트보다 작으면 입력받는 비밀번호 문자를 반복해서 뒤에 붙여두는식으로 구현하였다.
 이런 방식이 귀찮다면 블럭 암호화 방법 대신, MD5나 SHA1으로 메시지 해쉬값을 저장해서 그 값을 비교해도 된다. (이 방식은 복호화가 안된다.) my-sql password() 함수가 SHA1을 이용하는데, 다음번에 알아보도록 하자.

2009. 9. 14. 16:51

자바 정리 참고 1




<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 

ResourceBundle 클래스, Properties 클래스 

ResourceBundle 클래스
Properties
클래스
 

jsp 파일에서 web.xml 의 값 읽어오기

################################################

ResourceBundle 클래스
리소스번들을 이용하여 프로퍼티 파일을 읽어오기
# Res_test.properties
SINCE_YEAR=2007
SINCE_MONTH=9
SINCE_DATE=2
SINCE_DAY=Sunday

import java.util.ResourceBundle;
import java.util.Enumeration;

public class ResourceBundleTest
{
 public static void main(String[] args)
 {
  ResourceBundle bundle = null;
  try{
   bundle = ResourceBundle.getBundle("Res_test");//test.Res_test

   Enumeration enum1 = bundle.getKeys();
   for(; enum1.hasMoreElements() ; ){
    String name = (String)enum1.nextElement();
    String value = bundle.getString(name);
    System.out.println(name+" : "+value);
   }
  }catch(Exception e){
   e.toString();
  }
 }
}


################################################
Properties
클래스
프로퍼티 파일에 입력하고 불러오는 PropertyTest .java 파일

import java.util.*;
import java.io.*;
/*
 * Can use globels.properties
 * Properties
 **/
class Property_Sub{
  
 private String PROPERTIES_FILE="C://java/exam/globals.properties";//globals.properties
 
 public String getProperty(String keyName){
  String value = null;
  try{
   Properties props =  new Properties();
   FileInputStream fis  = new FileInputStream(PROPERTIES_FILE);
   props.load(new java.io.BufferedInputStream(fis));
   value = props.getProperty(keyName).trim();
   fis.close();   
  }catch(java.lang.Exception e){
   e.printStackTrace();
  } 
  return value;
 }
 
 public void setProperty(String keyName, String value){
  try{
   Properties props = new Properties();
   FileInputStream fis  = new FileInputStream(PROPERTIES_FILE);
   props.load(new java.io.BufferedInputStream(fis));
   props.setProperty(keyName, value);
   props.store(new FileOutputStream(PROPERTIES_FILE), "");
   fis.close();
  }catch(java.lang.Exception e){
   e.printStackTrace();
  }
 }
 
}

public class PropertyTest extends Property_Sub{
 
 public static void main(String[] args){
  
  PropertyTest o = null;
  
  if(args.length == 1){
   o = new PropertyTest();
   System.out.println (args[0] + " : " + o.getProperty(args[0]) );
  }
  else if(args.length == 2){
   o = new PropertyTest();
   o.setProperty(args[0], args[1]);
   System.out.println(args[0] + " : " + o.getProperty (args[0]) );
  }
  else{
   System.out.println("wrong argument");
   
  }
  
 }
}
#
#globals.properties
db=sadong
driver=oracle
name=richard

################################################
jsp
파일에서 web.xml 의 값 읽어오기

web.xml 에는
<context-param>
  <param-name>jdf.config.file</param-name>
  <param-value>D:\eclipse\WEB-INF\jdf.properties</param-value>
</context-param>

jsp 에서는
getServletContext().getInitParameter("jdf.config.file")
 

################################################
import java.util.ResourceBundle;

ResourceBundle userxconfig = ResourceBundle.getBundle("com.aaa.bbb.userxconfig");
String CURR_HOST = (String)userxconfig.getObject("currHost");

===============

userxconfig.properties 파일에
currHost=
www.naver.com

이라는 부분이 있을 경우

CURR_HOSTwww.naver.com이 할당된다.

 

 

 

JSP 기초과정

1. JSP 개요

2. 설치와 Setting

3. JSP에서 사용하는 스크립팅 요소들

4. JSP 유용한 클래스

 

#####################################################################

1. JSP 개요

- 서블릿(Servlet) CGI 프로그래밍에 대한 자바 기술의 대안이다.

- CGI 프로그램이 비교적 빠른 수행능력을 보인다면 프로세스가 시작할 때의 오버헤드를 빠른 수행시간으로 메울 수 있지만

그렇지 못하다. 서블릿을 쓸 때는 자바 가상 머신이 떠 있는 상태에서 각 요청이 들어올 때 마다 프로세스보다 가벼운 자바

스레드가 실행된다.

- Java Server Pages (JSP)는 동적인 HTML과 정적으로 생성된 HTML을 섞어 주는 기술이다. JSP

디자인 부분과 로직 부분이 나뉘어 있어 쉽게 일을 분리할 수 있다.

 

#####################################################################

2. 설치와 Setting

- 사용환경 J2SDK1.4 / Tomcat5.0 / MySQL4.0

- CLASSPATH란 자바가 어디서 클래스를 찾을 수 있는지 명시해 놓은 변수다. 이것이 명시되어 있지 않다면 자바는 현재

디렉토리와 표준 시스템 라이브러리에서 클래스를 찾게 된다. CLASSPATH를 설정할 때 현재 디렉토리를 나타내는 '.'

빼놓지 않도록 유의한다.

 

2.1 Unix (C Shell)

setenv CLASSPATH .:servlet_dir/servlet.jar:servlet_dir/jsp.jar:$CLASSPATH

 

2.2 Windows 95/98/NT

set CLASSPATH=.;servlet_dir/servlet.jar;servlet_dir/jsp.jar;%CLASSPATH%

 

2.3 Path setting

ex1) Windows98 - 시작\실행\sysedit\AUTOEXEC.BAT

 SET PATH=%PATH&;C:\JDK1.4\BIN

 SET JAVA_HOME=C:\j2sdk1.4

 SET TOMCAT_HOME=C:\TOMCAT4

 SET CLASSPATH=.;C:\tomcat5\common\lib\servlet-api.jar

 

ex2) WindowsXP, Windows2000 - 내컴퓨터\등록정보\환경변수\시스템변수

 PATH          C:j2sdk1.4\bin

 JAVA_HOME     C:\j2sdk1.4

 CATALINA_HOME C:\Tomcat5

 

2.4 Classpath setting servlet_api.jar, rt.jar 세팅함

CLASSPATH       .;C:\tomcat5\common\lib\servlet-api.jar

               ;C:\j2sdk1.4\jre\lib\rt.jar

(JDK1.2이후는 classpath를 따로 지정하지 않아도 됨)

참고 SET CLASSPATH=%CLASSPATH%;c:\tomcat4\lib\servlet.jar;c:\jdk1.3\jdbc\mm.mysql-2.02-bin.jar

톰켓에서만 JDBC를 사용하려면 TOMCAT\LIB밑에 mm.mysql-2.0.2-bin.jar파일을 복사

 

http://mmmysql.sourceforge.net mm.mysql-2.0.1-bin.jar를 다운받는다.

http://dev.mysql.com/downloads/connector/j/3.1.html 에서

mysql-connector-java-3.1.10.zip 다운받는다

 

- 확인방법 java -version, javap org.gjt.mm.mysql.Driver로 확인

c:\mysql\bin>mysql-max-nt -install

c:\mysql\bin>net start mysql

c:\mysql\bin>net stop mysql

c:\mysql\bin>mysql -u root -p

mysql>show databases;

mysql>show tables;

mysql>use mydb;

mysql>desc tablename;

mysql>create mydb;

mysql>exit;

 

"로드하고 연결하고 실행한다"

Class.forName("org.gjt.mm.mysql.Driver");

Class.forName("oracle.jdbc.dirver.OracleDriver");

Class.forName("sun.jdbc.odbc.jdbcOdbcDriver");

Class.forName("postgresql.Driver");

 

Connection conn =

DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb","id","passwd");

Connection conn =

DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORCL","id","passwd");

Connection conn =

DriverManager.getConnection("jdbc:odbc:odbc원본명","id","passwd");

 

 

2.5 작업용 Context를 하나 만들기로 하자.

- c:\tomcat5\webapps\bit 라는 디렉토리를 만들자.

- c:\tomcat5\conf\server.xml에서 </host> 앞쪽에 다음을 추가하자.

 

----------------------------------------------------------------

<!-- Tomcat bit Context -->

<Context path="/bit" docBase="c:/tomcat5/webapps/bit" debug="0"

reloadable="true" />

-----------------------------------------------------------------

- c:\tomcat5\webapps\bit\WEB-INF\web.xml을 아래와 같이 만들자.

------------------------------------------------------------------

<?xml version="1.0" encoding="ISO-8859-1"?>

<!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>

<servlet>

<servlet-name>HelloWorld</servlet-name>

<servlet-class>HelloWorld</servlet-class>

</servlet>

 

<servlet-mapping>

<servlet-name>HelloWorld</servlet-name>

<url-pattern>/servlet/HelloWorld</url-pattern>

</servlet-mapping>

</web-app>

------------------------------------------------------------------

Tomcat5.0 이하 버젼에서는 서블릿을 실행할때 설정하는 web.xml 내용임

<?xml version="1.0" encoding="ISO-8859-1"?>

<!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>

 <display-name>Welcome to Tomcat</display-name>

 <description>

    Welcome to Tomcat

 </description>

 <servlet-mapping>

       <servlet-name>invoker</servlet-name>

       <url-pattern>/servlet/*</url-pattern>

  </servlet-mapping>

</web-app>

 

 

2.6. c:\tomcat5\webapps\bit\WEB-INF\classes 디렉토리를 만들고

C:\j2sdk1.4\bin\java.exe -jar -Duser.dir="C:\Tomcat4.1"

"C:\Tomcat4.1\bin\bootstrap.jar" start

 

HelloWorld.java 서블릿을 만들자.

 

//HelloWorld.java

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class HelloWorld extends HttpServlet {

       public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException{

       res.setContentType("text/html; charset=euc-kr");

       PrintWriter out = res.getWriter();

 

       out.println("<html>");

       out.println("<head><title>Hello World!</title></head>");

       out.println("<body>");

       out.println("<BIG>Hello World!</BIG>");

       out.println("</body></html>");

       }

}

 

#####################################################################

3. JSP에서 사용하는 스크립팅 요소들

 

- 스크립트들과 그 역할

- 스크립트렛(Scriptlet) <% %>  _jspService에 프로그램 추가

- 표현식(Expressions) <%= %>   _jspService에 프로그램 추가(out.print의 간략화)

- 선언문(Declarations) <%! %>  .java파일에 멤버필드와 멤버메서드 추가

- 지시문(Directives) <%@ %>    .java파일에 여러 가지 속성 정하기(지시문의 형태에 따라 .java파일의

적당한 장소에 추가)

 

java.io (PrintWriter 등을 위해서)

javax.servlet (HttpServlet 등을 위해서)

javax.servlet.http (HttpServletRequest HttpServletResponse를 위해서)

 

"javac directory\ServletClass.java" (윈도 사용자; 백 슬래시)

"javac directory /ServletClass.java" (유닉스 사용자; 슬래시)

 

3.1 Page 지시문과 import 사용하기

ex)

- Date.jsp, contentType="text/html;charset=ksc5601"  8859_1

 

<%@ page contentType="text/html;charset=euc-kr"%>

 

<%@ page import="java.util.*"%>

<%

       Date date = new Date();

       out.println("오늘의 날짜는 "+date);

%>

 

 

ex) <%@ page 속성="" %>

<%@ page session="false"%> 기본값은 true, session 을 사용할 것인지 여부

<%@ page isThreadSafe="true"%> 기본값은 false, 여러클라이언트의 요청이 동시에 이루어질때 문제가

일어나는 것을 막고자할때 사용

<%@ page errorPage="error.jsp"%> 에러가 발생되었을때 이동하는 페이지

<%@ page isErrorPage="true"%> 이페이지가 에러페이지로 사용되는 페이지인지 아닌지정함

 

3.2 JSP 내장 객체 - 9

- out, request, response, session

- pageContext(javax.servlet.jsp.PageContent) - 내장객체와 page 지령문의 몇가지 속성을 초기화할때

- application(javax.servlet.ServletContent) - 애플리케이션에 대한 정보를 가져올때

- config(javax.servlet.ServletCongfig) - ServletConfig의 내장객체

- page(javax.servlet.jsp.HttpJspPage) - 페이지 자신을 참조하는 객체

- exception(java.lang.Throwable) - 에러페이지에만 사용

 

package org.apache.jsp;  import org.apache.jasper.runtime.*;

<%@ page import="javax.servlet.*, javax.servlet.http.*, javax.servlet.jsp.*"%>

 

1. out 객체

- javax.servlet.jsp.JspWriter 클래스에 대한 내장객체

 

2. request 객체 - get, post, cookie 불러오기(getCookie()), 헤더정보가저오기

- javax.servlet.http.HttpServletRequest 클래스의 내장객체

 

ex1)

- formget.html

<form method="get" action="getform.jsp">

ID: <input type="text" name="id"><br>

PASS: <input type="password" name="pass"><br>

<input type="submit" value="submit"><input type="reset" value="reset">

</form>

- getform.jsp

 

<%@ page contentType="text/html;charset=euc=kr"%>

<%

 

       String id = request.getParameter("id");

       String pass = request.getParameter("pass");

       out.println("Id : "+id+"<br>");

       out.println("Password : "+pass+"<hr>");

       out.println("<a href='javascript:history.back(-1)'>Rear</a>";)

%>

ex2)

- formpost.html

<form method="post" action="postform.jsp">

ID: <input type="text" name="id"><br>

PASS: <input type="password" name="pass"><br>

<input type="submit" value="submit"><input type="reset" value="reset">

</form>

- postform.jsp

 

<%@ page contentType="text/html;charset=euc=kr"%>

<%

 

       String id = new String(request.getParameter("id").getBytes("8859_1"),

"euc-kr");

       String pass = request.getParameter("pass");

       out.println("Id : "+id+"<br>");

       out.println("Password : "+pass+"<hr>");

       out.println("<a href='javascript:history.back(-1)'>Rear</a>";)

%>

 

ex3)

- headerInfo.jsp

<%

       out.println("현재문서의경로 "+request.getRequestURI());

       out.println("호스트명           "+request.getHeader("HOST"));

       out.println("브라우저       "+request.getHeader("User-Agent"));

       out.println("파일명         "+request.getServletPath());

       out.println("서버명         "+request.getServerName());

       out.println("포트명         "+request.getServerPort());

       out.println("URL

http://"+request.getServerName()+":"+request.getServerPort()+getServletPath());

%>

 

3. response 객체 - mine타입지정, 헤더정보세팅, sendRedirect(), cookie값 저장(addCookie())

 

ex1)

- minesetting.jsp

<%

       response.setContentType("application/zip");

       out.println("hi ~~~");

%>

ex2)

- setheader.jsp

<%

       response.setHeader("Cache-Control","no-store");

       response.setHeader("Pragma","no-cache");

       //response.setHeader("Refresh","5;URL=http://www.yahoo.co.kr") 지정한시간에 url로 이동

%>

 

ex3)

- sendRedirect.jsp

<%

       response.sendRedirect("http://www.google.co.kr");

%>

- javascript.html

<script language="javascript">

       document.location.href='http://www.hotmail.com';

</script>

 

4. session 객체

- javax.servlet.http.HttpSession 에 대한 내장객체

 

#####################################################################

5. JSP 유용한 클래스

 

ex1)

- date-format.jsp

 

<%@ page contentType="text/html;charset=euc-kr"%>

 

<%@ page import="java.util.*, java.text.*"%>

<%

       try{

               Date date = new Date();

               SimpleDateFormat simpledateformat1 = new

SimpleDateFormat("yyyy-MM-dd h:mm: a");

               String style = "MM-dd-yyyy"

               SimpleDateFormat simpledateformat2 = new SimpleDateFormat(style);

               String today1 = simpledateformat1.format(date);

               String today2 = simpledateformat2.format(date);

 

               out.println("Today "+date+"<br>");

               out.println("Today Expression 1 "+today1+"<br>");

               out.println("Today Experssion 2 "+today2+"<br>");

 

       }catch(Exception e){

               out.println(e);

       }

%>

 

 

5.1 파일관련

ex1)

- fileorg.jsp

 

<%@ page contentType="text/html;charset=euc-kr"%>

<%@ page import="java.io.*"%>

<%

       try{

 

               String filename = "c:\\tomcat4.1\webapps\\root\\test.txt";

 

               //String filename = "/usr/local/tomcat5.0/webapps/root/test.txt";

               File file = new File(filename);

               file.createNewFile();

               out.println("파일생성<br>");

               if (file != null){

                       FileWriter fw = new FileWriter(filename);

 

                       String text="나는 파일로 쓰여질 문구\n두번째 라인\n세번째 라인";

 

                       fw.write(text);

                       fw.close();

                       out.println("파일쓰기완료<br>");

               }

               FileReader fr = new FileReader(filename);

           BufferedReader br = new BufferedReader(fr);

           //out.println(br.readLine());

           String line;

           while( (line=br.readLine())!=null ){

                          out.println(line+"<br>");

           }

 

               out.println("파일절대경로 "+f.getAbsolutePath());

               out.println("파일이름 "+f.getName());

               out.println("파일디렉토리 "+f.getParent()):

               out.println("파일크기 "+f.length());

               Thread.sleep(5000);

               if (f.exists()){

                       f.delete();

                       out.println("파일삭제");

               }else{

                       out.println("파일없슴");

 

               }

 

       }catch(Exception e){

               out.println(e);

       }

%>

 

<%-- jsp 주석 --%>

<!-- html주석 -->

<%@ include file="copyrigth.jsp"%>

 

#################################################

import java.sql.*;

public class ConnectionTest{

  public static void main(String[] args){

      try{

         Class.forName("org.gjt.mm.mysql.Driver");

         Connection conn =

DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb","root","");

         System.out.println("Connection클래스의 객체 con 생성");

         con.close();

         System.out.println("데이터베이스와 연결을 끊음");

      }catch(Exception e){}

  }

}

#################################################

 

 

유틸소스

import javax.crypto.*;

import javax.crypto.spec.SecretKeySpec;

import javax.crypto.spec.DESKeySpec;

import javax.crypto.spec.IvParameterSpec;

import java.security.*;

import sun.misc.BASE64Decoder;

import sun.misc.BASE64Encoder;

 

import java.io.UnsupportedEncodingException;

import java.security.MessageDigest;

import java.text.SimpleDateFormat;

import java.util.Calendar;

import java.util.Date;

import java.util.StringTokenizer;

import java.util.Vector;

 

import org.w3c.dom.*;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import java.io.File;

import java.util.ArrayList;

import java.util.List;

import java.util.ResourceBundle;

 

public class Util {

        static byte[] seedKeyData = new byte[24];

        public Util(){   

        seedKeyData[0] = 0x01;

        seedKeyData[1] = 0x02;

        seedKeyData[2] = 0x03;

        seedKeyData[3] = 0x04;

        seedKeyData[4] = 0x05;

        seedKeyData[5] = 0x06;

        seedKeyData[6] = 0x07;

        seedKeyData[7] = 0x08;

        seedKeyData[8] = 0x09;

        seedKeyData[9] = 0x10;

        seedKeyData[10] = 0x11;

        seedKeyData[11] = 0x12;

        seedKeyData[12] = 0x13;

        seedKeyData[13] = 0x14;

        seedKeyData[14] = 0x15;

        seedKeyData[15] = 0x16;

        seedKeyData[16] = 0x01;

        seedKeyData[17] = 0x02;

        seedKeyData[18] = 0x03;

        seedKeyData[19] = 0x04;

        seedKeyData[20] = 0x05;

        seedKeyData[21] = 0x06;

        seedKeyData[22] = 0x07;

        seedKeyData[23] = 0x08;

        }

        public static String encTripleDES(String beforeStr) {

        String afterStr = "";

        try {

            SecretKeySpec desKey = new SecretKeySpec(seedKeyData, "DESede"); //seedKeyData로 암호화

            Cipher desCipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");

            byte[] cleartext = beforeStr.getBytes();

            desCipher.init(Cipher.ENCRYPT_MODE, desKey);

            byte[] ciphertext = desCipher.doFinal(cleartext);

            afterStr = (new BASE64Encoder()).encode(ciphertext);

        } catch (Exception e) {

            e.printStackTrace();

        }

        return afterStr;

    }

    public static String decTripleDES(String beforeStr) {        

        String afterStr = "";

        try {

            SecretKeySpec desKeyD = new SecretKeySpec(seedKeyData, "DESede");

            Cipher desCipherD = Cipher.getInstance("DESede/ECB/PKCS5Padding");

            desCipherD.init(Cipher.DECRYPT_MODE, desKeyD);

            byte[] decodedBytes = (new BASE64Decoder()).decodeBuffer(beforeStr);

            byte[] cleartext1 = desCipherD.doFinal(decodedBytes);

            afterStr = new String(cleartext1);

        } catch (Exception e) {

            e.printStackTrace();

        }

        return afterStr;

    }

    public static String getDate() {

        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        Date currentTime = new Date();

 

        return formatter.format(currentTime);

    }

    public static String cutRealNumber(String _real, int _cutPlace) {

        String result = _real;

        boolean isNumber = true;

        try {

            Double.parseDouble(_real);

        } catch (NumberFormatException e) {

            isNumber = false;

        }

        if (isNumber) {

            int pointIndex = _real.indexOf('.'); // 소수점 위치.

            if (pointIndex == -1) {

                // 소수점이 없을 때. 12 -> 12.000

                result += ".";

                for (int i = 0; i < _cutPlace; i++)

                    result += "0";

            } else {

                // 소수점이 있을 때.

                int decimalCount = (_real.length() - 1) - pointIndex; // 소수점 이하 개수 .

                if (_cutPlace > decimalCount) {

                    // 12.1 -> 12.100, 0.0 -> 0.000

                    for (int i = 0; i < _cutPlace - decimalCount; i++)

                        result += "0";

                } else if (_cutPlace < decimalCount) {

                    // 12.12345 -> 12.123

                    result = _real.substring(0, pointIndex + _cutPlace + 1);

                }

            }

        }

        return result;

    }

    public static String encode(String _origin) {

        String result = null;

        try {

            MessageDigest md5 = MessageDigest.getInstance("MD5");

            md5.update(_origin.getBytes());

            byte[] md5Password = md5.digest();

            BASE64Encoder enc = new BASE64Encoder();

            result = enc.encode(md5Password);

        } catch (Exception e) {

            e.printStackTrace();

        }

        return result;

    }

    public static String utfTo8859(String _str) {

        String result = "";

        try {

            if (_str != null)

                result = new String(_str.getBytes("UTF-8"), "8859_1");

        } catch (UnsupportedEncodingException e) {

            e.printStackTrace();

        }

        return result;

    }

    public static String kscTo8859(String _str) {

        String result = "";

        try {

            if (_str != null)

                result = new String(_str.getBytes("KSC5601"), "8859_1");

        } catch (UnsupportedEncodingException e) {

            e.printStackTrace();

        }

        return result;

    }

    public static String toKSC(String _str) {

        String result = "";

        try {

            if (_str != null)

                result = new String(_str.getBytes("8859_1"), "KSC5601");

        } catch (UnsupportedEncodingException e) {

            e.printStackTrace();

        }

        return result;

    }

    public static String toUTF(String _str) {

        String result = "";

        try {

            if (_str != null)

                result = new String(_str.getBytes("8859_1"), "UTF-8");

        } catch (UnsupportedEncodingException e) {

            e.printStackTrace();

        }

        return result;

    }

    // Unicode 2.0 -> Unicode 1.2

    public static String toUni12(String uni20) {

        int len = uni20.length();

        char[] out = new char[len];

        for (int i = 0; i < len; i++) {

            char c = uni20.charAt(i);

            if (c < 0xac00 || 0xd7a3 < c) {

                out[i] = c;

            } else  // 유니코드 2.0 한글 영역

            {

                try {

                    byte[] ksc = String.valueOf(c).getBytes("KSC5601");

                    if (ksc.length != 2) {

                        out[i] = '\ufffd';

                        System.err.println("Warning: Some of Unicode 2.0 hangul character was ignored.");

                    } else {

                        out[i] = (char) (0x3400 +

                                ((ksc[0] & 0xff) - 0xb0) * 94 +

                                (ksc[1] & 0xff) - 0xa1);

                    }

                } catch (UnsupportedEncodingException ex) {

                    throw new InternalError("Fatal Error: KSC5601 encoding is not supported.");

                }

            }

        }

        return new String(out);

    }

    // Unicode 1.2 -> Unicode 2.0

    public static String fromUni12(String uni12) {

        if (uni12 == null)

            uni12 = "";

        int len = uni12.length();

        char[] out = new char[len];

        byte[] ksc = new byte[2];

        for (int i = 0; i < len; i++) {

            char c = uni12.charAt(i);

            if (c < 0x3400 || 0x4dff < c) {

                out[i] = c;

            } else if (0x3d2e <= c) // 유니코드 1.2 한글 보충 영역 A, B

            {

                System.err.println("Warning: Some of Unicode 1.2 hangul character was ignored.");

                out[i] = '\ufffd';

            } else // 유니코드 1.2 KSC5601 대응 한글 영역

            {

                try {

                    ksc[0] = (byte) ((c - 0x3400) / 94 + 0xb0);

                    ksc[1] = (byte) ((c - 0x3400) % 94 + 0xa1);

                    out[i] = new String(ksc, "KSC5601").charAt(0);

                } catch (UnsupportedEncodingException ex) {

                    throw new InternalError("Fatal Error: KSC5601 encoding is not supported.");

                }

            }

        }

        return new String(out);

    }

    public String removeSpecialChar(String str) throws Exception {

        if (str == null || str.trim().length() == 0) return "";

        StringBuffer data = new StringBuffer(str);

        StringBuffer buf = new StringBuffer();

        for (int i = 0; i < data.length(); i++) {

            char c = data.charAt(i);

            if (((int) c > 47 && (int) c < 58)

                    || ((int) c > 64 && (int) c < 91)

                    || ((int) c > 96 && (int) c < 123))

                buf.append(c);

        }

 

        return buf.toString();

    }

    public static int cpMapping(int cpNum) {

        if (cpNum == 99)

            cpNum = 0;

        else if (cpNum == 20)

            cpNum = 15;

        else if (cpNum < 1 || cpNum > 15) cpNum = 0;

        return cpNum;

    }

    public static long diffDate(Calendar cal1, Calendar cal2) {

        long cal1sec = cal1.getTime().getTime();

        long cal2sec = cal2.getTime().getTime();

 

        long gap = cal1sec - cal2sec;

 

        long gapday = (gap / 86400) / 1000;

 

        return gapday;

    }

    public static String sqlInsectionProctect(String str) {

        if (str == null) return "";

        str = str.replace('\'', '\\');

        return str;

    }

    public static String removeSlush(String str) throws Exception {

        if (str == null || str.trim().length() == 0) return "";

        int i;

        while ((i = str.indexOf("/")) > -1) {

            str = str.substring(0, i) + str.substring(i + 1);

        }

 

        return str;

    }

    public static String removeDot(String str) throws Exception {

        if (str == null)

            return "";

        else {

            int i = -1;

            if ((i = str.indexOf(".")) > -1) {

                str = str.substring(0, i);

            }

            return str;

        }

    }

    public static String change_Code(String line, String oldString, String newString) {

        for (int index = 0; (index = line.indexOf(oldString, index)) >= 0; index += newString.length())

            line = line.substring(0, index) + newString + line.substring(index + oldString.length());

        return line;

    }

    public static String convertDate(String dateValue, String dateFormat, String gubun) {

        if (dateFormat == null || dateValue == null || dateValue.length() != 10) {

            return dateValue;

        } else if (dateFormat.equals("yyyy-MM-dd")) {

            return dateValue;

        } else {

            StringBuffer transDate = new StringBuffer(15);

            if (dateFormat.equals("dd-MM-yyyy"))

                return transDate.append(dateValue.substring(8, 10)).append(gubun).append(dateValue.substring(5, 7)).append(gubun).append(dateValue.substring(0, 4)).toString();

            else if (dateFormat.equals("MM-dd-yyyy"))

                return transDate.append(dateValue.substring(5, 7)).append(gubun).append(dateValue.substring(8, 10)).append(gubun).append(dateValue.substring(0, 4)).toString();

        }

        return "";

    }

    public static String[] splitString(String inputValue, String deli) {

        StringTokenizer st = null;

        String[] returnValue = null;

        try {

            st = new StringTokenizer(inputValue, deli);

            returnValue = new String[st.countTokens()];

            int i = 0;

            while (st.hasMoreTokens())

                returnValue[i++] = (st.nextToken());

        } catch (Exception ex) {

            returnValue = null;

        }

        return returnValue;

    }

    public static String[] splitStringIncludeNull(String inputValue, String deli) {

        StringTokenizer st = null;

        String[] returnValue = null;

        try {

            Vector returnVec = new Vector();

            st = new StringTokenizer(inputValue, deli, true);

            int count = st.countTokens();

            String previousValue = new String(deli);

            for (int i = 0; i < count; i++) {

                String currentValue = st.nextToken();

                if (previousValue.equals(currentValue)) {

                    returnVec.add("");

                } else {

                    if (!currentValue.equals(deli)) {

                        returnVec.add(currentValue);

                    }

                }

                previousValue = currentValue;

            }

            if (inputValue.substring((inputValue.length() - 1), (inputValue.length())).equals(deli))

                returnVec.add("");

            returnValue = new String[returnVec.size()];

            for (int i = 0; i < returnVec.size(); i++)

                returnValue[i] = (String) returnVec.get(i);

        } catch (Exception ex) {

            ex.printStackTrace();

            returnValue = null;

        }

        return returnValue;

    }

        public static String toEng (String ko)  {

               if (ko == null) {

                       return null;

               }             

               try {

                       return new String(ko.getBytes("KSC5601"),"8859_1");

               } catch(Exception e) {

                       return ko;

               }

        }

        public static String toKor (String en) {

               if (en == null) {

                       return null;

               }

               try {

                       return new String (en.getBytes("8859_1"), "KSC5601");

               } catch(Exception e) {

                       return en;

               }

        }

        /**

     * @param sDATE   : 입력 날짜 ==> 2000/03/09

     * @param sFORMAT : 날짜 형식(대소문자 구분없음) ==> YYYY/MM/DD

     *                ex) validDate("20040429", "yyyymmdd");

     *                validDate("2004-04-29", "yyyy-mm-dd");

     *                validDate("2004/04/29", "yyyy/mm/dd");

     */

    public static boolean validDate(String sDATE, String sFORMAT) {

        boolean bRET = false;

        sFORMAT = sFORMAT.toUpperCase();

        int iYYYY = Integer.parseInt(sDATE.substring(sFORMAT.indexOf("YYYY"), sFORMAT.indexOf("YYYY") + 4));

        int iMM = Integer.parseInt(sDATE.substring(sFORMAT.indexOf("MM"), sFORMAT.indexOf("MM") + 2));

        int iDD = Integer.parseInt(sDATE.substring(sFORMAT.indexOf("DD"), sFORMAT.indexOf("DD") + 2));

        bRET = iYYYY >= 0 && iYYYY < 10000 && iMM > 0 && iMM < 13 && iDD > 0 && iDD < 32;

        return bRET;

    }

  

        public static String checkDate(String input){

               String reVal = null;

               try

               {

                       if (input==null||input.length()!=8)

                       {

                              return input;

                       }else if (input.equals("00000000"))

                       {                     

                              return "99991231";

                       }      

                       Calendar ca = Calendar.getInstance();

                       int year = Integer.parseInt(input.substring(0,4));

                       int month = Integer.parseInt(input.substring(4,6));

                       int date = Integer.parseInt(input.substring(6));

                       ca.set(year, month-1, date);

                       String simple = "yyyyMMdd";

                       SimpleDateFormat simpledateformat = new SimpleDateFormat(simple);

                       reVal = simpledateformat.format(ca.getTime());

               }

               catch (Exception e)

               {

                       e.printStackTrace();

                       return input;

               }

               return reVal;

        }

        /**

        * 파라메터(data)의 스트링을 파라메터(before)인 문자열을 파라메터(after)로 대치한다.

        */

        public static String replaceAll(String data, String before, String after) throws Exception {

               // 쓰기전에 이스케이프 문자 처리

               if (data.length() < before.length())

                       return data;

               StringBuffer buf_data = new StringBuffer(data);

               StringBuffer buf_before = new StringBuffer(before);

               StringBuffer buffer = new StringBuffer();

               for (int i = 0; i < buf_data.length(); i++) {

                       if (buf_data.charAt(i) == buf_before.charAt(0)) {

                              if (i + before.length() <= buf_data.length() && buf_data.substring(i, i + before.length()).equals(before)) {

                                      buffer.append(after);

                                      i += buf_before.length() - 1;

                                      continue;

                              }

                       }

                       buffer.append(buf_data.charAt(i));

               }

               return buffer.toString();

        }

 

        public static String nvl(String str, String str2) {

               String retVal = null;

               if (str == null) {

                       retVal = str2;

               } else {

                       retVal = str;

               }

               return retVal;

        }

 

        public static void main(String[] args){

               try{

                       if (args.length > 0) {

                              for (int i = 0; i < args.length; i++) {

                                      String s = Util.encTripleDES(args[i]);

                                      String s1 = Util.decTripleDES(s);

                                      System.out.println("Original     ["+args[i]+"]");

                                      System.out.println("encTripleDES ["+s+"]");

                                      System.out.println("decTripleDES ["+s1+"]");

                              }      

                       }      

               } catch (Exception e) {

                       e.printStackTrace();

               }             

        }

}

 

 

 

자바소스 정리

01. ServerSocket Socket 간의 채팅 소스

02. Swing에서 이미지 아이콘 삽입 및 종료

03. RMI 정상적인 예제

04. RMI registry 등록하지 않고 사용

05. RMI 통신법

06. Directory 찾아서 리스트 만들기

07. IO 관련 소스

08. 파일을 읽는 소스

09. 입력받은 파일을 읽는 소스

10. 스트링을 파일로 저장하는 소스

11. 파일을 저장하는 소스

12. 에디터에는 볼 수 없으나 도스에서는 보임

13. Properties 파일에 관련된 소스

14. 파일과 관련해서 Properties 에서 읽어 오는 소스

15. 읽은 파일입력 후 내용확인하는 소스

16. 입력파일과 저장할 파일을 쓰는 소스

17. 파일을 읽어서 보여주는 소스

18. 2초마다 출력하는 쓰레드 소스

19. 배열과 관련하여

20. 배열로 합계 평균을 내는 소스

21. 프로그램 인수를 받아 평균과 총계를 보여주는 소스

22. 실행되지 않으나 참고해야 할 소스

23. 데이터베이스 커넷션하고 로그파일을 만듬

24. 엔터키를 두르면 검색하는 소스

25. 날짜 포맷 바꾸는 방법

26. Random 클래스로 가져온 값을 중복체크하기

27. TripleDES 을 이용한 암호화 알고리즘

28. MD5로 암호화 하는 알고리즘 (Base64 필요)

29. 버블 소트로 데이타 정렬

30. Random 함수로 받은 값을 중복 체크하는 소소

31. 날짜 관련 함수 이용

32. 특수문자 제거 메소드

33. 암호와 알고리즘을 사용후 복호화 하는 소스와 적용예

34. 특정 폴더에 쌓이는 파일을 없애는 소스

35. 원도우 배치 프로그램 실행하기

36. 메일을 발송하는 프로그램 소스

37. 세션관련 정리 소스

38. 로긴 및 서블릿 관련 소스(한글처리)

39. 쿠키 셋팅에 관한 예제

40. 클래스를 이용하는 샘플예제 소스

41. 버블 소트와 중복체크 예제

42. 날짜 체크하는 메소드 만들기

43. 조건에 맞게 메일을 보내는 소스

44. 로그를 만드는 소스

 

 

#####################################################################

44. 로그를 만드는 소스

#####################################################################

 

import java.io.*;

import java.text.SimpleDateFormat;

import java.util.Date;

 

public class LogMaker{

        static SimpleDateFormat simpledateformat = new SimpleDateFormat("HH:mm:ss");

       

        public static void println(String filename, String log){

               try{

                       Date date = new Date();

                       String pid = simpledateformat.format(date);

                       //System.out.println("LogMaker String pid->/"+pid+"/");

                       FileWriter fw = new FileWriter(filename+".log",true);

                       fw.write(pid + " : "+log+"\n");

                       fw.flush();

                       fw.close();

               } catch(Exception ex){

               }

        }

}

#####################################################################

 

#####################################################################

43. 조건에 맞게 메일을 보내는 소스

#####################################################################

 

import java.sql.*;

import java.util.*;

import java.io.*;

import javax.mail.*;

import javax.mail.internet.*;

import java.util.StringTokenizer;

import java.text.SimpleDateFormat;

import java.util.Date;

 

public class AirforwarderMailNCC extends Thread

{

        public static void main(String[] args) throws MessagingException

        {

                       Connection  conn_0 = null;

                       PreparedStatement pstmt_0 = null;

                       ResultSet rs_0 = null;

                      

                       String sDB_URL = "jdbc:oracle:thin:@165.213.251.202:1521:itts";

                       String sDB_USER = "itts";

                       String sDB_PASSWORD = "skan00";

              

                             

                       String name ="PTS_AirforwarderNCC";

                       String from = "pts.sec@samsung.com";

                       String to = null;

                       String title = "Notice from PTS";

                       String contents = null;

                       String host = "203.254.221.26"; 

                      

                       String logDir = "log";

                       SimpleDateFormat simpledateformat = new SimpleDateFormat("yyyy-MM-dd");

                       Calendar c = Calendar.getInstance();

                       Date d = c.getTime();

                       String fid = simpledateformat.format(d);

                       File resultFile = new File(logDir, fid);

                       if(resultFile.exists()== false)

                              resultFile.mkdir();

                       logDir = logDir + File.separator + fid;

                      

                       boolean ok1 = false, ok2 = false, ok3 = false;

 

                       try{

                              Class.forName("oracle.jdbc.driver.OracleDriver");                       

                             

                              StringBuffer sbSql_0 = new StringBuffer();                            

                             

                              sbSql_0.append(" SELECT EMAIL, USER_NAME, USER_ID FROM USER_INFO U, BASIC_CODE B WHERE  U.COMPANY_CODE=B.CODE AND DESC2 like 'AIR%' AND U.EMAIL IS NOT NULL AND U.STATUS='approved' AND U.PTS_MAIL='T' AND USER_TYPE='CARRIERLOAD' ");

                              conn_0 = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                              pstmt_0 = conn_0.prepareStatement(sbSql_0.toString());

                              rs_0 = pstmt_0.executeQuery();                        

                              int cnt_total = 0;

                             

                              while(rs_0.next()){     

                             

                                      int cnt_new = 0, cnt_change = 0, cnt_cancel = 0;

                                     

                                      to = rs_0.getString("EMAIL");

                                     

                                      //Count 보낼 메일 가져오기                                           

                                      StringBuffer sbSql_newmail = new StringBuffer(); //New Mail Count

                                      sbSql_newmail.append(" SELECT INV_NO, BILLNO_, UPDDATE, GETCONSIGNAME(CONSIG) CONSIG_NAME, GETCONSIGNAME(SHIPPE) SHIPPE_NAME, TRSTYP, MLDTUE, GI_DAT , A.CREDATE FROM INV_DOC A, USER_INFO U WHERE  USER_ID=? AND SHIPPE <> 'C100' AND A.SHIPPE = U.DEALING_SUBSIDIARY_CODE AND A.MLCARI = U.COMPANY_CODE AND MLCARI <> 'SITA' AND SHIPPE <> 'C100' AND TRSTYP = 'AIR' AND A.RCDFLG = 'A' AND A.UPDDATE = '99991231235959' AND MAIL_STS='F' ");

 

                                      Connection conn_1 = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                                      PreparedStatement pstmt_1 = conn_1.prepareStatement(sbSql_newmail.toString());

                                      pstmt_1.setString(1, rs_0.getString("USER_ID")); //순차적으로 조건을 셋팅

                                     

                                      ResultSet rs_1 = pstmt_1.executeQuery();

                                     

                                      while(rs_1.next()){                                           

                                             cnt_new = rs_1.getRow();                                            

                                      }

                                     

                                      pstmt_1.close();

                                      rs_1.close();

                                      conn_1.close();                               

                                     

                                      StringBuffer sbSql_changemail = new StringBuffer(); //Changed Mail Count

                                      sbSql_changemail.append(" SELECT INV_NO, BILLNO_, UPDDATE, GETCONSIGNAME(CONSIG) CONSIG_NAME, GETCONSIGNAME(SHIPPE) SHIPPE_NAME, TRSTYP, MLDTUE, GI_DAT , A.CREDATE FROM INV_DOC A, USER_INFO U WHERE  USER_ID=? AND SHIPPE <> 'C100' AND A.SHIPPE = U.DEALING_SUBSIDIARY_CODE AND A.MLCARI = U.COMPANY_CODE AND MLCARI <> 'SITA' AND SHIPPE <> 'C100' AND TRSTYP = 'AIR' AND A.RCDFLG = 'A' AND A.UPDDATE <> '99991231235959' AND MAIL_STS='F'  ");

 

                                      Connection conn_3 = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                                      PreparedStatement pstmt_3 = conn_3.prepareStatement(sbSql_changemail.toString());

                                      pstmt_3.setString(1, rs_0.getString("USER_ID")); //순차적으로 조건을 셋팅

                                      ResultSet rs_3 = pstmt_3.executeQuery();

                                     

                                      while(rs_3.next()){                                           

                                             cnt_change = rs_3.getRow();                                         

                                      }

                                      System.out.println("보낼 Change Content 갯수 "+cnt_change);    

                                      pstmt_3.close();

                                      rs_3.close();

                                      conn_3.close();

                             

                                      StringBuffer sbSql_cancelmail = new StringBuffer(); //Canceled Mail Count

                                      sbSql_cancelmail.append(" SELECT INV_NO, BILLNO_, UPDDATE, GETCONSIGNAME(CONSIG) CONSIG_NAME, GETCONSIGNAME(SHIPPE) SHIPPE_NAME, TRSTYP, MLDTUE, GI_DAT , A.CREDATE FROM INV_DOC A, USER_INFO U WHERE  USER_ID=? AND SHIPPE <> 'C100' AND A.SHIPPE = U.DEALING_SUBSIDIARY_CODE AND A.MLCARI = U.COMPANY_CODE AND MLCARI <> 'SITA' AND SHIPPE <> 'C100' AND TRSTYP = 'AIR' AND A.RCDFLG = 'D' AND MAIL_STS='F'        ");

 

                                      Connection conn_5 = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                                      PreparedStatement pstmt_5 = conn_5.prepareStatement(sbSql_cancelmail.toString());

                                      pstmt_5.setString(1, rs_0.getString("USER_ID")); //순차적으로 조건을 셋팅

                                      ResultSet rs_5 = pstmt_5.executeQuery();

                                     

                                      while(rs_5.next()){                                           

                                             cnt_cancel = rs_5.getRow();                                          

                                      }

                                      System.out.println("보낼 Cancel Content 갯수 "+cnt_cancel);     

                                      System.out.println();

                                      pstmt_5.close();

                                      rs_5.close();

                                      conn_5.close();

                                            

                                      // End 메일 카운터 RS_0 ~ RS_7

                                     

                                      //이쪽에서 한개의 메일을 받아서 아래의 컨텐츠를 보내야 함

                                      StringBuffer sbMessage = new StringBuffer();

                                     

                                      sbMessage.append("<html><head>");                                 

                                      sbMessage.append("<style type=\"text/css\" >");

                                      sbMessage.append("BODY,TD,font {");

                                      sbMessage.append("color: #666666; font-family: Arial;   font-size:9pt;line-height: 130%;}");

                                      sbMessage.append("A:link {color:#00639C;text-decoration: underline;}");

                                      sbMessage.append("A:visited {color:#006699;text-decoration:underline;}");

                                      sbMessage.append("A:active {color:#006699;text-decoration:none;}");

                                      sbMessage.append("A:hover {color:#FFFFFF;text-decoration:none;background-color:#E4628B;}");

                                      sbMessage.append("</style></head><body>");

                                     

                                      sbMessage.append("<table width=700 border=0 cellspacing=0 cellpadding=0>");  

                                      sbMessage.append("<tr><td height=4 bgcolor=#CDCDCD><img src=http://www.samsungitts.com/img/ptmail/blank.gif width=1 height=1></td></tr>");

                                      sbMessage.append("<tr><td height=43 bgcolor=#0A79A8><img src=http://www.samsungitts.com/img/ptmail/logo.gif width=127 height=43></td></tr></table>");

                                     

                                      sbMessage.append("<table width=700 border=0 cellspacing=0 cellpadding=0>");

                                      sbMessage.append("<tr><td width=551 class=text><Br><br><p>Dear,  "+rs_0.getString("USER_ID")+"</p><br><br>");

                                                                             

                                      sbMessage.append("Please, check the documents below.<br>");

                                      sbMessage.append("<b>*Total number of new invoices - <b>"+cnt_new+"</b> cases<br> </b>");

                                      sbMessage.append("<b>*Total number of changed invoices - <b>"+cnt_change+"</b> cases<br> </b>");

                                      sbMessage.append("<b>*Total number of canceled invoices - <b>"+cnt_cancel+"</b> cases<br> </b>");

                                                                           

                                      sbMessage.append("Please check it at <a href=http://www.samsungitts.com target=_blank>http://www.samsungitts.com</a></p> <br></p></td>");

                                      sbMessage.append("<td width=149 valign=top><img src=http://www.samsungitts.com/img/ptmail/img.jpg width=160 height=172></td></tr></table>");

 

                                      sbMessage.append("<table width=700 border=0 cellspacing=0 cellpadding=0><tr><td><p><b>New Invoice</b> (You have not opened or printed.)</p></td></tr></table>");

                                      sbMessage.append("<table width=700 border=0 cellspacing=0 cellpadding=0><tr><td bgcolor=#669900>");

                                      sbMessage.append("<table width=700 border=0 cellspacing=1 cellpadding=0>");

                                      sbMessage.append("<tr align=center bgcolor=#BBDD75>");

                                      sbMessage.append("<td height=20><strong>I/V No.</strong></td>");

                                      sbMessage.append("<td height=20><strong>B/L No.</strong></td>");

                                      sbMessage.append("<td height=20><strong>Consignee</strong></td>");

                                      sbMessage.append("<td height=20><strong>Trans Type</strong></td>");

                                      sbMessage.append("<td height=20><strong>ETA</strong></td>");

                                      sbMessage.append("<td height=20><strong>ETD</strong></td></tr>");          

                                     

                                      StringBuffer sbSql_1= new StringBuffer(); // New Mail Send

                                     

                                      sbSql_1.append(" SELECT INV_NO, BILLNO_,  GETCONSIGNAME(CONSIG) CONSIG_NAME,  TRSTYP, MLDTUE, GI_DAT FROM INV_DOC A, USER_INFO U WHERE  USER_ID=? AND SHIPPE <> 'C100' AND A.SHIPPE = U.DEALING_SUBSIDIARY_CODE AND A.MLCARI = U.COMPANY_CODE AND MLCARI <> 'SITA' AND SHIPPE <> 'C100' AND TRSTYP = 'AIR' AND A.RCDFLG = 'A' AND A.UPDDATE = '99991231235959' AND MAIL_STS='F'  ");

                      

                                      Connection conn_9 = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                                      PreparedStatement pstmt_9 = conn_9.prepareStatement(sbSql_1.toString());

                                      pstmt_9.setString(1, rs_0.getString("USER_ID")); //순차적으로 조건을 셋팅

                                     

                                      ResultSet rs_9 = pstmt_9.executeQuery();

                                     

                                      ok1 = false;

                                      while(rs_9.next()){

                                                 ok1 = true;        

                                                     sbMessage.append("<tr align=center bgcolor=#BBDD75>");

                                                     sbMessage.append("<td height=20>"+rs_9.getString("INV_NO")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_9.getString("BILLNO_")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_9.getString("CONSIG_NAME")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_9.getString("TRSTYP")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_9.getString("MLDTUE")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_9.getString("GI_DAT")+"</td>");

                                                     sbMessage.append("</tr>");    

                                                    

                                                     //Update

                                                     StringBuffer sb_update0 = new StringBuffer();

                                                     sb_update0.append (" UPDATE INV_DOC SET MAIL_STS='T' WHERE INV_NO=?  ");

                                                     Connection con_update0 = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                                                     PreparedStatement pstmt_update0 = con_update0.prepareStatement(sb_update0.toString());

                                                     pstmt_update0.setString(1, rs_9.getString("INV_NO"));

                                                     ResultSet rs_update0 = pstmt_update0.executeQuery();

                                                     con_update0.commit();

                                                     rs_update0.close();

                                                     pstmt_update0.close();

                                                     con_update0.close();                                         

                                                    

                                      }// 9 For

                                      pstmt_9.close();

                                      rs_9.close();

                                      conn_9.close();

                                      //데이타 없을때 처리

                                      if(!ok1){

                                                     sbMessage.append("<tr bgcolor=#ffffff><td colspan=6><center><b>No Data Found</b></center></td></tr>");

                                      }

                                     

                                      sbMessage.append("</table></table><br><br>");

 

                                      sbMessage.append("<table width=700 border=0 cellspacing=0 cellpadding=0><tr><td><p><b>Changed Invoice</b></p></td></tr></table>");

                                      sbMessage.append("<table width=700 border=0 cellspacing=0 cellpadding=0><tr><td bgcolor=#669900>");

                                      sbMessage.append("<table width=700 border=0 cellspacing=1 cellpadding=0>");

                                      sbMessage.append("<tr align=center bgcolor=#BBDD75>");

                                      sbMessage.append("<td height=20><strong>I/V No.</strong></td>");

                                      sbMessage.append("<td height=20><strong>B/L No.</strong></td>");

                                      sbMessage.append("<td height=20><strong>Consignee</strong></td>");

                                      sbMessage.append("<td height=20><strong>Trans Type</strong></td>");

                                      sbMessage.append("<td height=20><strong>ETA</strong></td>");

                                      sbMessage.append("<td height=20><strong>ETD</strong></td></tr>");

 

                                      StringBuffer sbSql_2= new StringBuffer(); //Changed Mail Send

                                     

                                      sbSql_2.append(" SELECT INV_NO, BILLNO_,  GETCONSIGNAME(CONSIG) CONSIG_NAME,  TRSTYP, MLDTUE, GI_DAT FROM INV_DOC A, USER_INFO U WHERE  USER_ID=? AND SHIPPE <> 'C100' AND A.SHIPPE = U.DEALING_SUBSIDIARY_CODE AND A.MLCARI = U.COMPANY_CODE AND MLCARI <> 'SITA' AND SHIPPE <> 'C100' AND TRSTYP = 'AIR' AND A.RCDFLG = 'A' AND A.UPDDATE <> '99991231235959' AND MAIL_STS='F'  ");

                                     

                                      Connection conn_10 = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                                      PreparedStatement pstmt_10 = conn_10.prepareStatement(sbSql_2.toString());

                                      pstmt_10.setString(1, rs_0.getString("USER_ID")); //순차적으로 조건을 셋팅

                                     

                                      ResultSet rs_10 = pstmt_10.executeQuery();

                                     

                                      ok2 = false ;

                                      while(rs_10.next()){

                                                     ok2 = true;                                                                                                                              

                                                     sbMessage.append("<tr align=center bgcolor=#BBDD75>");

                                                     sbMessage.append("<td height=20>"+rs_10.getString("INV_NO")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_10.getString("BILLNO_")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_10.getString("CONSIG_NAME")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_10.getString("TRSTYP")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_10.getString("MLDTUE")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_10.getString("GI_DAT")+"</td>");

                                                     sbMessage.append("</tr>");

                                                     //Do Update    

                                                     System.out.println("Change Invoice Number :::: "+rs_10.getString("INV_NO"));

                                                     StringBuffer sb_update1 = new StringBuffer();

                                                     sb_update1.append (" UPDATE INV_DOC SET MAIL_STS='T' WHERE INV_NO=?  ");

                                                     Connection con_update1 = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                                                     PreparedStatement pstmt_update1 = con_update1.prepareStatement(sb_update1.toString());

                                                     pstmt_update1.setString(1, rs_10.getString("INV_NO"));

                                                     ResultSet rs_update1 = pstmt_update1.executeQuery();

                                                     con_update1.commit();

                                                     rs_update1.close();

                                                     pstmt_update1.close();

                                                     con_update1.close();                                                 

                                                    

                                      } // 10 For

                                      pstmt_10.close();

                                      rs_10.close();

                                      conn_10.close();

                                      if(!ok2){

                                                     sbMessage.append("<tr bgcolor=#ffffff><td colspan=6><center><b>No Data Found</b></center></td></tr>");

                                      }                                            

                                     

                                      sbMessage.append("</table></table><br><br>");

                                     

                                      sbMessage.append("<table width=700 border=0 cellspacing=0 cellpadding=0><tr><td><p><b>Canceled Invoice</b> (The shipper has canceled.)</p></td></tr></table>");

                                      sbMessage.append("<table width=700 border=0 cellspacing=0 cellpadding=0><tr><td bgcolor=#669900>");

                                      sbMessage.append("<table width=700 border=0 cellspacing=1 cellpadding=0>");

                                      sbMessage.append("<tr align=center bgcolor=#BBDD75>");

                                      sbMessage.append("<td height=20><strong>I/V No.</strong></td>");

                                      sbMessage.append("<td height=20><strong>B/L No.</strong></td>");

                                      sbMessage.append("<td height=20><strong>Consignee</strong></td>");

                                      sbMessage.append("<td height=20><strong>Trans Type</strong></td>");

                                      sbMessage.append("<td height=20><strong>ETA</strong></td>");

                                      sbMessage.append("<td height=20><strong>ETD</strong></td></tr>");

 

                                      StringBuffer sbSql_3= new StringBuffer(); //Canceled Mail Send

                                     

                                      sbSql_3.append(" SELECT INV_NO, BILLNO_,  GETCONSIGNAME(CONSIG) CONSIG_NAME,  TRSTYP, MLDTUE, GI_DAT FROM INV_DOC A, USER_INFO U WHERE   SHIPPE <> 'C100' AND A.SHIPPE = U.DEALING_SUBSIDIARY_CODE AND A.MLCARI = U.COMPANY_CODE AND MLCARI <> 'SITA' AND SHIPPE <> 'C100' AND TRSTYP = 'AIR' AND A.RCDFLG = 'D' AND MAIL_STS='F' AND USER_ID=?   ");

                                     

                                     

                                      Connection conn_11 = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                                      PreparedStatement pstmt_11 = conn_11.prepareStatement(sbSql_3.toString());

                                      pstmt_11.setString(1, rs_0.getString("USER_ID")); //순차적으로 조건을 셋팅

                                     

                                      ResultSet rs_11 = pstmt_11.executeQuery();

                                     

                                      ok3 = false ;

                                      while(rs_11.next()){

                                                     ok3 = true;    

                                                     sbMessage.append("<tr align=center bgcolor=#BBDD75>");

                                                     sbMessage.append("<td height=20>"+rs_11.getString("INV_NO")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_11.getString("BILLNO_")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_11.getString("CONSIG_NAME")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_11.getString("TRSTYP")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_11.getString("MLDTUE")+"</td>");

                                                     sbMessage.append("<td height=20>"+rs_11.getString("GI_DAT")+"</td>");

                                                     sbMessage.append("</tr>");

                                                     //Do Update                                  

                                                     StringBuffer sb_update2 = new StringBuffer();

                                                     sb_update2.append (" UPDATE INV_DOC SET MAIL_STS='T' WHERE INV_NO=?  ");

                                                     Connection con_update2 = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                                                     PreparedStatement pstmt_update2 = con_update2.prepareStatement(sb_update2.toString());

                                                     pstmt_update2.setString(1, rs_11.getString("INV_NO"));

                                                     ResultSet rs_update2 = pstmt_update2.executeQuery();

                                                     con_update2.commit();

                                                     rs_update2.close();

                                                     pstmt_update2.close();

                                                     con_update2.close();                                                 

                                                    

                                      } // 11 For

                                      pstmt_11.close();

                                      rs_11.close();

                                      conn_11.close();

                                      if(!ok3){

                                                     sbMessage.append("<tr bgcolor=#ffffff><td colspan=6><center><b>No Data Found</b></center></td></tr>");

                                      }                             

                                     

                                      sbMessage.append("</table></table><br><br>");

                                     

                                      sbMessage.append("</table></table><br></td></tr></table><br>");

                                      sbMessage.append("<table width=700 border=0 cellspacing=0 cellpadding=0>");  

                                      sbMessage.append("<tr><td height=1 bgcolor=#CDCDCD><img src=http://www.samsungitts.com/img/ptmail/blank.gif width=1 height=1></td>      </tr>");

                                      sbMessage.append("<tr><td align=center>Copyright(c)2003 Samsung Electronics co.,Ltd. All rights reserved.</td></tr></table> </body></html>");

                                                            

                                      contents = sbMessage.toString();                             

                                     

                                      //System.out.println("내용 확인 222222222222 "+contents);

                                      if(ok1 || ok2 || ok3){

                                             StringTokenizer stTo = new StringTokenizer(to, ";");

                                             while(stTo.hasMoreTokens()){

                                                    

                                                     to = stTo.nextToken();

                                                     //메일 클래스 객체 생성

                                                     Properties prop = System.getProperties();      

                                                     Session session = Session.getInstance(prop, null);    

                                                     MimeMessage message = new MimeMessage(session);

                                                     message.setFrom(new InternetAddress(name+"<"+from+">"));

                                                     message.addRecipient(Message.RecipientType.TO,new InternetAddress(to));    

                                                     message.setSubject(title);

                                                     message.setContent(contents,"text/html;charset=euc-kr");

                                                     //message.setText(contents);

                                                    

                                                     Transport transport = session.getTransport("smtp");

                                                     transport.connect(host,"","");    

                                                     transport.sendMessage(message, message.getAllRecipients());

                                                     transport.close();

                                                     LogMaker lm = new LogMaker();

                                                     lm.println(logDir+File.separator+"AirforwarderMailNCC", to);

                                                                                                 

                                             }

                                             ++cnt_total;

                                             System.out.println("Total Mail 갯수 "+cnt_total); 

                                      }else{

                                                     System.out.println("메일을 보내지 않는다");

                                                     System.out.println();

                                      }                                                                  

                              } //Mail For         

                       Thread.currentThread().sleep(50);

                       }catch(ClassNotFoundException e){

                              e.printStackTrace();

                       }catch(SQLException e){

                              e.printStackTrace();

                       }catch(Exception e){

                              e.printStackTrace();

                       }finally{

                              try{

                                      if(pstmt_0 != null){pstmt_0.close();}

                                      if(rs_0 != null){rs_0.close();}

                                      if(conn_0 != null){conn_0.close();}

                                     

                              }catch(Exception e){

                                      e.printStackTrace();

                              }

                       }

        }

}

 

#####################################################################

 

#####################################################################

42. 날짜 체크하는 메소드 만들기

#####################################################################

import java.util.*;

import java.text.*;

public class DateTest

{             

        public String checkDate(String input){                        

               String reVal = null;           

               //리턴 타입을 설정한다

               try

               {             

                       if ( input == null || input.length() != 8 )

                       {

                              return input;

                              //OR의 우선순위는 앞에 있슴. 에러사항을 체크하여 Return을 한다

                       }             

                       Calendar cl = Calendar.getInstance();

                       int yyyy = Integer.parseInt(input.substring(0, 4));

                       int month = Integer.parseInt(input.substring(4, 6));

                       int day = Integer.parseInt(input.substring(6));

                       //입력값에 대해서 substring으로 나누어 가져온다

                       cl.set(yyyy, month-1, day);

                       //Calendar에 년월일을 셋팅한다

                       /*

                       int yy = cl.get(Calendar.YEAR);

                       int mm = cl.get(Calendar.MONTH)+1;

                       int dd = cl.get(Calendar.DATE);                

                       System.out.println(yy +", "+mm+", "+dd);

                       */                    

                       String date_format = "yyyyMMdd";

                       SimpleDateFormat simpledateformat = new SimpleDateFormat(date_format);     

                       reVal = simpledateformat.format(cl.getTime());                                        

               }catch (Exception e)

               {      

                       e.printStackTrace();

                       return input;

               }

               return reVal;

               //try catch 구분 밖에서 Return 타입을 받는다

        }

        public static void main(String[] args)

        {

               DateTest dt = new DateTest();

               System.out.println( dt.checkDate("20050200") );

               //객체를 생성하여 메소드를 호출한다

        }      

}

 

#####################################################################

41. 버블 소트와 중복체크 예제

#####################################################################

import java.util.*;

public class RandomTest

{

        public static void main(String[] args)

        {

               Random random = new Random();

               boolean[] boo = new boolean[45];

               int[] choo = new int[6];

               for (int i=0; i<boo.length ;  )

               {

                       boo[i] = true;

               }

               for (int i=0; i<choo.length ; i++)

               {

                       int num = random.nextInt(45);

                       if (boo[num])

                       {

                              boo[num] = false;

                choo[i] = num+1;

                       }                             

                       System.out.println(choo[i]);

               }

        }

}

class SortTest

{

        public static void main(String[] args)

        {

               String[] str = {"Orange","Apple","Grape","Peach","Lemon"};

               System.out.println("@@@@@ 정렬 전 @@@@@");

               for (int i=0; i<str.length; i++)

               {

                       System.out.println(str[i]);

               }

               System.out.println("@@@@@ 정렬 후 @@@@@");

               for (int j=0; j<str.length ; j++ )

               {

                       for (int i= j+1 ; i<str.length ; i++)

                       {

                              if ( str[i].compareTo(str[j]) < 0 )

                              {

                                      String temp = str[j];

                                      str[j] = str[i];

                                      str[i] = temp;  

                              }

                       }

                       System.out.println(str[j]);                                     

               }

        }

}

#####################################################################

 

#####################################################################

40. 클래스를 이용하는 샘플예제 소스

#####################################################################

class TopScret

{

        private int scret;

        public void setScret(int x){

               scret = x;

        }

        public int getScret(){

               return scret;

        }

}

public class TopScretMain

{

        public static void main(String[] args){

       

               TopScret top = new TopScret();

               top.setScret(4000);

               int i = top.getScret();

               System.out.println("i ->"+i);

               System.out.println("top.getScret() ->"+top.getScret());

        }

};

 

class MeanCalc

{

        private int sum;

        private int num;

        public void setValue(int x, int y, int z, int n){

               sum = x * y * z;

               num = n;

        }

        public int getValue(){

               return sum - num;

        }

}

public class MeanCalcMain

{

        public static void main(String[] args){

               MeanCalc cal = new MeanCalc();

               cal.setValue(100, 40, 500, 6);

               int z = cal.getValue();

               System.out.println("z ->"+z);

               System.out.println("cal ->"+cal);

        }

};

 

class MotorCycle

{

        private int id;

        private int speed;

        public void setData(int x, int y){

               id = x;

               speed = y;

        }

        public void drive(){

               System.out.println("id ->"+id);

               System.out.println("speed ->"+speed);

        }

}

public class MotorCycleMain

{

        public static void main(String[] args){

               MotorCycle m = new MotorCycle();

               m.setData(5384, 120);

               m.drive();

               System.out.println("m ->"+m);

 

               MotorCycle c = m;

               c.setData(7777, 100);

               c.drive();

               System.out.println("c ->"+c);

        }

};

#####################################################################

 

 

#####################################################################

39. 쿠키 셋팅에 관한 예제

#####################################################################

<form method="post" action="setCookie.jsp">

<input type="text" name="id"><br>

<input type="password" name="passwd"><br>

<input type="submit"><input type="reset">

 

<%@ page contentType="text/html;charset=euc-kr"%>

<%

        String id = request.getParameter("id");

        String passwd = request.getParameter("passwd");

        String value = request.getHeader("Cookie");

        if (value == null){

               Cookie cookie = new Cookie(id, passwd);

               cookie.setMaxAge(60);

               response.addCookie(cookie);

               out.println("Setting Cookie<br><hr>");

               out.println("id :"+id+ "<br>passwd :"+passwd);

 

        }else{

               out.println("Have done Cookie<br><hr>");      

               out.println("id :"+id+ "<br>passwd :"+passwd+"<br>");

               Cookie[] cookies = request.getCookies();

               for (int i=0; i<cookies.length; i++){

                       out.println("name :"+cookies[i].getName()+"<br>");

                       out.println("value :"+cookies[i].getValue()+"<br>");

                       out.println("domain :"+cookies[i].getDomain()+"<br>");

                       out.println("maxage :"+cookies[i].getMaxAge()+"<br>");

                       out.println("path :"+cookies[i].getPath()+"<br>");

                       out.println("secure :"+cookies[i].getSecure()+"<br>");

                       out.println("comment :"+cookies[i].getComment()+"<br>");

                       out.println("version :"+cookies[i].getVersion()+"<br>");

               }

 

        }

%>

#####################################################################

 

#####################################################################

38. 로긴 및 서블릿 관련 소스(한글처리)

#####################################################################

<BODY onContextmenu="return false">

<form name="login" action="/ServletJsp/servlet/loginServlet" method="post">

        <table border=1 cellspacing="0" cellpadding="0">

               <tr>

                       <td>User ID  </td>

                       <td><input type="text" name="id" ></td>

               </tr>

               <tr>

                       <td>Password  </td>

                       <td><input type="password" name="passwd"></td>

               </tr>

               <tr>

                       <td colspan=2 align=center>

                       <input type="submit" value="CONFIRM">

                       <input type="reset" value="CANCEL">

                       </td>

               </tr>

        </table>

</form>

</BODY>

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

import util.HangulConversion;

public class loginServlet extends HttpServlet

{

        public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException

        {             

               //res.setContentType("text/html;charset=euc-kr");// 필히 설정함

               //PrintWriter out = res.getWriter();

               res.setContentType("text/html");        

               PrintWriter out = new PrintWriter( new OutputStreamWriter(res.getOutputStream(), "KSC5601"), true );

               String userid = HangulConversion.toKor(req.getParameter("id")); //util 팩키지에서 메소드 이용

               String passwd = req.getParameter("passwd");

               out.println("<html><body>");          

               if ( userid.equals("") && passwd.equals(""))

               {

                       out.println("NOWHERE");

               }else{         

                       out.println("INPUT ID IS :"+ userid +"<br>");

                       out.println("INPUT PASSWORD IS :"+passwd);

               }

               out.println("<p><font size=2><a href=javascript:history.back()>BACK TO THE FUTURE</a></font>");

               out.println("<body></html>");

               out.close();

        }

}

package util;

import java.io.*;

public class HangulConversion

{

        public static String toEng(String ko){

               if (ko ==null)

               {

                       return null;

               }

               try

               {

                       return new String(ko.getBytes("KSC5601"), "8859_1");

               }

               catch (Exception e)

               {

                       return ko;

               }

        }

        public static String toKor(String en){

               if (en == null)

               {

                       return null;

               }

               try

               {

                       return new String(en.getBytes("8859_1"), "KSC5601");

               }

               catch (Exception e)

               {

                       return en;

               }

        }

}

#####################################################################

37. 세션관련 정리 소스

#####################################################################

<form action="sessionCreate.jsp" method="post">

<table border="1" cellspacing=0 cellpadding=0 >

        <tr>

               <td> IDENTIFICATION </td>

               <td><input type=textfield name=id></td>

        </tr>

        <tr align=center>

               <td colspan=2><input type="submit" value="Confirm"><input type="reset" value="Cancel"></td>

        </tr>

<table>

</form>

<%@ page contentType="text/html;charset=euc-kr" session="false"%>

<%@ page import="util.HangulConversion"%>

<html>

<body>

Session Creation<br>

<%

        String id = HangulConversion.toKor(request.getParameter("id"));

        if (id.equals("")){              

               out.println("<hr>No Data<br>");

               out.println("<a href=javascript:history.back()>Back To The Future</a>");

               //response.sendRedirect("sessionLogin.html");

                return;

        }

        HttpSession session = request.getSession(true);

        if(session.isNew()){

               session.setAttribute("id", id); //세션을 설정한다

               out.println("<hr>New session is created :"+ id+"<br>");

               out.println("Session ID :"+session.getId()+"<br>");

               out.println("<a href=sessionRead.jsp>[Get Session]</a><br>");

        }else{

               out.println("<hr>Already exist session<br>");

               out.println("Input Id :"+id +"<br>");

               out.println("Session ID :"+session.getId()+"<br>"); //기존에 만든 세선 Id를 가져온다

               out.println("<a href=sessionRead.jsp>[Get Session]</a>");

        }

%>

</body>

</html>

<%@ page contentType="text/html;charset=euc-kr" session="false"%>

<html>

<body>

<%

        HttpSession session = request.getSession(false);

        String s = (String)session.getAttribute("id");

        if (s == null){

               out.println("Not Session");     

        }else{

               out.println("Attribute id is :"+s+"<br>");

        }

%>

<hr>

isNew() :<%= session.isNew()%><br>

Session ID :<%= session.getId()%><br>

Session Create Time :<%= new java.util.Date(session.getCreationTime()).toString()%><br>

Session Last Time&nbsp;&nbsp;:<%= new java.util.Date(session.getLastAccessedTime()).toString()%><br>

Session Active Time :<%= session.getMaxInactiveInterval()%> sec<br>

<a href="sessionDelete.jsp">[Session Delete]</a> <a href="sessionLogin.html">[Login Page]</a>

</body>

</html>

<%@ page contentType="text/html;charset=euc-kr" session="false" %>

<%

        HttpSession session = request.getSession(false);

        String s = (String)session.getAttribute("id");

        out.println("Id :"+s+"<br><hr>");

        if (session != null){

               session.invalidate();

        }

%>

Delete Session<br>

<a href="sessionLogin.html">Login Page</a>

 

#####################################################################

36. 메일을 발송하는 프로그램 소스

#####################################################################

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.Hashtable;

import java.util.Properties;

import javax.mail.Message;

import javax.mail.Session;

import javax.mail.Transport;

import javax.mail.internet.InternetAddress;

import javax.mail.internet.MimeMessage;

public class SendMail {

        /**

         * @param pMAIL

         * @param conn

         */

        public Properties sendCronMail(Properties pARGS, Connection conn) {

               Properties pRETURN = new Properties();

               pRETURN.setProperty("RESULT", "true");

               String sMAIL_SMTP_HOST = pARGS.getProperty("MAIL_SMTP_HOST", "203.254.221.26");

               String sEMAIL_ADDRESS_FROM = pARGS.getProperty("EMAIL_ADDRESS_FROM", "swbupload@samsung.com"); // 보내는 사람 주소

               String sEMAIL_ADDRESS_TO = pARGS.getProperty("EMAIL_ADDRESS_TO", "swbupload@samsung.com"); // 받는 사람 주소

               String sSUBJECT = pARGS.getProperty("SUBJECT", "CronMail Error..."); // 타이틀

               String sTEXT = pARGS.getProperty("TEXT", "CronMail Error!!!"); // 내용

               try {

                       Properties pMAIL = System.getProperties();

                       pMAIL.put("mail.smtp.host", sMAIL_SMTP_HOST);

                       Session sMAIL_SESSION = Session.getDefaultInstance(pMAIL, null);

                       if (sMAIL_SESSION == null) {

                              pRETURN.setProperty("RESULT", "false");

                              pRETURN.setProperty("MSG", "Mail Session can't be created...");

                              return pRETURN;

                       }

                       MimeMessage reply = new MimeMessage(sMAIL_SESSION);

                       reply.setFrom(new InternetAddress(sEMAIL_ADDRESS_FROM));

                       reply.setSubject(sSUBJECT);

                       reply.setText(sTEXT);

                       reply.addRecipient(Message.RecipientType.TO, new InternetAddress(sEMAIL_ADDRESS_TO));

                       Transport.send(reply);

                       StringBuffer sbSQL = new StringBuffer();

                       sbSQL.append(" UPDATE SENDMAIL SET SENDDATE = TO_CHAR(SYSDATE, 'YYYYMMDDHH24MISS') WHERE MAIL_ID = ?");

                       conn.setAutoCommit(false);

                       PreparedStatement pstmt = conn.prepareStatement(sbSQL.toString());

                       pstmt.setString(1, pARGS.getProperty("MAIL_ID", "1"));

                       pstmt.executeUpdate();

                       conn.commit();

               } catch (Exception e) {

                       pRETURN.setProperty("RESULT", "false");

                       pRETURN.setProperty("MSG", "sendCronMail(Properties pARGS) Error : " + e.toString());

               }

               return pRETURN;

        }

        public static void main(String[] args) {

               String sDB_URL = "jdbc:oracle:thin:@165.213.251.202:1521:itts";

               String sDB_USER = "itts";

               String sDB_PASSWORD = "skan00";

               Connection conn = null;

               PreparedStatement pstmt = null;

               ResultSet rs = null;

               try {

                       Class.forName("oracle.jdbc.driver.OracleDriver");

                       conn = DriverManager.getConnection(sDB_URL, sDB_USER, sDB_PASSWORD);

                       if (conn != null) {

                              StringBuffer sbSQL = new StringBuffer();

                              sbSQL.append(" SELECT MAIL_ID, MAIL_FROM, MAIL_TO, MAIL_SUBJECT, MAIL_BODY FROM SENDMAIL WHERE SENDDATE = '99991231235959'");

                              pstmt = conn.prepareStatement(sbSQL.toString());

                              rs = pstmt.executeQuery();

                              Hashtable htSENDMAIL = new Hashtable();

                              for (int i = 0; rs.next(); i++) {

                                      Properties pSendCronMail_ARGS = new Properties();

                                      pSendCronMail_ARGS.setProperty("MAIL_ID", rs.getString("MAIL_ID"));

                                      pSendCronMail_ARGS.setProperty("EMAIL_ADDRESS_FROM", rs.getString("MAIL_FROM"));

                                      pSendCronMail_ARGS.setProperty("EMAIL_ADDRESS_TO", rs.getString("MAIL_TO"));

                                      pSendCronMail_ARGS.setProperty("SUBJECT", rs.getString("MAIL_SUBJECT"));

                                      pSendCronMail_ARGS.setProperty("TEXT", rs.getString("MAIL_BODY"));

                                      htSENDMAIL.put(new Integer(i), pSendCronMail_ARGS);

                              }

                              rs.close();

                              pstmt.close();

                              SendMail cm = new SendMail();

                              for (int i = 0; i < htSENDMAIL.size(); i++) {

                                      cm.sendCronMail((Properties) htSENDMAIL.get(new Integer(i)), conn);

                              }

                       }

               } catch (ClassNotFoundException e) {

                       System.err.println(e.toString());

               } catch (SQLException e) {

                       System.err.println(e.toString());

               } finally {

                       try {

                              conn.close();

                       } catch (SQLException e1) {

                              System.err.println(e1.toString());

                       }

               }

        }

}

#####################################################################

35. 원도우 배치 프로그램 실행하기

#####################################################################

xcopy /s /e /y C:\Tomcat4\webapps\itts\paperless\fileupload e:\paperless\fileupload

 

move /Y C:\Tomcat4\webapps\itts\paperless\swbTempDir\*.* D:\PT-LOG\swbTempDir\

move /Y C:\Tomcat4\webapps\itts\paperless\mProSystem\log\*.* D:\PT-LOG\mProSystem\

move /Y C:\Tomcat4\webapps\itts\paperless\logs\*.* D:\PT-LOG\logs\

 

#####################################################################

34. 특정 폴더에 쌓이는 파일을 없애는 소스

#####################################################################

 

배치 프로그램을 돌리면서 로그파일을 남김.

java paperless.cron.TempPdfDelete e:\\pdf_temp 1 >> PtPDF_TEMP_DIR_DEL.log

 

import java.io.File;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.Properties;

public class TempPdfDelete {

        public static void main(String[] args) {

               TempPdfDelete td = new TempPdfDelete();             

               String sTRASH_LOCATION = "e:\\pdf_temp";

               String sTRASH_DELETE_DAY = "1";           

               if( args.length == 2 ) {

                       sTRASH_LOCATION = args[0];

                       sTRASH_DELETE_DAY = args[1];

               }

               Properties pARGS = new Properties();

               pARGS.setProperty("TRASH_LOCATION", sTRASH_LOCATION);

               pARGS.setProperty("TRASH_DELETE_DAY", sTRASH_DELETE_DAY);

               Properties pRETURN = td.trashDelete(pARGS);

               SimpleDateFormat sdfTODAY = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

               Date dTODAY = new Date();           

               System.out.println("[" + sdfTODAY.format(dTODAY) + "] '"+sTRASH_LOCATION+"' Directory Deletion Started...");

               for(int i=0; i<Integer.parseInt(pRETURN.getProperty("DELETED_TRASH_FILE_COUNT")); i++) {

                       dTODAY = new Date();

                       System.out.println("[" + sdfTODAY.format(dTODAY) + "] " + pRETURN.getProperty("#" + i, "Deleted File Name not found") + " was deleted");

               }

               dTODAY = new Date();

               System.out.println("[" + sdfTODAY.format(dTODAY) + "] '"+sTRASH_LOCATION+"' Directory Deletion Ended...");

        }

        /**

         * @param pARGS

         * @return

         */

        private Properties trashDelete(Properties pARGS) {

               Properties pRETURN = new Properties();

               pRETURN.setProperty("RESULT", "true");

               String sTRASH_LOCATION = pARGS.getProperty("TRASH_LOCATION", "e:\\pdf_temp");

               if (sTRASH_LOCATION.trim().equals("")) {

                       pRETURN.setProperty("RESULT", "true");

                       pRETURN.setProperty("MSG", "TRASH_LOCATION is null...");

                       return pRETURN;

               }

               long lTRASH_DELETE_DAY = Long.parseLong(pARGS.getProperty("TRASH_DELETE_DAY", "0")) * 24 * 60 * 60 * 1000;

               File fTRASH_LOCATION = new File(sTRASH_LOCATION);

               int iDELETED_TRASH_FILE_COUNT = 0;

               if (fTRASH_LOCATION.isDirectory()) {

                       File[] fofTRASH_LOCATION_FILES = fTRASH_LOCATION.listFiles();

                       Date dTODAY = new Date();

                       for (int i = 0; i < fofTRASH_LOCATION_FILES.length; i++) {

                              long lTRASH_FILE_AGE = dTODAY.getTime() - fofTRASH_LOCATION_FILES[i].lastModified();

                              if (fofTRASH_LOCATION_FILES[i].isFile() && lTRASH_FILE_AGE >= lTRASH_DELETE_DAY) {

                                      if (fofTRASH_LOCATION_FILES[i].delete()) {

                                             pRETURN.setProperty("#" + String.valueOf(iDELETED_TRASH_FILE_COUNT), fofTRASH_LOCATION_FILES[i].getName());

                                             iDELETED_TRASH_FILE_COUNT++;

                                      }

                              }

                       }

               }

               pRETURN.setProperty("DELETED_TRASH_FILE_COUNT", String.valueOf(iDELETED_TRASH_FILE_COUNT));

               return pRETURN;

        }

}

#####################################################################

33. 암호와 알고리즘을 사용후 복호화 하는 소스와 적용예

#####################################################################

import javax.crypto.*;

import javax.crypto.spec.SecretKeySpec;

import javax.crypto.spec.DESKeySpec;

import javax.crypto.spec.IvParameterSpec;

import java.security.*;

import sun.misc.BASE64Decoder;

import sun.misc.BASE64Encoder;

public class AlgoDES {

    static byte[] seedKeyData = new byte[24];

    public AlgoDES() {

        seedKeyData[0] = 0x01;

        seedKeyData[1] = 0x02;

        seedKeyData[2] = 0x03;

        seedKeyData[3] = 0x04;

        seedKeyData[4] = 0x05;

        seedKeyData[5] = 0x06;

        seedKeyData[6] = 0x07;

        seedKeyData[7] = 0x08;

        seedKeyData[8] = 0x09;

        seedKeyData[9] = 0x10;

        seedKeyData[10] = 0x11;

        seedKeyData[11] = 0x12;

        seedKeyData[12] = 0x13;

        seedKeyData[13] = 0x14;

        seedKeyData[14] = 0x15;

        seedKeyData[15] = 0x16;

        seedKeyData[16] = 0x01;

        seedKeyData[17] = 0x02;

        seedKeyData[18] = 0x03;

        seedKeyData[19] = 0x04;

        seedKeyData[20] = 0x05;

        seedKeyData[21] = 0x06;

        seedKeyData[22] = 0x07;

        seedKeyData[23] = 0x08;

    }

    public static String encTripleDES(String beforeStr) {

        String afterStr = "";

        try {

            SecretKeySpec desKey = new SecretKeySpec(seedKeyData, "DESede"); //seedKeyData로 암호화

            Cipher desCipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");

            byte[] cleartext = beforeStr.getBytes();

            desCipher.init(Cipher.ENCRYPT_MODE, desKey);

            byte[] ciphertext = desCipher.doFinal(cleartext);

            afterStr = (new BASE64Encoder()).encode(ciphertext);

        } catch (Exception e) {

            e.printStackTrace();

        }

        return afterStr;

    }

    public static String decTripleDES(String beforeStr) {        

        String afterStr = "";

        try {

            SecretKeySpec desKeyD = new SecretKeySpec(seedKeyData, "DESede");

            Cipher desCipherD = Cipher.getInstance("DESede/ECB/PKCS5Padding");

            desCipherD.init(Cipher.DECRYPT_MODE, desKeyD);

            byte[] decodedBytes = (new BASE64Decoder()).decodeBuffer(beforeStr);

            byte[] cleartext1 = desCipherD.doFinal(decodedBytes);

            afterStr = new String(cleartext1);

        } catch (Exception e) {

            e.printStackTrace();

        }

        return afterStr;

    }

        public static void main(String args[]) {         

               try{

                       if (args.length > 0) {

                              for (int i = 0; i < args.length; i++) {

                                      String s = AlgoDES.encTripleDES(args[i]);

                                      String s1 = AlgoDES.decTripleDES(s);

                                      System.out.println("Original ["+args[i]+"]");

                                      System.out.println("encTripleDES ["+s+"]");

                                      System.out.println("decTripleDES ["+s1+"]");

                              }

       

                       }      

               } catch (Exception e) {

                       e.printStackTrace();

               }

        }

}

/** 적용예제

        if(secu) {

               _sdbServer = AlgoDES.decTripleDES(ReadConfig.get ("DATABASE", "dbServer")) ;

        }else{

               _sdbServer = ReadConfig.get ("DATABASE", "dbServer") ;

        }

    if(secu){

               _sdbPassword = AlgoDES.decTripleDES(ReadConfig.get ("DATABASE", "dbPassword")) ;

        }else{

               _sdbPassword = ReadConfig.get ("DATABASE", "dbPassword") ;

        }

*/

#####################################################################

32. 특수문자 제거 메소드

#####################################################################

public static String removeSpecialChar(String str) {

               if (str == null || str.trim().length() == 0)

                       return "";

               StringBuffer data = new StringBuffer(str);

               StringBuffer buf = new StringBuffer();

               for (int i = 0; i < data.length(); i++) {

                       char c = data.charAt(i);

                       if (((int) c > 47 && (int) c < 58) || ((int) c > 64 && (int) c < 91) || ((int) c > 96 && (int) c < 123))

                              buf.append(c);

               }

 

               return buf.toString();

}

 

#####################################################################

31. 날짜 관련 함수 이용

#####################################################################

import java.util.*;

import java.io.*;

import java.text.*;

public class DateCheck

{

        public static void main(String[] args)

        {

               SimpleDateFormat date = new SimpleDateFormat();

               date = (SimpleDateFormat)DateFormat.getDateTimeInstance();

               long now = System.currentTimeMillis();

               System.out.println("Now  ->/"+now+"/");

               Date dd = new Date();         

               System.out.println("Date ->/"+dd+"/");

               String str = date.format(new Date(now));

               System.out.println("Time ->/"+str+"/");         

        }

}

 

 

#####################################################################

30. Random 함수로 받은 값을 중복 체크하는 소소

#####################################################################

import java.util.*;

public class RandomTest

{

        public static void main(String[] args)

        {

               Random random = new Random();

               boolean[] total = new boolean[45];

               int[] choose = new int[6];             

               for (int i=0; i<total.length ; i++ )

               {

                       total[i] = true;

               }             

               for (int i=0; i<choose.length ; i++)

               {

                       int num = random.nextInt(45);                 

                       if (total[num])

                       {

                              total[num] = false;

                              choose[i] = num+1;                                  

                       }             

                       System.out.print( choose[i] + " \n");                 

               }

        }

}

 

#####################################################################

29. 버블 소트로 데이타 정렬

#####################################################################

public class SortTest

{

        public static void main(String[] args)

        {

               String[] str = {"Orange","Apple","Grape","Peach","Lemon"};

               System.out.println("@@@@@ 정렬 전 @@@@@");

               for (int i=0; i<str.length; i++)

               {

                       System.out.println(str[i]);

               }

               System.out.println("@@@@@ 정렬 후 @@@@@");

               for (int j=0; j<str.length ; j++ )

               {

                       for (int i= j+1 ; i<str.length ; i++)

                       {

                              if ( str[i].compareTo(str[j]) < 0 ) //음수이면 들어감

                              {

                                      String temp = str[j];

                                      str[j] = str[i];

                                      str[i] = temp;  

                              }

                       }

                       System.out.println(str[j]);                                     

               }

        }

}

#####################################################################

28. MD5로 암호화 하는 알고리즘 (Base64 필요)

#####################################################################

import sun.misc.*;

import java.io.*;

public class Base64Util {      

    public Base64Util() {}

    /**

     *   Base64Encoding을 수행한다. binany in ascii out

     *

     *   @param encodeBytes encoding byte array

     *   @return encoding String

     */

    public static String encode(byte[] encodeBytes) {

        BASE64Encoder base64Encoder = new BASE64Encoder();

        ByteArrayInputStream bin = new ByteArrayInputStream(encodeBytes);

        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        byte[] buf = null;

        try{

            base64Encoder.encodeBuffer(bin, bout);

        } catch(Exception e) {

            System.out.println("Exception");

            e.printStackTrace();

        }

        buf = bout.toByteArray();

        return new String(buf).trim();

    }

    /**

     *   Base64Decoding 수행한다. binany out ascii in

     *

     *   @param strDecode decoding String

     *   @return decoding byte array

     */

    public static byte[] decode(String strDecode) {       

        BASE64Decoder base64Decoder = new BASE64Decoder();

        ByteArrayInputStream bin = new ByteArrayInputStream(strDecode.getBytes());

        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        byte[] buf = null;

        try {           

            base64Decoder.decodeBuffer(bin, bout);

        } catch(Exception e) {

            System.out.println("Exception");

            e.printStackTrace();

        }

        buf = bout.toByteArray();

        return buf;

    }

}

import java.security.*;

public class  SecurityUtil {

    /**

     *   byte[] ret = HashUtil.digest("MD5", "abcd".getBytes());

     *  처럼 호출

     */

    public static byte[] digest(String alg, byte[] input) throws NoSuchAlgorithmException {

        MessageDigest md = MessageDigest.getInstance(alg);

        return md.digest(input);

    }

    public static String getCryptoMD5String(String inputValue) throws Exception {

        if( inputValue == null ) throw new Exception("Can't conver to Message Digest 5 String value!!");

        byte[] ret = digest("MD5", inputValue.getBytes());

        String result = Base64Util.encode(ret);   

        return result;

    }

        public static void main(String[] args) throws Exception

        {

               String passwd = SecurityUtil.getCryptoMD5String("gkskfh2005");

               System.out.println("passwd :"+passwd);

        }

}

#####################################################################

 

#####################################################################

27. TripleDES 을 이용한 암호화 알고리즘

#####################################################################

import javax.crypto.*;

import javax.crypto.spec.SecretKeySpec;

import javax.crypto.spec.DESKeySpec;

import javax.crypto.spec.IvParameterSpec;

import java.security.*;

import sun.misc.BASE64Decoder;

import sun.misc.BASE64Encoder;

public class AlgoDES {

    static byte[] seedKeyData = new byte[24];

    public AlgoDES() {

        seedKeyData[0] = 0x01;

        seedKeyData[1] = 0x02;

        seedKeyData[2] = 0x03;

        seedKeyData[3] = 0x04;

        seedKeyData[4] = 0x05;

        seedKeyData[5] = 0x06;

        seedKeyData[6] = 0x07;

        seedKeyData[7] = 0x08;

        seedKeyData[8] = 0x09;

        seedKeyData[9] = 0x10;

        seedKeyData[10] = 0x11;

        seedKeyData[11] = 0x12;

        seedKeyData[12] = 0x13;

        seedKeyData[13] = 0x14;

        seedKeyData[14] = 0x15;

        seedKeyData[15] = 0x16;

        seedKeyData[16] = 0x01;

        seedKeyData[17] = 0x02;

        seedKeyData[18] = 0x03;

        seedKeyData[19] = 0x04;

        seedKeyData[20] = 0x05;

        seedKeyData[21] = 0x06;

        seedKeyData[22] = 0x07;

        seedKeyData[23] = 0x08;

    }

    public static String encTripleDES(String beforeStr) {

        String afterStr = "";

        try {

            SecretKeySpec desKey = new SecretKeySpec(seedKeyData, "DESede"); //seedKeyData로 암호화

            Cipher desCipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");

            byte[] cleartext = beforeStr.getBytes();

            desCipher.init(Cipher.ENCRYPT_MODE, desKey);

            byte[] ciphertext = desCipher.doFinal(cleartext);

            afterStr = (new BASE64Encoder()).encode(ciphertext);

        } catch (Exception e) {

            e.printStackTrace();

        }

        return afterStr;

    }

    public static String decTripleDES(String beforeStr) {        

        String afterStr = "";

        try {

            SecretKeySpec desKeyD = new SecretKeySpec(seedKeyData, "DESede");

            Cipher desCipherD = Cipher.getInstance("DESede/ECB/PKCS5Padding");

            desCipherD.init(Cipher.DECRYPT_MODE, desKeyD);

            byte[] decodedBytes = (new BASE64Decoder()).decodeBuffer(beforeStr);

            byte[] cleartext1 = desCipherD.doFinal(decodedBytes);

            afterStr = new String(cleartext1);

        } catch (Exception e) {

            e.printStackTrace();

        }

        return afterStr;

    }

        public static void main(String args[]) {         

               try{

                       if (args.length > 0) {

                              for (int i = 0; i < args.length; i++) {

                                      String s = AlgoDES.encTripleDES(args[i]);

                                      String s1 = AlgoDES.decTripleDES(s);

                                      System.out.println("Original ["+args[i]+"]");

                                      System.out.println("encTripleDES ["+s+"]");

                                      System.out.println("decTripleDES ["+s1+"]");

                              }      

                       }      

               } catch (Exception e) {

                       e.printStackTrace();

               }

        }

}

 

import java.security.*;

import javax.crypto.*;

public class TripleDes {

    public static void main(String [] args) throws Exception {

        if( args.length != 1) {

            System.out.println("Usage : java SimpleExample text ");

            System.exit(1);

        }

        String text = args[0];

        System.out.println("Generating a DESded (TripleDES) key...");

        // Triple DES 생성

        KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");

        keyGenerator.init(168);   // 키의 크기를 168비트로 초기화

        Key key = keyGenerator.generateKey();

        System.out.println("키생성이 완료되었음");

        // Cipher를 생성, 사용할 키로 초기화

        Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");

        cipher.init(Cipher.ENCRYPT_MODE, key);

        byte [] plainText = text.getBytes("UTF8");

        System.out.println("Plain Text : ");

        for (int i = 0; i < plainText.length ; i++) {

            System.out.print(plainText[i] + " ");

        }

        // 암호화 시작

        byte [] cipherText = cipher.doFinal(plainText);

        // 암호문서 출력

        System.out.println("\nCipher Text : ");

        for (int i = 0; i < cipherText.length ; i++)       {

            System.out.print(cipherText[i] + " ");

        }

        //복호화 모드로서 다시 초기화

        cipher.init(Cipher.DECRYPT_MODE, key);

        //복호화 수행

        byte [] decryptedText = cipher.doFinal(cipherText);

        String output =  new String(decryptedText, "UTF8");

        System.out.println("\nDecrypted Text : " + output);

    }

};

#####################################################################

 

#####################################################################

26. Random 클래스로 가져온 값을 중복체크하기

#####################################################################

import java.util.*;

public class RandomTest

{

        public static void main(String[] args)

        {

               Random random = new Random();

               boolean[] boo = new boolean[45];

                int[] choo = new int[6];

               for (int i=0; i<boo.length ;  )

               {

                       boo[i] = true;

               }

               for (int i=0; i<choo.length ; i++)

               {

                       int num = random.nextInt(45);

                       if (boo[num])

                       {

                              boo[num] = false;

                              choo[i] = num+1;      

                              System.out.println(choo[i]);

                       }                     

               }

        }

}

#####################################################################

 

#####################################################################

26. MS-SQL JDBC 드라이버 연결하는 방법

#####################################################################

 

다음의 사이트를 방문하여 MS-SQL2000 JDBC 드라이버를 다운로드

http://www.microsoft.com/sql/downloads/2000/jdbc.asp

적절한 위치에 설치하고 다음의 세 파일을 클래스패스에 추가 (%JAVA_HOME%\lib\ext\classes12.zip;)

설치 디렉터리/lib/msbase.jar 

설치 디렉터리/lib/msutil.jar 

설치 디렉터리/lib/mssqlserver.jar

 

MS-SQL2000, Oracle 그리고 MySQL 데이터베이스의 드라이버를 로딩하는 것을 각각 보여주고 있습니다.

Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");

Class.forName("oracle.jdbc.driver.OracleDriver");

Class.forName("org.gjt.mm.mysql.Driver");

 

 

#####################################################################

 

#####################################################################

25. 날짜 포맷 바꾸는 방법

#####################################################################

 

String ETA_END_DATE = null;

String ETA_START_DATE = null;

String date_format = "yyyy-MM-dd";

SimpleDateFormat simpledateformat = new SimpleDateFormat(date_format);             

ETA_END_DATE = simpledateformat.format(Format.getSwbEndDate());

ETA_START_DATE = simpledateformat.format(Format.getSwbStartDate());

 

#####################################################################

24. 엔터키를 두르면 검색하는 소스

#####################################################################

 

<script language="JavaScript">

function goUrl(command, _url) {

        theForm = document.searchForm;      

        if (command == 'search'  && theForm.BLNO.value != null)

               theForm.BLNO.value = theForm.BLNO.value.toUpperCase();             

        theForm.command.value=command;

        theForm.PAGE.value="1";

        theForm.action = _url;

        theForm.submit();

}

function EnterCheck()

{      

        if(event.keyCode == 13)

               goUrl('search', '/servlet/paperless.SwbListServlet');

}

</script>

<form name="searchForm" method="post" action="">

<table width="98%" border="0" cellspacing="1" cellpadding="3">

    <input type="text" name="BLNO" class="main_input" value = "<%=blno%>"size="20" __onBlur="checkBLNo(this)" onKeyDown="EnterCheck()">

        <img src="../img/btn_multifile_upload.gif" width="92" height="18" border="0"

         onmouseover="this.style.cursor='hand'"

          onClick="goUrl('','/servlet/paperless.SwbUploadServlet')"/>

        <input type="hidden" name="PROCESS_TYPE" value="CARRIER"/>

</table>

</form>

 

#####################################################################

23. 데이터베이스 커넷션하고 로그파일을 만듬

#####################################################################

package common;

import BrokerBean;

import javax.servlet.jsp.PageContext;

import java.sql.SQLException;

import java.sql.Connection;

import java.sql.PreparedStatement;

public class SystemUsageHandler {

            synchronized public static void insertLog(PageContext jspContext, String id, String menu, String func, String comment) throws SQLException, Exception

            {

                       BrokerBean brokerBean = null;

                       Connection conn = null;

                       PreparedStatement pstmt = null;

                       try {

                              brokerBean = (BrokerBean) jspContext.getServletContext().getAttribute("DBPOOL");

                              conn = brokerBean.getDbConnection();

                              conn.setAutoCommit(false);

                              pstmt = conn.prepareStatement("insert into SYSTEM_USAGE_DETAIL (USER_ID, MENU, FUNC, COMMON ) "

                                             + " values (?,?,?,?)");

                              pstmt.setString(1, id);

                              pstmt.setString(2, menu);

                              pstmt.setString(3, func);

                              pstmt.setString(4, comment);

                              pstmt.executeUpdate();

                              conn.commit();

                       } catch (SQLException e) {

                              e.printStackTrace();

                              conn.rollback();

                       } catch (Exception e) {

                              e.printStackTrace();

                              conn.rollback();

                       } finally {

                              try {

                                      if (pstmt != null) pstmt.close();

                                      if (conn != null) brokerBean.freeConnection(conn);

                              } catch (Exception ae) {

                                      ae.printStackTrace();

                              }

                       }

               }

}

#####################################################################

 

 

#####################################################################

01. ServerSocket Socket 간의 채팅 소스

#####################################################################

import java.io.*;

import java.net.*;

import java.util.*;

public class Server_15

{

        public static void main(String[] args)

        {

               try

               {

                       ServerSocket server = new ServerSocket(9999);

                       while (true)

                       {

                              System.out.println("Server Ready");

                              Socket socket = server.accept();

                              ServerHandle_15 sh = new ServerHandle_15(socket);

                              Thread th = new Thread(sh);

                              th.start();

                       }

               }catch (IOException e){}       

        }

}

class ServerHandle_15 implements Runnable

{

        private DataInputStream dis = null;

        private DataOutputStream dos = null;

        private Socket socket = null;

        public static Vector v = new Vector();

        public ServerHandle_15(Socket socket){

               try

               {

                       dis = new DataInputStream(socket.getInputStream());

                       dos = new DataOutputStream(socket.getOutputStream());

                       v.addElement(this);

               }catch (IOException e){}       

        }

        public void run(){

               try

               {

                       while (true)

                       {

                              String str = dis.readUTF();

                              broad(str);

                       }                             

               }catch (IOException e){}

        }

        public void broad(String str){

               for (int i=0; i<v.size() ; i++)

               {

                       try

                       {

                              ServerHandle_15 sh = (ServerHandle_15)v.elementAt(i);

                              sh.dos.writeUTF(str);

                       }catch (IOException e){ }                     

               }                             

        }

};

 

import java.io.*;

import java.net.*;

public class Client_15 implements Runnable

{

        private DataInputStream dis = null;

        private DataOutputStream dos = null;

        private Socket socket = null;

        public Client_15()

        {

               try

               {

                       socket = new Socket("localhost", 9999);

                       dis = new DataInputStream(socket.getInputStream());

                       dos = new DataOutputStream(socket.getOutputStream());

                       Thread th = new Thread(this);

                       th.start();

                       display();

               }catch (IOException e){}

        }

        public void run(){

               try

               {

                       while (true)

                       {

                              String str = dis.readUTF();

                              System.out.println(str);

                       }                                                                  

               }catch (IOException e){}

        }

        public void display(){

               try

               {

                       BufferedReader br = new BufferedReader(new InputStreamReader(System.in));                          

                       while (true)

                       {

                              String str = br.readLine();     

                              dos.writeUTF(str);

                       }                     

               }catch (IOException e){}

        }

        public static void main(String[] args){

               Client_15 sc = new Client_15();

        }

}

#####################################################################

 

 

#####################################################################

02. Swing에서 이미지 아이콘 삽입 및 종료

#####################################################################

import java.awt.*;

import javax.swing.*;

JFrame: Construct

private Container con;

con = this.getContentPane();

con.add(jbutton);

this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //플레임 종료

ImageIcon:

ImageIcon im = new ImageIcon("aa.gif");

this.setIconImage(im.getImage());

#####################################################################

 

 

#####################################################################

03. RMI 정상적인 예제

#####################################################################

package ksh.first;

import java.rmi.*;

public interface FirstRMI extends Remote{

        String getString() throws RemoteException;

        //void setString(String str) throws RemoteException;

}

#####################################################################

package ksh.first;

import java.rmi.*;

import java.rmi.server.*;

public class SecondRMI extends UnicastRemoteObject implements FirstRMI{

        public SecondRMI() throws RemoteException{

               super();

        }

        public String getString() throws RemoteException{

               return "Hello RMI!!!";

        }

}

#####################################################################

import ksh.first.*;

import java.rmi.*;

import java.net.*;

public class ThirdRMI{

        public static void main(String[] ar){

               SecondRMI sr = null;

               try{

                       sr = new SecondRMI();

                       Naming.rebind("first", sr);

                       System.out.println("Server Ready...");

               }catch(RemoteException ee){

               }catch(MalformedURLException ee){}

        }

}

#####################################################################

import ksh.first.*;

import java.rmi.*;

import java.net.*;

public class FourthRMI{

        public static void main(String[] ar){

               FirstRMI fr = null;

               try{

                       fr = (FirstRMI)Naming.lookup("rmi://192.168.0.155/first");

                       String str = fr.getString();

                       System.out.println("실행된 메시지 = " + str);

               }catch(NotBoundException ee){

               }catch(MalformedURLException ee){

               }catch(RemoteException ee){}

        }

}

#####################################################################

 

 

#####################################################################

04. RMI registry 등록하지 않고 사용

#####################################################################

package ksh.second;

import java.rmi.*;

public interface FirstRMI extends Remote{

        String getString() throws RemoteException;

}

#####################################################################

package ksh.second;

import java.rmi.*;

import java.rmi.server.*;

public class SecondRMI extends UnicastRemoteObject   implements FirstRMI{

        public SecondRMI() throws RemoteException{

               super();

        }

        public String getString() throws RemoteException{

               return "Hello RMI2!!";

        }

}

#####################################################################

import ksh.second.*;

import java.rmi.*;

import java.rmi.registry.*;

import java.net.*;

public class ThirdRMI{

        public static void main(String[] ar){

               Registry rg = null;

               SecondRMI sr = null;

               try{

                       rg = LocateRegistry.createRegistry(2000);

                       sr = new SecondRMI();

                       Naming.rebind("rmi://192.168.0.155:2000/second", sr);

                       System.out.println("Server Ready...");

               }catch(RemoteException ee){

               }catch(MalformedURLException ee){}

        }

}

#####################################################################

import ksh.second.*;

import java.rmi.*;

import java.net.*;

public class FourthRMI{

        public static void main(String[] ar){

               FirstRMI fr = null;

               try{

                       fr = (FirstRMI)Naming.lookup(

                                                                    "rmi://192.168.0.155:2000/second");

                       String str = fr.getString();

                       System.out.println("메시지 = " + str);

               }catch(NotBoundException ee){

               }catch(MalformedURLException ee){

               }catch(RemoteException ee){}

        }

}

#####################################################################

 

 

#####################################################################

05. RMI 통신법

#####################################################################

기본 Port 1099

Remote Method Invocation

rmi package & 중요 클래스

---------------------------------------------------------------------

1.서버: 공통 인터페이스, 구현 클래스, 스켈레톤 클래스, 서버실행 클래스

서버기동 bind(String str, Remote obj)

- 공통 인터페이스

        public 인터페이스, Remote 인터페이스를 상속

        abstract Method RemoteException 를 처리

        매개변수들은 객체이거나 직렬화가 가능

- 구현클래스

        public class Second extends UnicastRemoteObject implements FirstRMI 를 상속

- 스텝과 스켈레톤 계증 만들기

        rmic -d . ksh.first.SecondRMI 구현클래스로 생성한다.

- rmiregistry 기동해야 한다. 1099 Port 이용

        대체클래스로 기동할 수도 있다

        start rmiregistry 새창으로 돌려라 (unix에 백앤드 프로그램 생성하는 것과 같다)

- Binding 시키기 (등록시킨다)

        ThirdRMI {

               SecondRMI sr = new SecondRMI();

               sr.rebind("first", sr);

        }

        C\>start java ThirdRMI 로 실행

#####################################################################

2.클라이언트: 공통인터페이스, 스넵 클래스, GUI 클래스

- 서버 파일 가져오기, 스텝 클래스를 가져온다

        공통인터페이스 FirstRMI.class, SecondRMI_Stub.class

- 서버의 기동중인 객체를 얻어오기

        import ksh.first.*;

        import java.rmi.*;

        public class FourthRMI{

               try

               {

                       FirstRMI fr = new First();

                       fr = (FirstRMI)Naming.lookup("rmi://165.213.42.29:1099/first");

               }

               catch (NotBoundException e){}

               catch (MalformedURLException e){}

               catch (RemoteException e){}

        }

- 해당 객체를 통해서 Method 를 사용하기

        String str = fr.getString();

        System.out.println("얻어온 실행된 메시지 "+str);

#####################################################################

3. rmiregistry 대체 클래스

Registry interface (Naming class 와 유사)

LocateRegistry class

 

import ksh.second.*;

import java.rmi.*;

import java.rmi.registry.*;

import java.net.*;

public class ThirdRMI

{

        public static void main(String[] args){

               SecondRMI sc = null;

               Registry rg = null;

               try

               {

                       rg = LocateRegistry.createRegistry(2000);

                       sc = new SecondRMI();

                       Naming.rebind("rmi:192.168.0.155:2000/second", sc);

               }

               catch (RemoteException e){}

               catch (MalformedURLException e){}

        }

};

#####################################################################

 

 

#####################################################################

06. Directory 찾아서 리스트 만들기

#####################################################################

 

import java.io.*;

public class MakeFileList

{

        PrintWriter pw = null;

        public MakeFileList(){

               try

               {

                       FileOutputStream fos = new FileOutputStream(new File("./Text.txt"));

                       OutputStreamWriter osw = new OutputStreamWriter(fos, "EUC-KR");

                       pw = new PrintWriter(osw, true);

               }catch (IOException e){

                       System.out.println("File write error "+e);

               }

               searchDirectory(new File("."));

               pw.flush();

               pw.close();

        }

        public void searchDirectory(File here){

               File[] rootFileList = here.listFiles();

               for (int i=0; i<rootFileList.length ; i++ )

               {

                       if (rootFileList[i].isDirectory())

                       {

                              searchDirectory(rootFileList[i]);

                       }else{

                              try

                              {

                                      String filename = rootFileList[i].getName();

                                      String ext = filename.substring(filename.lastIndexOf("."), filename.length());

                                      if (!ext.equalsIgnoreCase(".gif") && !ext.equalsIgnoreCase(".jpg") )

                                      {

                                             pw.print(rootFileList[i].toURL());

                                             pw.print("\n");

                                             System.out.println(rootFileList[i].toURL());

                                      }else{

                                             System.out.println(ext);

                                      }

                              }catch (Exception e){}

                       }

               }

        }

        public static void main(String[] args){

               MakeFileList mf = new MakeFileList();

        }

};

#####################################################################

 

 

#####################################################################

07. IO 관련 소스

#####################################################################

Abstract OutStream을 이용한 입력하기

 

ch01 - AAA - Exam02_01.java, jar02.jar (jar -cvf jar02.jar *.*)

       BBB - Exam02_Main.java

jar 위치: C:\jdk1.3.1\jre\lib\ext\jar02.jar

 

private PrintStream ps;

public Exam02_03(){

        super();

        ps = System.out;

}

public void write(int i){

        ps.write(i);

        ps.flush();

}

public void close(){

        ps.close();

}

 

Exam02_03 es = new Exam02_03();

es.write(65);

es.write('\r');

es.write('\n');

byte[] bb = {'h','e','l','l','o'};

es.write(bb);

//es.close();

byte[] cc = "HELLO JAVA".getBytes();

es.write('\r');

es.write('\n');

es.write(cc, 0, 5);

es.write("송하연".getBytes("ISO8859_1"));

#####################################################################

private InputStream is;

public Exam02_04(){

        super();

        is  = System.in;

}

public int read() throws IOException{

        int xx = is.read();

        if (xx == '\n')

        {

               return -1;

        }

        return xx;

}

 

/*

byte[] bb = new byte[20];

System.out.print("입력 ");

System.in.read(bb);

int i = System.in.read();

System.out.println("입력된 문자 "+ new String(bb));

*/

Exam02_04 es = new Exam02_04();

byte[] bb = new byte[20];

System.out.print("입력 : ");

es.read(bb);

System.out.println("입력된 문자 : "+ new String(bb));

#####################################################################

 

 

#####################################################################

08. 파일을 읽는 소스

#####################################################################

 

int cnt;

FileReader rf = new FileReader("Test.java");            

cnt = rf.read();

while (cnt != -1)

{

        System.out.print(cnt);

        cnt = rf.read();

}

#####################################################################

 

 

#####################################################################

09. 입력받은 파일을 읽는 소스

#####################################################################

 

int cnt = 0;

System.out.print("Input your filename ");

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

String str = br.readLine();

RandomAccessFile raf = new RandomAccessFile(str, "r");

cnt = raf.read();

while (cnt != -1)

{

        System.out.print((char)cnt);

        cnt = raf.read();

}

#####################################################################

 

 

#####################################################################

10. 스트링을 파일로 저장하는 소스

#####################################################################

 

String str = "어디로 가야 할지";

char[] ch =  new char[str.length()];

str.getChars(0, ch.length, ch, 0);

FileWriter fw = new FileWriter(args[0]);

fw.write(ch);

fw.close();

#####################################################################

 

 

#####################################################################

11. 파일을 저장하는 소스

#####################################################################

 

FileOutputStream fos = new FileOutputStream(args[0]);

for (int i=0; i<500 ; i++ )

{

        fos.write(i);

}

fos.close();

System.out.println("FileOutputStream 했습니다!");

#####################################################################

 

 

#####################################################################

12. 에디터에는 볼 수 없으나 도스에서는 보임

#####################################################################

 

int cnt;

FileInputStream fis = new FileInputStream(args[0]);             

while ( (cnt = fis.read()) != -1)

{                     

        System.out.println(cnt);

}

System.out.println("FileInputStreamDemo를 확인했습니다.");

#####################################################################

 

 

#####################################################################

13. Properties 파일에 관련된 소스

#####################################################################

파일과 관련해서 Properties 저장하는 소스

 

Properties props = new Properties();

props.put("이름","마법사프로퍼티");

props.put("주소","마법의나라");

props.store(new FileOutputStream("properties.dat"),"프로퍼트테스트");

#####################################################################

 

 

#####################################################################

14. 파일과 관련해서 Properties 에서 읽어 오는 소스

#####################################################################

 

Properties props = new Properties();

props.load(new FileInputStream("properties.dat"));

System.out.println("이름: "+props.getProperty("이름"));

System.out.println("주소: "+props.getProperty("주소"));

#####################################################################

 

 

#####################################################################

15. 읽은 파일입력 후 내용확인하는 소스

#####################################################################

 

String fileInput;

int c = 0;;

System.out.print("Input your file name ");

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

fileInput = br.readLine();

RandomAccessFile raf = new RandomAccessFile(fileInput, "r");

c = raf.read();

while ( c != -1 )

{

        System.out.print((char)c);

        c = raf.read();

}

raf.close();

#####################################################################

 

 

#####################################################################

16. 입력파일과 저장할 파일을 쓰는 소스

#####################################################################

 

String fileinput;

String fileoutput;

int c;

System.out.print("Input your filename ");

BufferedReader bf1 = new BufferedReader(new InputStreamReader(System.in));

fileinput = bf1.readLine();

System.out.print("Write save filename ");

BufferedReader bf2 = new BufferedReader(new InputStreamReader(System.in));

fileoutput = bf2.readLine();

RandomAccessFile raf1 = new RandomAccessFile(fileinput, "r");

RandomAccessFile raf2 = new RandomAccessFile(fileoutput, "rw");

c = raf1.read();

while (c!=-1)

{

        raf2.write(c);

        System.out.print((char)c);

        c = raf1.read();

}

raf1.close();

raf2.close();

#####################################################################

 

 

#####################################################################

17. 파일을 읽어서 보여주는 소스

#####################################################################

 

FileInputStream fis = new FileInputStream("Tst.java");

byte[] byteTxt = new byte[fis.available()];

fis.read(byteTxt);

fis.close();

System.out.println(new String(byteTxt));

#####################################################################

 

 

#####################################################################

18. 2초마다 출력하는 쓰레드 소스

#####################################################################

 

public class ThreadFromThread extends Thread

{

        public static void main(String[] args)

        {

               ThreadFromThread tt = new ThreadFromThread();

               tt.start();

        }

        public void run(){     

               try{

                       for(int i=0; i<10; i++)

                       {

                               Thread.sleep(2000);

                              System.out.println(i + " 2초 마다 출력되는 소스 !!!");

                       }                     

               }

               catch(InterruptedException e){}

        }

};

#####################################################################

 

#####################################################################

19. 배열과 관련하여

#####################################################################

2차원 배열 출력하는 소스

 

int[][] aa =

        {

               {1,  6, 11, 16, 21},

               {2,  7, 12, 17, 22},

               {3,  8, 13, 18, 23},

               {4,  9, 14, 19, 24},

               {5, 10, 15, 20, 25}

        };

for (int i=0; i<aa.length ; i++)

{

        if (i<5)

        {

               for (int j=0; j<aa[i].length ; j++ )

               {

                       System.out.print(aa[i][j] +"  ");

               }

               System.out.println();

        }                     

}

#####################################################################

 

 

#####################################################################

20. 배열로 합계 평균을 내는 소스

#####################################################################

 

int sum = 0;

long average = 0;             

int[] aa = {20, 30, 40, 50, 30, 70};

int len = aa.length;

for (int i=0; i<aa.length ; i++)

{             

        sum += aa[i];

}

System.out.println("sum ->/"+sum+"/");

System.out.println("average ->/"+(long)sum/len+"/");

#####################################################################

 

 

#####################################################################

21. 프로그램 인수를 받아 평균과 총계를 보여주는 소스

#####################################################################

 

if ( args.length<1 )

{

        System.out.println("Usage : java ArrayDe_05  ");

        System.exit(-1);

}

int aa = Integer.parseInt(args[0]);

int[] bb = new int[aa];

for (int i=0; i<bb.length ; i++ )

{

        bb[i] = i+1;

}

int sum = 0;

long average = 0;

int len = bb.length;

for (int i=0; i<bb.length ; i++ )

{

        sum += bb[i];

}

System.out.println("sum ->/"+sum+"/");

System.out.println("average ->/"+(long)sum/len+"/");

#####################################################################

 

 

#####################################################################

22. [실행되지 않으나 참고해야 할 소스]

#####################################################################

프로그램 실행시간을 체크하는 소스

 

CronLog cl = new CronLog("Maker.log");

Date dSTART_PROG = new Date();

중간 프로세스 처리

Date dEND_PROG = new Date();

long lELAPSED = dEND_PROG.getTime() - dSTART_PROG.getTime();

cl.log("Making started at " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(dSTART_PROG));

cl.log("Making ended at " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(dEND_PROG));

cl.log("Making " + lELAPSED / 1000 + " seconds elapsed.");

#####################################################################

 

 

#####################################################################

23. 로그 관련 소스

#####################################################################

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.PrintStream;

import java.util.Date;

import java.text.SimpleDateFormat;

import java.util.Calendar;

public class CronLog {

        private String sLOG_FILE = null, sLOG_PATH = null;

        private PrintStream out = null;

        SimpleDateFormat formatter;

        int iBACKUPFILE_KEEPING_DURATION = 30;    // 로그 파일 보관 일수(디폴트 30)

        public CronLog() {

               sLOG_FILE = makeLogFileName(Calendar.getInstance(), "MINU.log");

               formatter = new SimpleDateFormat("HH:mm:ss");

               removeLog( sLOG_FILE );

        }

        public CronLog(String sLOG_FILENAME) {

               sLOG_FILE = makeLogFileName(Calendar.getInstance(), sLOG_FILENAME);

               formatter = new SimpleDateFormat("HH:mm:ss");

               removeLog( sLOG_FILE );

        }

        public CronLog(String sLOG_FILENAME, int iBACKUPFILE_KEEPING_DURATION) {

               sLOG_FILE = makeLogFileName(Calendar.getInstance(), sLOG_FILENAME);

               formatter = new SimpleDateFormat("HH:mm:ss");

               this.iBACKUPFILE_KEEPING_DURATION = iBACKUPFILE_KEEPING_DURATION;

               removeLog( sLOG_FILE );

        }

        /** 해당 주기(iBACKUPFILE_KEEPING_DURATION)에 따라 로그 파일을 삭제함... */

        public void removeLog(String sLOG_FILE) {

               String sLOG_PATH = sLOG_FILE.substring(0, sLOG_FILE.lastIndexOf(File.separator)+1);

               if( sLOG_PATH.trim().equals("") ) {

                       sLOG_PATH = ".";

               }

               File fLOG_PATH = new File(sLOG_PATH);

               if( fLOG_PATH.isDirectory() ) {

                       File[] foLOG_FILES = fLOG_PATH.listFiles();

                       for(int i=0; i<foLOG_FILES.length; i++) {

                              File fLOG_FILE = foLOG_FILES[i];

                              long lLOG_FILE_ELAPSED = (new Date().getTime() - fLOG_FILE.lastModified()) / (24 * 60 * 60 * 1000);

                              if( fLOG_FILE.isFile() && lLOG_FILE_ELAPSED >= iBACKUPFILE_KEEPING_DURATION) {

                                      fLOG_FILE.delete();

                              }

                       }

               }

        }

        public void log(String sLOG) {

               if( sLOG_FILE == null ) {

                       sLOG_FILE = makeLogFileName(Calendar.getInstance(), "MINU.log");

               }             

               if( out == null ) {

                       File fLOG_FILE = new File(sLOG_FILE);       

                       FileOutputStream fos = null;

                       try {

                              fos = new FileOutputStream(sLOG_FILE, true);

                       } catch (FileNotFoundException e) {

                              System.err.println(e.toString());

                       }

                       out = new PrintStream(fos);

               }

               out.println("[" + formatter.format(new Date()) + "] " + sLOG);

        }

        public static String makeLogFileName(Calendar cal, String name) {

               java.util.Date dt = cal.getTime();

               SimpleDateFormat formatter = new SimpleDateFormat("@yyyy-MM-dd");

               String dateString = formatter.format(dt);

               int ind = name.indexOf(".");

               String head = name.substring(0, ind);

               String tail = name.substring(ind);

               name = head + dateString + tail;              

               checkDirectory(new File(name.substring(0, name.lastIndexOf(File.separator))));

               return name;

        }

        public static File checkDirectory(File dir) {

               File retVal = null;

               try {

                       if (!dir.isDirectory()) {

                              if (dir.mkdir()) {

                                      System.out.println("[" + dir.toString() + "] Directory Created, sucessfully.");

                              }

                       }

               } catch (Exception e) {

                       e.printStackTrace();

               }

               return dir;

        }

        public static void main(String[] args) {

               CronLog l = new CronLog("KKK.LOG");

               for(int i=0; i<100; i++ ) {

                       l.log(String.valueOf(i));

               }

               l.log("한글은???????????????????????????");

               l.log("Enddddddddddddddddddddddddddddddd");

        }

        public void log(int i) {

               log(String.valueOf(i));

        }

}

#####################################################################


2009. 9. 8. 12:41

Java DES/TripleDES 암호화





Java 기반 DES 암호화를 찾던 중 정리한 소스

1. Key는 관리차원에서 고정키로 사용 (키발급 안함)
2. DES, TripleDES 모두 가능
3. TripleDES로 사용하는 경우 Key Size는 24 바이트로 지정 (이하시 에러)

 package system.user.MD5;

import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DESedeKeySpec;

public class DES {

    public static void main(String[] args) throws Exception {
        String text = "java12345";

        String enen = encrypt(text);
        String dede = decrypt(enen);

        System.out.println("\nOrigin key: " + key());
        System.out.println("\nOrigin text: " + text);
        System.out.println("\nEncrypted text: " + enen);
        System.out.println("\nDecrypted text: " + dede);
    }
    
    /**
     * 고정키 정보
     * @return
     */
    public static String key()
    {
        return "ab_booktv_abcd09";
        //return "ab_booktv_abcd0912345678";
    }
    
    /**
     * 키값
     * 24바이트인 경우 TripleDES 아니면 DES
     * @return
     * @throws Exception
     */
    public static Key getKey() throws Exception {
        return (key().length() == 24) ? getKey2(key()) : getKey1(key());
    }

    /**
     * 지정된 비밀키를 가지고 오는 메서드 (DES)
     * require Key Size : 16 bytes
     *
     * @return Key 비밀키 클래스
     * @exception Exception
     */
    public static Key getKey1(String keyValue) throws Exception {
        DESKeySpec desKeySpec = new DESKeySpec(keyValue.getBytes());
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        Key key = keyFactory.generateSecret(desKeySpec);
        return key;
    }
    
    /**
     * 지정된 비밀키를 가지고 오는 메서드 (TripleDES)
     * require Key Size : 24 bytes
     * @return
     * @throws Exception
     */
    public static Key getKey2(String keyValue) throws Exception {
        DESedeKeySpec desKeySpec = new DESedeKeySpec(keyValue.getBytes());
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
        Key key = keyFactory.generateSecret(desKeySpec);
        return key;
    }

    /**
     * 문자열 대칭 암호화
     *
     * @param ID
     *            비밀키 암호화를 희망하는 문자열
     * @return String 암호화된 ID
     * @exception Exception
     */
    public static String encrypt(String ID) throws Exception {
        if (ID == null || ID.length() == 0)
            return "";
        
        String instance = (key().length() == 24) ? "DESede/ECB/PKCS5Padding" : "DES/ECB/PKCS5Padding";
        javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(instance);
        cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, getKey());
        String amalgam = ID;

        byte[] inputBytes1 = amalgam.getBytes("UTF8");
        byte[] outputBytes1 = cipher.doFinal(inputBytes1);
        sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
        String outputStr1 = encoder.encode(outputBytes1);
        return outputStr1;
    }

    /**
     * 문자열 대칭 복호화
     *
     * @param codedID
     *            비밀키 복호화를 희망하는 문자열
     * @return String 복호화된 ID
     * @exception Exception
     */
    public static String decrypt(String codedID) throws Exception {
        if (codedID == null || codedID.length() == 0)
            return "";
        
        String instance = (key().length() == 24) ? "DESede/ECB/PKCS5Padding" : "DES/ECB/PKCS5Padding";
        javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(instance);
        cipher.init(javax.crypto.Cipher.DECRYPT_MODE, getKey());
        sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();

        byte[] inputBytes1 = decoder.decodeBuffer(codedID);
        byte[] outputBytes2 = cipher.doFinal(inputBytes1);

        String strResult = new String(outputBytes2, "UTF8");
        return strResult;
    }

}


결과
==================================
Origin key: ab_booktv_abcd09

Origin text: java12345

Encrypted text: 3nAuFnFNtxDWBHOqueEpdA==

Decrypted text: java12345

 

2009. 5. 22. 14:42

JMeter





1. JMeter의 소개

JMeter는 Apache Jakarta 프로젝트의 일환으로 만들어진 테스트 기능과 퍼포먼스를 측정하는 애플리케이션이다. 이 애플리케이션 100% 순수 자바로 작성되었으며 원래 웹 애플리케이션을 위해 디자인되고 사용되었으나 현재는 다른 기능의 테스트를 위해 확장되고 있다.

JMeter는 static, dynamic 한 모든 자원(파일, 서블릿, perl script, java object, database and query, ftp, soap 등)에 대해 퍼포먼스를 테스트할 수 있다. 이러한 테스트 결과로 분석결과를 그래픽으로 표시해주는 기능과 스크립트 객체 등에 대해 과도한 동시 처리 부하가 가능하다.

Apache Jmeter는 다음 사이트에서 얻을 수 있다.

http://jakarta.apache.org/jmeter/

JMeter에 대한 추가 정보는 위 사이트에서 얻을 수 있다. 한데 위 사이트는 영어로 구성되어 있기 때문에 조금 빈약하더라도 한국어로 된 정보는 자카르타 서울 프로젝트에서 얻을 수 있다.

http://jakarta.apache-korea.org

2. JMeter를 얻고 설치하기

JMeter를 설치 하기 위해서 우선 Jakarta 의 JMeter 사이트에 접속한다. 다음 그림들은 Jakarta 메인 사이트와 JMeter 사이트를 보여준다.


[그림 2-1] Jakarta 사이트


[그림 2-2] Jakarta JMeter 사이트

JMeter를 다운로드 받기 위해선 화면 왼쪽의 Download Binary 링크를 통해 들어가면 다운로드 받을 수 있다.


[그림 2-3] Jakarta 다운로드 링크 페이지

Jakarta 프로젝트는 다수의 서브 프로젝트들로 이뤄져 있기 때문에 실제적인 다운로드는 Downloads 섹션의 Jmeter를 클릭하면 다운로드 받을 수 있게 된다. [그림 2-3]은 그 화면을 나타낸다.


[그림 2-4] Mirror 페이지

[그림 2-3]에서 JMeter 를 클릭하면 곧 바로 다운로드를 시작하지는 않고 어디서 다운로드 받을지 물어오게 된다. 본 문서에서는 미러 사이트로 한국 아파치 사용자 모임을 선택했다. [그림 2-4]에 보이는 빨간 박스를 클릭하면 미러 사이트가 나타나게 된다. 미러 사이트를 선택했다면 Change 버튼을 눌러 미러 페이지를 변경시킨다.

미러 사이트는 다수가 존재하는데 다운로드 속도가 느릴 경우 바꿔가며 시도해본다. 일반적으로 기본적으로 선택되는 미러 사이트를 이용해도 된다.


[그림 2-5] JMeter 다운로드 링크

[그림 2-4]의 화면에서 미러 사이트를 변경하지 않았거나 변경한 경우 화면 스크롤을 내리면 빨간박스로 그려진 부분 2.3.1.zip 부분을 볼 수 있다. 이 부분을 클릭하면 본격적인 다운로드 윈도우를 볼 수 있다.


[그림 2-6] 파일 저장 윈도우

[그림 2-6]에서 보여지는 윈도우는 파일을 어디에 저장할 것인지를 물어오는 화면이다. 본문에서는 바탕 화면에 저장하였다.


[그림 2-7] 바탕화면에 저장된 JMeter 파일

[그림 2-6]에서 바탕 화면에 저장된 파일은 [그림 2-7]과 같다.


[그림 2-8] JMeter 실행 파일

JMeter는 Java Application 이기 때문에 별도의 설치 프로그램을 필요로 하지 않는다. 본문에서는 압축파일을 선택한 후 바탕 화면 폴더에 압축 내용을 별도의 폴더 생성 과정 없이 풀었다. 이렇게 푸는 이유는 JMeter 압축파일이 별도의 상위 폴더를 가지고 있기 때문이다. [그림 2-8]은 그런 화면을 나타낸다.

JMeter의 실행은 [그림 2-8]에 보이는 것처럼 jmeterw.cmd 파일을 더블 클릭하면 실행한다.


[그림 2-9] JMeter의 실행 화면

3. JMeter 기본 과정

JMeter는 테스트 환경에 있어 상위 항목으로 WorkBench와 Test Plan를 두고 있다. 이 상위 항목을 다른 이름으로 노드라고 표현한다. 노드라는 단어는 Tree에서 비롯되었는데 JMeter는 각각의 element를 계층적으로 관리 하기 위해서 Tree 구조를 사용한다. JMeter는 테스트 앞서 Test Plan을 먼저 작성하게 된다. Test Plan은 한국어로 풀어 쓰면 테스트 계획인데 JMeter의 모든 Test는 이 Test Plan을 먼저 세워야 한다.
Test Plan  + HTTP TestWorkBench
[모식도 3-1] JMeter 모식도

[모식도 3-1]은 위에서 설명한 구조를 간략히 그려내 본 것이다. JMeter는 Test Plan과 WorkBench 아래에 놓이는 것을 가리켜 Element라고 한다. 한국어로는 요소라는 말이 적절하겠지만 이해를 돕기 위해 원어로 표기한다. Element는 여러 종류가 있는데 Test Plan에 바로 올 수 있는 Element는 다음과 같은 것들이 있다.


[그림 3-1] Test Plan 에 추가될 수 있는 element

[그림 3-1]은 Test Plan 노드 아래 추가될 수 있는 element를 나타내고 있는데 기본 테스트를 하기 위해선 Thread Group과 Listener 의 추가가 우선적이다. Thread Group는 테스트의 가장 기본적인 요소로써 Test Plan 노드 아래 몇 개든 올 수 있다. Thread Group은 논리적인 모습으로 표현하면 테스트 클라이언트들의 묶음이라고 보는 표현이 타당하다.

JMeter는 단지 Thread Group을 구성하는 것만으로 결과를 볼 수 없다. 이 뚱딴지 같은 이야기는 JMeter가 결과를 보기 위한 또 다른 구성 요소를 필요로 한다는 것이다. JMeter는 테스트 결과를 다양한 형태로 볼 수 있도록 다음과 같은 구성 요소를 지원한다.


[그림 3-2] Listener의 구성 요소

[그림 3-2]에 보이는 element 중 가장 많이 사용되는 요소는 Graph Results, Summary Report, View Results in Table, View Results Tree다.

다시 앞으로 돌아가서 Thread Group은 테스트에 있어서 기본적인 단위가 된다고 했다. 이제 Thread Group 아래 다른 element를 추가할 필요가 있다.

Test Plan 노드에 Thread Group을 추가한 후 마우스 오른쪽 클릭을 통해 보면 [그림 3-1]에서 보던 것과 비슷한 화면이 보이는데 하나 더 추가된 element 그룹이 존재한다.


[그림 3-3] Thread Group에서의 마우스 오른쪽 팝업

[그림 3-3]에는 Sampler 그룹이 보이는데 바로 이 그룹이 JMeter 테스트에 있어서 실제로 부하를 보내는 실제적인 주체가 들어 있는 그룹이다. Sampler는 다양한 element가 들어가 있는데 대표적으로 다음과 같은 sampler 항목을 들어볼 수 있다.


[그림 3-4] 많이 쓰이는 Sampler

Sampler는 위의 그림에 보이는 것보다 더 많지만 기본적인 기능 테스트 및 웹 애플리케이션 테스트에는 [그림 3-4]에 있는 Sampler가 더 많이 쓰인다.

본문에서는 HTTP Request Sampler에 대해서만 집중적으로 다룰텐데 다른 Sampler에 대해선 다른 기회가 주어진다면 하나씩 파보는 기회를 가져보도록 하겠다.

Thread Group에는 Sampler 뿐 아니라 다른 element들도 추가가 가능한데 이들 element에 대해서도 다른 기회가 주어졌을 때 파보는 기회를 가져보겠다.

자 이제까지 설명한 것은 JMeter에서 스트레스 테스트를 하기 위한 기본 구조를 설명하였다. [그림 3-5]는 지금까지 설명한 구조를 실제로 구현한 JMeter의 Test Plan 의 모습이다.


[그림 3-5] 간단한 Test를 위한 Test Plan

다음 섹션에서는 [그림 3-5]를 기반으로 한 Simple한 Test를 진행해보겠다.

4. JMeter를 사용한 Simple Test

지금까지 간략히 JMeter의 테스트 계획에 대해서 알아보았는데 본 섹션에서는 앞에서 다루었던 Test Plan에 실제 숨을 불어넣어 본다.

본문에서는 저자가 운영진으로 활동하고 있는 한국 아파치 사용자 모임 사이트를 대상으로 삼았다. 우선 HTTP Request 노드를 선택하면 JMeter 의 오른쪽 화면이 바뀌면서 [그림 4-1]처럼 나타나게 된다.


[그림 4-1] HTTP Request 선택

[그림 4-1]에는 빨간 박스가 다수 보이는데 이 박스 안의 내용을 입력하거나 선택함으로서 테스트 정보를 입력한다.

첫번째 입력할 정보는 테스트할 서버의 IP 또는 URL을 입력하는데 URL은 프로토콜 정보를 제외한 순수 주소만 적는다. 다음과 같은 URL이 테스트할 정보라고 보고 Server Name을 분리하자.

http://www.apache-kr.org/www/board.php?cmd=retrieveContent&board_cd=techtalk&rg_d=20080410&rg_seq_n=1&pageID=1&search_gb=&fr_rg_d=&to_rg_d=&searchstring=
Server Name은 www.apache-kr.org가 되어야 한다.

두번째 입력하고 선택할 정보는 Method와 Path인데 이 정보는 실제로 테스트할 URL 정보가 들어간다.

Path는 실제 테스트할 URL을 말하는데 / 로부터 시작하고 인자가 시작되기 전까지의 정보를 적는다. 앞에서 언급된 URL을 기준으로 path는 아래와 같이 분리된다.
/www/board.php
위에서 나타난 파일이 어떤 인자를 받는다면 그 인자 방식을 Method에서 선택해준다. 여러 Method를 사용할 수 있지만 웹 애플리케이션은 일반적으로 GET 방식과 POST 방식을 사용한다. 예제로 쓰는 URL은 모든 정보를 GET 방식으로 던진다. 따라서 Method는 GET으로 지정한다. 물론 인자를 받지 않는다면 Parameter 정보를 입력하지 않아도 된다.

HTTP Request 노드의 Method 기본값은 GET이기 때문에 혹시 GET이 아니면 수정해주도록 한다. Add 버튼을 클릭하면 파라메터 정보를 입력받게 된다.

본문에서는 Add 버튼을 5번 눌러 파라메터 입력을 5개 만들었다. 입력할 파라메터는 다음 목록과 같다.
cmdboard_cdrg_drg_seq_npageID
서버 정보 및 파라메터 정보를 모두 입력한 화면은 [그림 4-2]와 같다.


[그림 4-2] 테스트 정보를 모두 입력한 화면

HTTP Request 정보를 모두 입력했다면 Thread Group에 관한 설정을 할 차례다. JMeter의 왼쪽 트리에서 Thread Group 노드를 선택하면 [그림 4-3]과 같은 화면이 나타난다.


[그림 4-3] Thread Group 정보 입력

[그림 4-3]에 보이는 Thread Group 정보는 몇가지 예의 주시해서 입력해야 할 정보가 있다.

첫번째 박스는 몇 명의 사용자로 동시 접속 부하를 줄것인지에 대한 것인데 사용자명을 높게 지정할수록 서버의 부하 테스트도 더 커진다.
두번째 박스는 한명의 사용자가 접속자가 서버로 부하를 주러 떠나고 다른 사용자가 부하를 주러 떠날 때 사용자간의 접속 시간을 지정한다. JMeter 문서에는 쓰레드의 생성 주기를 나타낸다고 하지만 이해를 돕기 위해서 사용자간의 접속 시간으로 보면 된다.

세번째 박스는 Thread Group에 속해있는 Sampler들을 몇번 보낼 것인지 지정한다. 이해가 어렵다면 HTTP Request에서 지정했던 URL을 Loop Count 에 지정한 숫자만큼 다시 서버에 요청을 한다고 이해하면 된다. 만약 Forever 박스를 체크했다면 JMeter 실행자는 JMeter를 수동적으로 정지시켜줘야 한다.

본문에서는 Number of Threads(users)를 10명으로 Loop Count는 3으로 수정하였다. 이 숫자나 양은 테스트 부하에 따라 적절하게 조정해주면 된다.

이제 부하 테스트를 해볼 차례다. 부하 테스트는 메뉴에서 Run > Start를 선택하거나 Ctrl + R 키를 누르면 Test Plan을 저장할 것인지 묻게 되고 아니오(N)을 누르면 테스트를 시작한다. 만약 Test Plan이 저장되어 있다면 바로 부하 테스트를 시작하게 된다.


[그림 4-4] 테스트 계획을 저장하시겠습니까?

부하가 진행되는 중에는 [그림 4-5]에 있는 네모 박스에 녹색불이 들어오면서 테스트를 시작한다.


[그림 4-5] 부하가 진행중인 표시

부하 테스트가 끝나면 JMeter의 왼쪽 트리에서 View Results Tree를 선택하면 오른쪽 화면이 [그림 4-6] 처럼 바뀌게 된다.


[그림 4-6] View Results Tree

[그림 4-6]과 같이 테스트한 결과가 출력되었다면 테스트는 정상적으로 이루어진 것이다. 하지만 종종 화면 좌측에 보이는 초록색의 삼각 아이콘 모습이 나타나지 않으면 네트워크 연결 오류나 서버측 오류로 인해 테스트할 없는 상황으로 본다.

다음 [그림 4-7]은 Graph Results 노드를 선택한 결과이다.


[그림 4-7] Graph 결과로 표시된 부하 테스트

5. JMeter를 사용한 실제 전략

본 섹션에서는 JMeter를 사용해 실제로 로그인하고 로그인한 정보를 바탕으로 접근 가능한 URL에 접근해 부하 테스트를 진행해보겠다.

본문에서는 테스트를 설명하기 위해서 daum 에 로그인하고 저자가 카페지기로 있는 사이트에 가서 특정글에 대한 요청을 서버에게 던져서 정상적으로 글을 가져오는지 확인해보겠다.

우선 다음과 같이 Test Plan 노드를 추가한다.


[그림 5-1] Daum의 Café에 접속하기 위해 글을 가져오기 위한 Test Plan

[그림 5-1]과 같이 Test Plan을 만들면 되는데 가만 보면 못 보던 요소가 하나 존재한다. 바로 HTTP Cookie Manager인데 Cookie Manager는 JMeter 2.0.3 부터 새로이 추가된 element다.

이 element의 추가는 [그림 5-2]에서 보이는 것과 같이 추가한다.


[그림 5-2] HTTP Cookie Manager 추가하기

Cookie Manager는 Thread Group이나 Test Plan 노드에 추가해도 되는데 Test Plan 노드에 추가하는 경우는 여러 Thread Group이 모두 로그인 페이지를 요구하는 경우 유용하게 사용할 수 있다.

반대로 특정 Thread Group에서만 로그인 페이지를 요구하는 경우 Thread Group에만 추가하면 된다.

그리고 HTTP Request Sampler를 총 2개 추가한다.

HTTP Request Sampler는 다음 [그림 5-3]과 같이 설정한다.


[그림 5-3] HTTP Request 1

첫번째 HTTP Request는 Daum에 로그인 요청을 하는 Sampler이다. [그림 5-3]에 보이는 요소에 대해서는 섹션 4에서 다루었기 때문에 섹션 4와 비교했을 때 다른 것만 알아보기로 한다.

[그림 5-3]에는 빨간 박스로 강조되어진 부분이 있는데 이 부분은 JMeter에서 요청을 한후 자동으로 주소를 변경할지를 설정하는데 일부 사이트의 경우 이 옵션이 켜져 있을 경우 문제가 된다. 때문에 로그인하는 요청의 경우 옵션은 해제해주는게 좋다. 그리고 Follow Redirects 옵션을 켜주는게 좋다. Follow Redirects 옵션은 웹 사이트의 Redirect 액션을 허용한다.

[그림 5-4]는 실제 게시물에 접근하는 요청이다. 이때 접근하는 URL은 미리 페이지 등록정보를 통해 확인한 URL을 이용한다.


[그림 5-4] HTTP Request 2

[그림 5-4]는 게시물에 접근하는 요청이다. 빨간 박스로 강조된 부분은 Path에 인자로 넘겨지는 부분인데 이와 같이 URL에 접근에 있어서 파라메터를 요구하는 경우는 1개씩 인자를 생성해주어야 한다.

이제 요청 결과를 알아보기 위해 Test Plan을 실행한다. Test Plan의 실행은 Run > Start 메뉴를 선택하거나 Ctrl + R 키를 누른다.

그럼 [그림 5-5]와 같이 Test Plan을 저장할 것이냐고 물어오는데 본문은 테스트이므로 아니오를 눌러 저장하지 않는다.


[그림 5-5] Test Plan을 저장할것입니까?

스트레스 테스트가 끝나면 JMeter의 왼쪽 트리에서 View Results Tree 노드를 선택한다.


[그림 5-6] View Results Tree 노드 선택

이제 JMeter의 화면 오른편에는 [그림 5-7]과 같은 화면이 있는 걸 볼 수 있다.


[그림 5-7] View Results Tree 초기 화면

이제 [그림 5-7]에 보이는 항목을 클릭하면 [그림 5-8]과 같이 변경된다.


[그림 5-8] 2번째 HTTP Request 클릭 후

[그림 5-8]의 결과를 보면 정상적으로 수행은 된거 같은데 어떤 결과가 왔을지 사뭇 궁금해진다. 결과를 확인하기 위해 [그림 5-8]에 보이는 Response data 탭을 클릭하고 적절하게 위치를 이동하면 아래와 같은 결과를 확인해볼 수 있다.


[그림 5-9] 정상적으로 받아온 데이터

[그림 5-9]에 보이는 화면과 같이 데이터를 정상적으로 받아온 것을 확인할 수 있다. 물론 [그림 5-9]에 보이는 것과 같이 보이지 않는다면 [그림 5-9] 하단에 보이는 Render HTML이나 Show Text 라디오 버튼을 선택해야 한다. Render HTML의 경우 일부 웹 사이트는 정상적으로 보여지지 않을 수 있기 때문에 Show Text 라디오 버튼을 선택해서 보는 것이 더 정확하다.

사족으로 [그림 5-9]에 보이는 내용은 가수 이소은씨가 팬카페에 2007년 4월에 게시판에 남겨준 내용이다.

여기까지 잘 따라왔다면 정상적으로 처리가 되어야 하지만 그렇지 않다면 뭔가 이상이 있는 경우이므로 다시한번 확인해보기로 한다.

6. JMeter 로그 분석하기

JMeter로 테스트는 했는데 정작 테스트 결과를 볼 수 없거나 엉뚱한 자료만 가지고 있다면 분석은 못하게 될것이다.

이를 위해서 Listener로 Summary Report element를 추가해주는 것을 권장한다.

섹션 5에서 수행했던 Summary Report를 보면 [그림 6-1]과 같다.


[그림 6-1] Summary Report

[그림 6-1]은 섹션 5에서 수행된 Summary Report인데 눈여겨 봐야 할 컬럼은 총 5개로 위 그림에서 강조되어 있다.

결과 분석을 위해 첫번째 컬럼부터 살펴본다.
  • 1번째 컬럼 : 서버에 요청한 횟수
  • 2번째 컬럼 : 평균 응답시간(ms단위)
  • 3번째 컬럼 : 최소 응답시간(ms단위)
  • 4번째 컬럼 : 최대 응답시간(ms단위)
  • 5번째 컬럼 : Error율(%단위)
5번째 컬럼을 제외하곤 4번째 컬럼까지는 값의 단위는 밀리 세컨드 단위(ms)이다. 1초는 0.001 밀리세컨드이다. 계산이 귀찮다면 google 에 밀리 세컨드 값을 넣고 다음과 같이 검색어를 입력한다.

78 milliseconds to seconds

그러면 google이 자동으로 계산해준다.

5번째 컬럼은 서버로의 요청 중에 문제가 있을 경우 에러가 났다거나 한 경우 그 에러율을 퍼센테이지로 표시해준다.

여기서 살펴본 것을 기준으로 간단한 보고서 작성이 가능하겠으나 더 자세한 보고서 작성을 위해 JMeter User Manual을 참조해 다양한 Listener 추가를 통해 다양한 결과 형태를 볼 수 있으므로 꼭 한번씩 참조해서 실행해보는 것을 권장한다.

7. JMeter 활용 전략

JMeter는 그 초기 목적과 달리 다양한 테스트를 위해 확장되고 있으므로 여러 목적으로 사용할 수 있다.

따라서 저자는 JDBC, Java Request, JUnit, FTP 등을 테스트 하기 위해 JMeter 사용을 권장한다.

JMeter의 보다 폭 넓은 활용을 위해서 다음과 같은 사용 전략을 제시한다.
  • JDBC 테스트 : DB가 얼마나 부하를 견딜 수 있는지 알고 싶다
  • Java Request 테스트 : 순수 자바 애플리케이션을 만들고 있는데 별도의 테스트 툴이 없어서 힘들다.
  • JUnit 테스트 : 애플리케이션 테스트를 위해 Junit Test Case를 만들었는데 JMeter로 대신 실행해서 JUnit을 부하 테스트 하는데 사용하고 싶다.
  • FTP 테스트 : FTP 서버를 구축했는데 얼마나 접속했을 때 다운되는지 알고 싶다(실제 이렇게 할 사람이 있는지 모르겠다)
물론 위와 같은 사용 제시 외에도 JMeter는 대표적인 오픈소스 IDE인 Eclipse와 연동해서 사용하는 것이 가능하다. 다음에 기회가 되면 이 같은 사용법을 제시할 수 있기 바란다.
2009. 4. 14. 11:30

JAAS(Java Authentication and Authorization Service) 개요




JAAS(Java Authentication and Authorization Service) 개요

 

  JAAS는 클라이언트 및 서버 시스템을 보호하기 위한 유연하고 확장성 있는 자바 인증 및 권한 부여 서비스를 제공하는 표준 API 입니다.

 

  JAAS JDK 1.3에서는 선택적인 패키지로 소개되었으며, J2SE 1.4 부터 표준 확장 API(javax.security.auth)로 배포되고 있습니다.

 

  JAAS는 플러그 가능한 구조로 되어 있어 자바 용용 프로그램이 JAAS 구현 기술에 관계없이 구현할 수 있도록 지원합니다. , 응용 프로그램 개발자는 제공되는 SPI를 이용하여 JAAS 구현 부분과 인터페이스를 하고 구현 부분은 설정 파일을 통하여 구동 시 결정됩니다.

 

  따라서, JAAS 기반으로 개발된 응용 프로그램은 소스 변경 및 재 컴파일 없이 설정 파일을 변경함으로써 JAAS 구현 메커니즘을 변경할 수 있습니다.

 

  JAAS는 다음과 같은 패턴 및 프레임워크에서 많은 장점들을 받아들였습니다.

 

- PAM (Pluggable Authentication Module) Stackable Feature

- 2-PC (Two-phase commit)  - Transactional View

- Policy, Permission J2SE security package

- Subject Java security framework

 

 

1. 주요 SPI(Service Provider Interface) 인터페이스

 

1.1 LoginContext 클래스

  응용 프로그램과의 인터페이스를 정의 합니다. 응용 프로그램은 Login Context 인터페이스만을 사용하여 로그인 및 로그 아웃을 수행하고 Principal Credential에 대한 정보를 얻을 수 있습니다.

 

주요 메소드들은 다음과 같습니다.



login()      : 로그인을  수행합니다.

getSubject() : 로그인으로  인증된 사용자 정보를 리턴 합니다.

logout()     : 로그아웃을 수행합니다.


1.2 LoginModule 인터페이스

  LoginContext 클래스에서 내부적으로 사용되며 응용 프로그램이 직접 사용하지 않습니다.

서비스 제공자는 이 인터페이스를 구현해야 합니다. 구현된 클래스들은 환경설정 파일을 통하여 LoginContext 생성 시 동적으로 로드 됩니다.

 

  JDK 1.4부터 제공하는 로그인 모듈들은 다음과 같은 것들이 있습니다.

 


JndiLoginModule : com.sun.security.auth.module.JndiLoginModule

Krb5LoginModule : com.sun.security.auth.module.Krb5LoginModule

NTLoginModule : com.sun.security.auth.module.NTLoginModule

UnixLoginModule : com.sun.security.auth.module.UnixLoginModule

 


주요 메소드들은 다음과 같습니다.



initialize()     : 모듈 초기화를 수행합니다.

login()         : 사용자인증(로그인)을 수행합니다.

commit()      : 모든 로그인 모듈이 성공 한 후에 호출됩니다.

abort()         : 로그인을 취소합니다.

logout()        : 로그아웃을 수행합니다.

 

 

1.3 Subject 인터페이스

  한 사람에 대한 식별 이름(Principals: login id, 주민번호, email)들과 인증을 위하여 사용되는 개체 정보(Credentials: 비밀번호, ticket, key)들을 묶어서 관리합니다.

 

1.4 Principal 인터페이스

  개체를 나타내는 id (login id, 주민번호, email, etc)를 나타냅니다.

 

1.5 Credential 인터페이스

  인증을 위하여 사용되는 개체 정보(비밀번호, ticket, key)를 나타냅니다.

 

1.6 CallbackHandler, Callback 인터페이스

  사용자 인증에 필요한 정보 입력(예를 들어 id/pwd)을 처리하는 인터페이스로 응용프로그램에서 직접 구현 하거나 제공되는 다음과 같은 클래스들을 사용할 수 있습니다.



ChoiceCallback          : 선택항목들을 나열하고 하나 도는 다수를 선택하도록 합니다.

ConfirmationCallback  : YES/NO, OK/CANCEL, YES/NO/CANCEL등의 질문에 답하도록 합니다.

LanguageCallback      : 로케일을 입력 받습니다.

NameCallback           : 이름을 입력 받습니다.

PasswordCallback      : 프롬프트를 출력하고 비밀번호를 입력 받습니다.

TextInputCallback       : 프롬프트를 출력하고 텍스트를 입력 받습니다.

TextOutputCallback     : 경고 및 에러 메시지를 출력합니다.

 

 

 

2. JAAS 시스템 구조

   응용프로그램은 LoginContext 클래스만 사용하면 됩니다. 하부 JAAS SPI 구현은 LoginContext에 의하여 실행시 동적으로 로딩됩니다. SPI를 구현한 서비스 제공자는 데이터베이스, JNDI, 웹서비스, Unix 인증 등의 다양한 인증 서비스를 기반으로 작성 될 수 있습니다.

 

사용자 삽입 이미지
 

 

3. JAAS 설정파일

3.1 설정파일의 용도

  Login Module 구현 클래스들 및 파라미터 그리고 인증 프로세스를 제어하는 플래그들을 설정합니다.


3.2 설정 파일 이름 및 경로

  특별한 제한이 없으나 보통 파일명은 “jaas.conf”를 사용합니다. 설정파일 경로는 특별한 제한이 없으며 “javax.security.auth.login.Configuration” 시스템 환경변수를 이용하여 경로를 설정 해야 합니다.

 

  시스템 프로퍼티는 크게 두 가지 방법으로 설정할 수 있습니다. 첫 번째 방법은 운영체제에서 지원하는 시스템 환경 설정 방법을 이용하며 운영 체제 별로 다음과 같이 수행합니다.

 

- Windows :

 

    set JAVAOPTS=-Djavax.security.auth.login.Configuration=../conf/jaas.conf

 

    또는 GUI로 제공되는 환경 변수 설정 창을 이용합니다.


- Linux :

 

   export JAVAOPTS=-Djavax.security.auth.login.Configuration=../conf/jaas.conf

 

  두 번째 방법은 실행 시 명령 행을 이용하여 설정합니다.

 

   java Djavax.security.auth.login.Configuration ./jaas.conf AppClass

 

 

3.4 설정파일 구조

  설정파일은 하나 이상의 Application정의로 구성됩니다. Application은 보통 하나의 응용 프로그램에서 사용 될 LoginModule들의 구현에 대한 정보를 설정합니다. Application내에 LoginModule은 하나 이상 정의할 수 있으며 2-PC(Two-phase commit)로 인증 트랜잭션을 관리합니다.

 


      Application {

          ModuleClass  Flag    ModuleOptions;

          ModuleClass  Flag    ModuleOptions;

          ...

      };

 

      Application {

          ModuleClass  Flag    ModuleOptions;

          ...

      };

      ...



3.4.1 Application

  Application 이름으로 LoginContext 생성시 파라미터에 사용됩니다. 설정 파일에는 여러 응용프로그램들을 위한 LoginModule들이 정의될 수 있으므로 Application 이름으로 구분합니다.

 

3.4.2 ModuleClass

  LoginModule 인터페이스를 구현한 클래스 명을 기록합니다. 하나의 Application에는 하나 이상의 LoginModule들이 기술될 수 있으며 Flag에 따라 LoginModule들을 이용하여 인증을 수행합니다.

 

3.4.3 Flag

Application에 다수의 LoginModule들이 있을 때 인증(로그인) 과정을 제어하는데 사용됩니다.

 

- Required: 로그인 모듈 요구됨

  인증 성공이 요구되지만 하나이상의 LoginModule이 정의되어 있고 다른 로그인 모둘 중에 하나라도 성공하면 인증이 성공합니다. 따라서 반드시 성공할 필요는 없습니다. 하지만 Application LoginModule이 하나만 정의되어 있다면 반드시 성공해야 합니다.

 

- Requisite: 반드시 성공

  LoginModule을 통한 인증이 반드시 성공해야 합니다. 하나 이상의 LoginModule이 정의되어 있고 다른 LoginModule이 모두 성공하여도 이 LoginModule이 실패하면 최종적으로 실패하게 됩니다.

 

- Sufficient:이 것만 확인되면 충분함.

  만약 이 LoginModule의 인증이 성공 하면, 처리할 수 있는 다른 LoginModule이 있다고 하여도 더 이상 인증을 시도하지 않습니다. , 이 인증이 성공하면 바로 인증이 성공하게 됩니다.

 

- Optional : 선택사항

  이 LoginModule은 인증 트랜잭션에 아무런 영향을 주지 않습니다. LoginModule 인증을 수행하는 것은 옵션입니다.

 

3.4.4 ModuleOptions

  모듈에 전달할 옵션 값을 설정(모듈마다 다름)합니다. 예를 들어 데이터베이스에 연결하여 인증을 수행하는 경우라면 데이터베이스 연결정보가 될 것입니다.

       형식 : name=value name=value name=value ………

 

3.5 설정 파일 예제

 


Sample {

    com.sun.security.auth.module.NTLoginModule required debug=true;

};

 

Example {

      RdbmsLoginModule required debug="true driver="org.gjt.mm.mysql.Driver"

                url="jdbc:mysql://localhost/jaasdb?user=root;

};

 

 

3.6 JAAS Flow


0) 실행 시 명령 행 인자를 이용하여 시스템 프로퍼티 javax.security.auth.login.config를 설정하고 응용프로그램을 실행합니다.

 

1) 응용프로그램은 Application Name Subject(사용자 로그인을 위한 정보) Callback(사용자 로그인을 위한 정보를 입력 받기 위한)을 인자로 넘겨LoginContext를 생성합니다.

 

2) LoginContext는 환경설정 파일에서 Application Name에 해당하는 LoginModule 구현 클래스를 로딩합니다.

 

3) 생성된 LoginContext의 메소드들을 이용하여 인증 및 권한을 부여합니다.

 

사용자 삽입 이미지

 

4. 참고자료

 

-  JAAS 개요 및 LoginModule 작성방법

     http://www.javaworld.com/javaworld/jw-09-2002/jw-0913-jaas_p.html

 

-  JavaTM Authentication and Authorization Service (JAAS) Reference Guide

     http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASRefGuide.html


-  Introduction to JAAS and Java GSS-API Tutorials

     http://java.sun.com/j2se/1.4.2/docs/guide/security/jgss/tutorials/index.html


javax.security.auth 패키지

 



2009. 3. 17. 13:49

[java][성능] JVM GC와 메모리 튜닝




JVM GC와 메모리 튜닝




자바스터디 네트워크 [www.javastudy.co.kr]

조대협 [bcho_N_O_SPAM@j2eestudy.co.kr]



모든 Java Application은 JVM(Java Virtual Machine)위에서 동작한다.
이 JVM이 동작하는데 있어서, 메모리의 구조와 특히 GC는 Application의 응답시간과 성능에 밀접한 관계를 미친다. 이번 강좌에서는 JVM 의 메모리 구조와 GC 알고리즘 (JDK 1.4.X에 포함된 새로운 알고리즘 포함) 그리고, JVM의 메모리 튜닝을 통한 Application의 성능향상방법에 대해서 알아보도록 하자.


1.GC란 무엇인가?


GC는 Garbage Collection의 약자로 Java 언어의 중요한 특징중의 하나이다.
GC는 Java Application에서 사용하지 않는 메모리를 자동으로 수거하는 기능을 말한다.
예전의 전통적인 언어 C등의 경우 malloc, free등을 이용해서 메모리를 할당하고, 일일이 그 메모리를 수거해줘야했다. 그러나 Java 언어에서는 GC 기술을 사용함에 따라서 개발자로 하여금 메모리 관리에서 부터 좀더 자유롭게 해주었다.


2.GC의 동작 방법은 어떻게 되는가?


1) JVM 메모리 영역

GC의 동작 방법을 이해하기 위해서는 Java의 메모리 구조를 먼저 이해할 필요가 있다.
일반적으로 Application에서 사용되는 객체는 오래 유지 되는 객체보다, 생성되고 얼마안있어서 사용되지 않는 경우가 많다. <그림 1 참조>


<그림 1. 메모리 foot print>


그래서 Java에서는 크게 두가지 영역으로 메모리를 나누는데 Young 영역과 Old 영역이 그것이다.
Young 영역은 생긴지 얼마 안된 객체들을 저장하는 장소이고, Old영역은 생성된지 오래된 객체를 저장하는 장소이다. 각 영역의 성격이 다른 만큼 GC의 방법도 다르다.
먼저 Java의 메모리 구조를 살펴보자.


<그림 2. Java 메모리 구조>


Java의 메모리 영역은 앞에서 이야기한 두 영역 (Young 영역,Old 영역)과 Perm 영역 이렇게 3가지로 영역으로 구성된다.


<표 1. Java 메모리 영역>



2) GC 알고리즘

그러면 이 메모리 영역을 JVM이 어떻게 관리하는지에 대해서 알아보자.
JVM은 New/Young 영역과, Old영역 이 두영역에 대해서만 GC를 수행한다. Perm영역은 앞에서 설명했듯이 Code가 올라가는 부분이기 때문에, GC가 일어날 필요가 없다. Perm영역은 Code가 모두 Load되고 나면 거의 일정한 수치를 유지한다.


○ Minor GC
먼저 New/Young영역의 GC방법을 살펴보자 New/Young 영역의 GC를 Minor GC라고 부르는데, New/Young영역은 Eden과 Survivor라는 두가지 영역으로 또 나뉘어 진다. Eden영역은 Java 객체가 생성되자 마자 저장이 되는곳이다. 이렇게 생성된 객체는 Minor GC가 발생할때 Survivor 영역으로 이동된다.

Survivor 영역은 Survivor 1과 Suvivor2 영역 두 영역으로 나뉘어 지는데, Minor GC가 발생하면 Eden과 Survivor1에 Alive되어 있는 객체를 Suvivor2로 복사한다. 그리고 Alive되어 있지 않는 객체는 자연히 Suvivor1에 남아있게 되고, Survivor1과 Eden영역을 Clear한다. (결과적으로 Alive된 객체만 Survivor2로 이동한것이다.)
다음번 Minor GC가 발생하면 같은 원리로 Eden과 Survivor2영역에서 Alive되어 있는 객체를 Survivor1에 복사한다. 계속 이런 방법을 반복적으로 수행하면서 Minor GC를 수행한다.

이렇게 Minor GC를 수행하다가, Survivor영역에서 오래된 객체는 Old영역으로 옮기게 된다.

이런 방식의 GC 알고리즘을 Copy & Scavenge라고 한다. 이 방법은 매우 속도가 빠르며 작은 크기의 메모리를 Collecting하는데 매우 효과적이다. Minor GC의 경우에는 자주 일어나기 때문에, GC에 소요되는 시간이 짧은 알고리즘이 적합하다.

이 내용을 그림을 보면서 살펴보도록 하자.


<그림 3-1. 1st Minor GC>


Eden에서 Alive된 객체를 Suvivor1으로 이동한다. Eden 영역을 Clear한다.


<그림 3-2. 2nd Minor GC>


Eden영역에 Alive된 객체와 Suvivor1영역에 Alive된 객체를 Survivor 2에 copy한다.
Eden영역과 Suvivor2영역을 clear한다.


<그림 3-3. 3rd Minor GC>


객체가 생성된 시간이 오래지나면 Eden과 Suvivor영역에 있는 오래된 객체들을 Old 영역으로 이동한다.


○ Full GC

Old 영역의 Garbage Collection을 Full GC라고 부르며, Full GC에 사용되는 알고리즘은 Mark & Compact라는 알고리즘을 이용한다. Mark & Compact 알고리즘은 전체 객체들의 reference를 쭉 따라가다면서 reference가 연결되지 않는 객체를 Mark한다. 이 작업이 끝나면 사용되지 않는 객체를 모두 Mark가 되고, 이 mark된 객체를 삭제한다.<그림 4 참고> (실제로는 compact라고 해서, mark된 객체로 생기는 부분을 unmark된 즉 사용하는 객체로 메꾸어 버리는 방법이다.)

Full GC는 매우 속도가 느리며, Full GC가 일어나는 도중에는 순간적으로 Java Application이 멈춰 버리기 때문에, Full GC가 일어나는 정도와 Full GC에 소요되는 시간은 Application의 성능과 안정성에 아주 큰 영향을 준다.


<그림 4. Full GC>




3. GC가 왜 중요한가?


Garbage Collection중에서 Minor GC의 경우 보통 0.5초 이내에 끝나기 때문에 큰문제가 되지 않는다. 그러나 Full GC의 경우 보통 수초가 소요가 되고, Full GC동안에는 Java Application이 멈춰버리기 때문에 문제가 될 수 있다.
예를 들어 게임 서버와 같은 Real Time Server를 구현을 했을때, Full GC가 일어나서 5초동안 시스템이 멈춘다고 생각해보자.
또 일반 WAS에서도 5~10초동안 멈추면, 멈추는동안의 사용자의 Request가 Queue에 저장되었다가 Full GC가 끝난후에 그 요청이 한꺼번에 들어오게 되면 과부하에 의한 여러 장애를 만들 수 있다..
그래서 원할한 서비스를 위해서는 GC를 어떻게 일어나게 하느냐가 시스템의 안정성과 성능에 큰 변수로 작용할 수 있다.


4. 다양한 GC 알고리즘


앞에서 설명한 기본적인 GC방법 (Scavenge 와 Mark and compact)이외에 JVM에서는 좀더 다양한 GC 방법을 제공하고 그 동작방법이나 사용방법도 틀리다. 이번에는 다양한 GC 알고리즘에 대해서 알아보자. 현재 (JDK 1.4)까지 나와 있는 JVM의 GC방법은 크게 아래 4가지를 지원하고 있다.

- Default Collector
- Parallel GC for young generation (from JDK 1.4 )
- Concurrent GC for old generation (from JDK 1.4)
- Incremental GC (Train GC)

1) Default Collector
이 GC 방법은 앞에서 설명한 전통적인 GC방법으로 Minor GC에 Scavenge를, Full GC에 Mark & compact 알고리즘을 사용하는 방법이다. 이 알고리즘에는 이미 앞에서 설명했기 때문에 별도의 설명을 하지는 않는다.

JDK 1.4에서부터 새로 적용되는 GC방법은 Parallel GC와 Concurrent GC 두가지 방법이 있다. Parallel GC는 Minor GC를 좀더 빨리하게 하는 방법이고 (Throughput 위주) Concurrent GC는 Full GC시에 시스템의 멈춤(Pause)현상을 최소화하는 GC방법이다.

2) Parallel GC
JDK1.3까지 GC는 하나의 Thread에서 이루어진다. Java가 Multi Thread환경을 지원함에도 불구하고, 1 CPU에서는 동시에 하나의 Thread만을 수행할 수 밖에 없기때문에, 예전에는 하나의 CPU에서만 GC를 수행했지만, 근래에 들어서 하나의 CPU에서 동시에 여러개의 Thread를 실행할 수 있는 Hyper Threading기술이나, 여러개의 CPU를 동시에 장착한 HW의 보급으로 하나의 HW Box에서 동시에 여러개의 Thread를 수행할 수 있게 되었다.

JDK 1.4부터 지원되는 Parallel GC는 Minor GC를 동시에 여러개의 Thread를 이용해서 GC를 수행하는 방법으로 하나의 Thread를 이용하는것보다 훨씬 빨리 GC를 수행할 수 있다.


<그림 7. Parallel GC 개념도>


<그림 7> 을 보자 왼쪽의 Default GC방법은 GC가 일어날때 Thread들이 작업을 멈추고, GC를 수행하는 thread만 gc를 수행한다. (그림에서 파란영역), Parallel GC에서는 여러 thread들이 gc를 수행이 가능하기 때문에, gc에 소요되는 시간이 낮아진다.

Parallel GC가 언제나 유익한것은 아니다. 앞에서도 말했듯이 1CPU에서는 동시에 여러개의 thread를 실행할 수 없기 때문에 오히혀 Parallel GC가 Default GC에 비해서 느리다. 2 CPU에서도 Multi thread에 대한 지원이나 계산등을 위해서 CPU Power가 사용되기 때문에, 최소한 4CPU의 256M 정도의 메모리를 가지고 있는 HW에서 Parallel GC가 유용하게 사용된다.

Parallel GC는 크게 두가지 종류의 옵션을 가지고 있는데,Low-pause 방식과 Throughput 방식의 GC방식이 있다.

Solaris 기준에서 Low-pause Parallel GC는 ?XX:+UseParNewGC 옵션을 사용한다. 이 모델은 Old 영역을 GC할때 다음에 설명할 Concurrent GC방법과 함께 사용할 수 있다. 이 방법은 GC가 일어날때 빨리 GC하는것이 아니라 GC가 발생할때 Application이 멈춰지는 현상(pause)를 최소화하는데 역점을 뒀다.

Throughput 방식의 Parallel GC는 ?XX:+UseParallelGC (Solaris 기준) 옵션을 이용하며 Old 영역을 GC할때는 Default GC (Mark and compact)방법만을 사용하도록 되어 있다.Minor GC가 발생했을때, 되도록이면 빨리 수행하도록 throughput에 역점을 두었다.

그외에도 ParallelGC를 수행할때 동시에 몇개의 Thread를 이용하여 Minor영역을 Parallel GC할지를 결정할 수 있는데, -XX:ParallelGCThreads= 옵션을 이용하여 Parallel GC에 사용되는 Thread의 수를 지정할 수 있다.

3) Concurrent GC

앞에서도 설명했듯이, Full GC즉 Old 영역을 GC하는 경우에는 그 시간이 길고 Application이 순간적으로 멈춰버리기 때문에, 시스템 운용에 문제가 된다.

그래서 JDK1.4부터 제공하는 Concurrent GC는 기존의 이런 Full GC의 단점을 보완하기 위해서 Full GC에 의해서 Application이 멈추어 지는 현상을 최소화 하기 위한 GC방법이다.
Full GC에 소요되는 작업을 Application을 멈추고 진행하는것이 아니라, 일부는 Application이 돌아가는 단계에서 수행하고, 최소한의 작업만을 Application이 멈췄을때 수행하는 방법으로 Application이 멈추는 시간을 최소화한다.


<그림 8. Concurrent GC 개념도>


그림 8에서와 같이 Application이 수행중일때(붉은 라인) Full GC를 위한 작업을 수행한다. (Sweep,mark) Application을 멈추고 수행하는 작업은 일부분 (initial-mark, remark 작업)만을 수행하기 때문에, 기존 Default GC의 Mark & Sweep Collector에 비해서 Application이 멈추는 시간이 현저하게 줄어든다.

Solaris JVM에서는 -XX:+UseConcMarkSweepGC Parameter를 이용해 세팅한다.

4) Incremental GC (Train GC)

Incremental GC또는 Train GC라고도 불리는 GC방법은 JDK 1.3에서부터 지원된 GC방법이다. 앞에서 설명한 Concurrent GC와 비슷하게, 의도 자체는 Full GC에 의해서 Application이 멈추는 시간을 줄이고자 하는데 있다.

Incremental GC의 작동방법은 간단하다. Minor GC가 일어날때 마다 Old영역을 조금씩 GC를 해서 Full GC가 발생하는 횟수나 시간을 줄이는 방법이다.


<그림 9. Incremental GC 개념도>


그림 9에서 보듯이. 왼쪽의 Default GC는 FullGC가 일어난후에나 Old 영역이 Clear된다. 그러나, 오른쪽의 Incremental GC를 보면 Minor GC가 일어난후에, Old 영역이 일부 Collect된것을 볼 수 있다.

Incremental GC를 사용하는 방법은 JVM 옵션에 ?Xinc 옵션을 사용하면 된다.
Incremental GC는 많은 자원을 소모하고, Minor GC를 자주일으키고, 그리고 Incremental GC를 사용한다고 Full GC가 없어지거나 그 횟수가 획기적으로 줄어드는 것은 아니다. 오히려 느려지는 경우가 많다. 필히 테스트 후에 사용하도록 하자.

※ Default GC이외의 알고리즘은 Application의 형태나 HW Spec(CPU수, Hyper threading 지원 여부), 그리고 JVM 버전(JDK 1.4.1이냐 1.4.2냐)에 따라서 차이가 매우 크다. 이론상으로는 실제로 성능이 좋아보일 수 있으나, 운영환경에서는 여러 요인으로 인해서 기대했던것만큼의 성능이 안나올 수 있기 때문에, 실환경에서 미리 충분한 테스트를 거쳐서 검증한후에 사용해야 한다.


5. GC 로그는 어떻게 수집과 분석


JVM에서는 GC 상황에 대한 로그를 남기기 위해서 옵션을 제공하고 있다.
Java 옵션에 ?verbosegc 라는 옵션을 주면되고 HP Unix의 경우 ?verbosegc ?Xverbosegc 옵션을 주면 좀더 자세한 GC정보를 얻을 수 있다. GC 정보는 stdout으로 출력이 되기 때문에 “>” redirection등을 이용해서 file에 저장해놓고 분석할 수 있다.

Example ) java ?verbosegc MyApplication

그럼 실제로 나온 GC로그를 어떻게 보는지를 알아보자.


<그림 5. 일반적인 GC 로그, Windows, Solaris>


<그림 5>는 GC로그 결과를 모아논 내용이다. (실제로는 Application의 stdout으로 출력되는 내용과 섞여서 출력된다.)
Minor GC는 ”[GC “로 표기되고, Full GC는 “[Full GC”로 표기된다.
그 다음값은 Heap size before GC인데,GC 전에 Heap 사용량 ( New/Young 영역 + Old 영역 + Perm 영역)의 크기를 나타낸다.

Heap size after GC는 GC가 발생한후에 Heap의 사용량이다. Minor GC가 발생했을때는 Eden과 Survivor 영역으 GC가 됨으로 Heap size after GC는 Old영역의 용량과 유사하다.(Minor GC에서 GC되지 않은 하나의 Survivor영역내의 Object들의 크기도 포함해야한다.)

Total Heap Size는 현재 JVM이 사용하는 Heap Memory양이다. 이 크기는 Java에서 ?ms와 ?mx 옵션으로 조정이 가능한데. 예를 들어 ?ms512m ?mx1024m로 해놓으면 Java Heap은 메모리 사용량에 따라서 512~1024m사이의 크기에서 적절하게 늘었다 줄었다한다. (이 늘어나는 기준과 줄어드는 기준은 (-XX:MaxHeapFreeRatio와 ?XX:MinHeapFreeRation를 이용해서 조정할 수 있으나 JVM vendor에 따라서 차이가 나기때문에 각 vendor별 JVM 메뉴얼을 참고하기 바란다.) Parameter에 대한 이야기는 추후에 좀더 자세히하도록 하자.

그 다음값은 GC에 소요된 시간이다.

<그림 5>의 GC로그를 보면 Minor GC가 일어날때마다 약 20,000K 정도의 Collection이 일어난다. Minor GC는 Eden과 Suvivor영역 하나를 GC하는 것이기 때문에 New/Young 영역을 20,000Kbyte 정도로 생각할 수 있다.

Full GC때를 보면 약44,000Kbyte에서 1,749Kbyte로 GC가 되었음을 볼 수 있다. Old영역에 큰 데이타가 많지 않은 경우이다. Data를 많이 사용하는 Application의 경우 전체 Heap이 512이라고 가정할때, Full GC후에도 480M정도로 유지되는 경우가 있다. 이런 경우에는 실제로 Application에서 Memory를 많이 사용하고 있다고 판단할 수 있기 때문에 전체 Heap Size를 늘려줄 필요가 있다.

이렇게 수집된 GC로그는 다소 보기가 어렵기 때문에, 좀더 쉽게 분석할 수 있게 하기 위해서 GC로그를 awk 스크립트를 이용해서 정제하면 분석이 용이하다.


<표 2. gc.awk 스크립트>


이 스크립트를 작성한후에 Unix의 awk 명령을 이용해서

% awk ?f gc.awk GC로그파일명

을 쳐주면 아래<표 3>와 같이 정리된 형태로 GC 로그만 추출하여 보여준다.


<표 3. gc.awk 스크립트에 의해서 정재된 로그>


Minor와 Major는 각각 Minor GC와 Full GC가 일어날때 소요된 시간을 나타내며, Alive는 GC후에 남아있는 메모리양, 그리고 Freed는 GC에 의해서 collect된 메모리 양이다.

이 로그파일은 excel등을 이용하여 그래프등으로 변환해서 보면 좀더 다각적인 분석이 가능해진다.

※ JDK 1.4에서부터는 ?XX:+PrintGCDetails 옵션이 추가되어서 좀더 자세한 GC정보를 수집할 수 있다.


※ HP JVM의 GC Log 수집

HP JVM은 전체 heap 뿐 아니라 ?Xverbosegc 옵션을 통해서 Perm,Eden,Old등의 모든 영역에 대한 GC정보를 좀더 정확하게 수집할 수 있다.

Example ) java ?verbosegc ?Xverbosegc MyApplication ß (HP JVM Only)

HP JVM의 GC정보는 18개의 필드를 제공하는데 그 내용을 정리해보면 <표 4.>와 같다.

<GC : %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16 %17 %18>


<표 4. HP JVM GC 로그 필드별 의미>


이 로그를 직접 보면서 분석하기는 쉽지가 않다. 그래서, HP에서는 좀더 Visual한 환경에서 분석이 가능하도록 HPJtune이라는 툴을 제공한다. 다음 URL에서 다운로드 받을 수 있다.

http://www.hp.com/products1/unix/java/java2/hpjtune/index.html


<그림 6. HP Jtune을 이용해서 GC후 Old영역의 변화 추이를 모니터링하는 화면>




6. GC 관련 Parameter


GC관련 설정값을 보기전에 앞서서 ?X와 ?XX 옵션에 대해서 먼저 언급하자. 이 옵션들은 표준 옵션이 아니라, 벤더별 JVM에서 따로 제공하는 옵션이기 때문에, 예고 없이 변경되거나 없어질 수 있기 때문에, 사용전에 미리 JVM 벤더 홈페이지를 통해서 검증한다음에 사용해야한다.

1) 전체 Heap Size 조정 옵션

전체 Heap size는 ?ms와 ?mx로 Heap 사이즈의 영역을 조정할 수 있다. 예를 들어 ?ms512m ?mx 1024m로 설정하면 JVM은 전체 Heap size를 application의 상황에 따라서 512m~1024m byte 사이에서 사용하게 된다. 그림2의 Total heap size

메모리가 모자를때는 heap을 늘리고, 남을때는 heap을 줄이는 heap growing과 shirinking 작업을 수행하는데, 메모리 변화량이 큰 애플리케이션이 아니라면 이 min heap size와 max heap size는 동일하게 설정하는 것이 좋다. 일반적으로 1GB까지의 Heap을 설정하는데에는 문제가 없으나, 1GB가 넘는 대용량 메모리를 설정하고자 할 경우에는 별도의 JVM 옵션이 필요한 경우가 있기때문에 미리 자료를 참고할 필요가 있다.

※ IBM AIX JVM의 경우
%export LDR_CNTRL=MAXDATA=0x10000000
%java -Xms1500m -Xmx1500m MyApplication

2) Perm size 조정 옵션

Perm Size는 앞에서도 설명했듯이, Java Application 자체(Java class etc..)가 로딩되는 영역이다. J2EE application의 경우에는 application 자체의 크기가 큰 편에 속하기 때문에, Default로 설정된 Perm Size로는 application class가 loading되기에 모자른 경우가 대부분이기 때문에, WAS start초기나, 가동 초기에 Out Of Memory 에러를 유발하는 경우가 많다.

PermSize는 -XX:MaxPermSize=128m 식으로 지정할 수 있다.
일반적으로 WAS에서 PermSize는 64~256m 사이가 적절하다.

3) New 영역과 Old 영역의 조정New 영역은 ?XX:NewRatio=2 에 의해서 조정이 된다.
NewRatio Old/New Size의 값이다. 전체 Heap Size가 768일때, NewRatio=2이면 New영역이 256m, Old 영역이 512m 로 설정이 된다.
JVM 1.4.X에서는 ?XX:NewSize=128m 옵션을 이용해서 직접 New 영역의 크기를 지정하는 것이 가능하다.

4) Survivor 영역 조정 옵션
-XX:SurvivorRatio=64 (eden/survivor 의 비율) :64이면 eden 이 128m일때, survivor영역은 2m가 된다.

5) -server와 ?client 옵션
JVM에는 일반적으로 server와 client 두가지 옵션을 제공한다.
결론만 말하면 server 옵션은 WAS와 같은 Server환경에 최적화된 옵션이고, client옵션은 워드프로세서와 같은 client application에 최적화된 옵션이다. 그냥 언뜻 보기에는 단순한 옵션 하나로보일 수 있지만, 내부에서 돌아가는 hotspot compiler에 대한 최적화 방법과 메모리 구조자체가 아예 틀리다.

○ -server 옵션

server용 application에 최적화된 옵션이다. Server application은 boot up 시간 보다는 user에 대한 response time이 중요하고, 많은 사용자가 동시에 사용하기 때문에 session등의 user data를 다루는게 일반적이다. 그래서 server 옵션으로 제공되는 hotspot compiler는 java application을 최적화 해서 빠른 response time을 내는데 집중되어 있다.

또한 메모리 모델 역시, 서버의 경우에는 특정 사용자가 서버 운영시간동안 계속 서버를 사용하는게 아니기 때문에 (Login하고, 사용한 후에는 Logout되기 때문에..) 사용자에 관련된 객체들이 오래 지속되는 경우가 드물다. 그래서 상대적으로 Old영역이 작고 New 영역이 크게 배정된다. <그림 7. 참조 >

○ -client 옵션

client application은 워드프로세서 처럼 혼자 사용하는 application이다. 그래서 client application은 response time보다는 빨리 기동되는데에 최적화가 되어 있다. 또한대부분의 client application을 구성하는 object는GUI Component와 같이 application이 종료될때까지 남아있는 object의 비중이 높기 때문에 상대적으로 Old 영역의 비율이 높다.


<그림 7. ?server와 ?client 옵션에 따른 JVM Old와 New영역>


이 두옵션은 가장 간단한 옵션이지만, JVM의 최적화에 아주 큰부분을 차지하고 있는 옵션이기 때문에, 반드시 Application의 성격에 맞춰서 적용하기 바란다.
(※ 참고로, SUN JVM은 default가 client, HPJVM는 default가 server로 세팅되어 있다.)

○ GC 방식에 대한 옵션

GC 방식에 대한 옵션은 앞에서도 설명했지만, 일반적인 GC방식이외에, Concurrent GC,Parallel GC,Inceremental GC와 같이 추가적인 GC Algorithm이 존재한다. 옵션과 내용은 앞장에서 설명한 “다양한 GC알고리즘” 을 참고하기 바란다.


7.JVM GC 튜닝


그러면 이제부터 지금까지 설명한 내용을 기반으로 실제로 JVM 튜닝을 어떻게 하는지 알아보도록 하자.

STEP 1. Application의 종류와 튜닝목표값을 결정한다.

JVM 튜닝을 하기위해서 가장 중요한것은 JVM 튜닝의 목표를 설정하는것이다. 메모리를 적게 쓰는것이 목표인지, GC 횟수를 줄이는것이 목표인지, GC에 소요되는시간이 목표인지, Application의 성능(Throughput or response time) 향상인지를 먼저 정의한후에. 그 목표치에 근접하도록 JVM Parameter를 조정하는것이 필요하다.

STEP 2. Heap size와 Perm size를 설정한다.

-ms와 ?mx 옵션을 이용해서 Heap Size를 정한다. 일반적으로 server application인 경우에는 ms와 mx 사이즈를 같게 하는것이 Memory의 growing과 shrinking에 의한 불필요한 로드를 막을 수 있어서 권장할만하다.

ms와mx사이즈를 다르게 하는 경우는 Application의 시간대별 memory 사용량이 급격하게 변화가 있는 Application에 효과적이다.
PermSize는 JVM vendor에 따라 다소 차이가 있으나 일반적으로 16m정도이다. Client application의 경우에는 문제가 없을 수 있지만, J2EE Server Application의 경우 64~128m 사이로 사용이 된다.

Heap Size와 Perm Size는 아래 과정을 통해서 적정 수치를 얻어가야한다.

STEP 3. 테스트 & 로그 분석.

JVM Option에 GC 로그를 수집하기 위한 ?verbosegc 옵션을 적용한다. (HP의 경우 ?Xverbosegc 옵션을 적용한다.)

LoadRunner나 MS Strest(무료로 MS社의 홈페이지에서 다운로드 받을 수 있다.)와 같은 Strest Test툴을 통해서 Application에 Strest를 줘서. 그 log를 수집한다. 튜닝에서 있어서 가장 중요한것은 목표산정이지만, 그만큼이나 중요한것은 실제 Tuning한 Parameter가 Application에 어떤 영향을 주는지를 테스트하는 방법이 매우 중요하다. 그런 의미에서 적절한 Strest Tool의 선정과, Strest Test 시나리오는 정확한 Tuning을 위해서 매우 중요한 요인이다.

○ Perm size 조정
아래 그림8.은 HP JVM에서 ?Xverbosegc 옵션으로 수집한 GC log를 HP Jtune을 통해서 graph로 나타낸 그래프이다. 그림을 보면 Application이 startup되었을때 Perm 영역이 40m에서. 시간이 지난후에도 50m 이하로 유지되는것을 볼 수 있다. 특별하게 동적 classloading등이 수십m byte가 일어나지 않는등의 큰 변화요인이 없을때, 이 application의 적정 Perm 영역은 64m로 판단할 수 있다.


<그림 8. GC 결과중 Perm 영역 그래프>


○ GC Time 수행 시간 분석

다음은 GC에 걸린 시간을 분석해보자. 앞에 강좌 내용에서도 설명햇듯이. GC Tuning에서 중요한 부분중 하나가 GC에 소요되는 시간 특히 Full GC 시간이다.

지금부터 볼 Log는 모社의 물류 시스템의 WAS 시스템 GC Log이다. HP JVM을 사용하며, -server ?ms512m ?mx512m 옵션으로 기동되는 시스템이다.

그림 9를 보면 Peak 시간 (첫번째 동그라미) 14시간동안에 Full GC(동그란점)가 7번일어난것을 볼 수 있다. 각각에 걸린 시간은2.5~6sec 사이이다.
여기서 STEP 1.에서 설정한 AP Tuning의 목표치를 참고해야하는데.

Full GC가 길게 일어나서 Full GC에 수행되는 시간을 줄이고자 한다면 Old 영역을 줄이면 Full GC가 일어나는 횟수는 늘어나고, 반대로 Full GC가 일어나는 시간을 줄어들것이다.

반대로 Full GC가 일어나는 횟수가 많다면, Old 영역을 늘려주면 Full GC가 일어나는 횟수는 상대적으로 줄어들것이고 반대로 Full GC 수행시간이 늘어날 것이다.

특히 Server Application의 경우Full GC가 일어날때는 JVM자체가 멈춰버리기 때문에, 그림 9의 instance는 14시간동안 총 7번 시스템이 멈추고, 그때마다 2.5~6sec가량 시스템이 response를 못하는 상태가 된것이다. 그래서 멈춘 시간이 고객이 납득할만한 시간인지를 판단해야 하고, 거기에 적절한 Tuning을 해야한다.

Server Application에서 Full GC를 적게일어나게하고, Full GC 시간을 양쪽다 줄이기 위해서는 Old영역을 적게한후에, 여러개의 Instance를 동시에 뛰어서 Load Balancing을 해주면, Load가 분산되기 때문에 Full GC가 일어나는 횟수가 줄어들테고, Old 영역을 줄였기 때문에, Full GC에 드는 시간도 줄어들것이다. 또한 각각의 FullGC가 일어나는동안 하나의 서버 instance가 멈춰져 있어도, Load Balancing이 되는 다른 서버가 response를 하고 있기때문에, Full GC로 인한 Application이 멈추는것에 의한 영향을 최소화할 수 있다.


<그림 9. GC 소요시간>


데이타에 따라서 GC Tuning을 진행한후에는 다시 Strest Test를 진행해서 응답시간과 TPS(Throughput Per Second)를 체크해서 어떤 변화를 주었는지를 반드시 체크해봐야한다.


<그림 10. GC후의 Old 영역>


그림 10은 GC후에 Old 영역의 메모리 변화량을 나타낸다.

금요일 업무시간에 메모리 사용량이 올라가다가. 주말에가서 완만한 곡선을 그리는것을 볼 수 있다. 월요일 근무시간에 메모리 사용량이 매우 많고, 화요일에도 어느정도 메모리 사용량이 있는것을 볼 수 있다. 월요일에 메모리 사용량이 많은것을 볼때, 이 시스템의 사용자들이 월요일에 시스템 사용량이 많을 수 있다고 생각할 수 있고, 또는 다른 주의 로그를 분석해봤을때 이 주만 월요일 사용량이 많았다면, 특별한 요인이나 Application 변경등이 있었는지를 고려해봐야할것이다.

이 그래프만을 봤을때 Full GC가 일어난후에도 월요일 근무시간을 보면 Old 영역이 180M를 유지하고 있는것을 볼 수 있다. 이 시스템의 Full GC후의 Old영역은 80M~180M를 유지하는것을 볼 수 있다. 그래서 이 시스템은 최소 180M이상의 Old 영역을 필요로하는것으로 판단할 수 있다.

STEP 4. Parameter 변경.
STEP 3에서 구한 각 영역의 허용 범위를 기준으로 Old영역과 New 영역을 적절하게 조절한다.
PermSize와 New영역의 배분 (Eden,Survivor)영역등을 조정한다.
PermSize는 대부분 Log에서 명확하게 나타나기 때문에, 크게 조정이 필요가 없고 New영역내의 Eden과 Survivor는 거의 조정하지 않는다. 가장 중요한것은 Old영역과 New 영역의 비율을 어떻게 조정하는가가 관건이다.

이 비율을 결정하면서, STEP1에서 세운 튜닝 목표에 따라서 JVM의 GC Algorithm을 적용한다. GC Algorithm을 결정하는 기본적인 판단 내용은 아래와 같다.



이렇게 Parameter를 변경하면서 테스트를 진행하고, 다시 변경하고 테스트를 진행하는 과정을 거쳐서 최적의 Parameter와 GC Algorithm을 찾아내는것이 JVM의 메모리 튜닝의 이상적인 절차이다.


지금까지 JVM의 메모리 구조와 GC 모델 그리고 GC 튜닝에 대해서 알아보았다.

정리하자면 GC 튜닝은 Application의 구조나 성격 그리고, 사용자의 이용 Pattern에 따라서 크게 좌우 되기때문에, 얼마만큼의 Parameter를 많이 아느냐 보다는 얼마만큼의 테스트와 로그를 통해서 목표 값에 접근하느냐가 가장 중요하다.
2009. 3. 17. 13:32

미리 보는 미래 개발환경「자바SE 6





[지디넷코리아]혁신의 중심에 있으면서 동시에 성장하고 진화하는 자바 기술의 원천은 자바를 사용하고 있는 5백만 명의 개발자들이다. 활발하게 활동하고 있는 자바 개발자 커뮤니티는 자바 커뮤니티 프로세스 프로그램(Java Community Process Program)을 통해 누구나 자유롭게 아이디어를 제공하고 동시에 변화를 제안하면서 자바 플랫폼의 미래를 설계할 수 있다. 특히, 이번 자바원에서는 엔터프라이즈 자바 플랫폼의 최신 기술과 특장점을 반영한 자바 플랫폼 엔터프라이즈(자바EE) 5가 새롭게 선보였다. 더불어 곧 출시될 예정인 자바플랫폼 스탠다드 에디션(자바SE) 6에 대한 소개도 이어졌다.

<그림 1> 숫자로 보는 자바


  자바SE(Java Standard Edition)?

자바 플랫폼 스탠다드 에디션에는 두 가지 주요 제품이 있다. Java SE Runtime Environment(이하 JRE)와 Java SE Development Kit(이하 JDK)가 그것이다. JRE는 자바 API, 자바 버츄얼 머신, 그리고 자바 프로그래밍 언어상에서 구동되는 애플릿과 애플리케이션에 필요한 컴포넌트이다. 자바SE는 또한 엔터프라이즈 소프트웨어 개발 및 디플로이를 위한 자바엔터프라이즈 에디션(Java EE)의 근간이기도 하다. JDK는 JRE상에 있는 모든 구성요소 외에도 애플릿과 애플리케이션을 개발하는 데에 필요한 컴파일러와 디버거 등의 툴도 포함한다.

<그림2> 자바 플랫폼 스탠다드 에디션 (Java SE)

<그림 2>는 이러한 자바SE의 플랫폼상의 모든 컴포넌트 기술을 보여준다.

<그림 3> 자바SE의 로드맵

<그림 3>에서도 알 수 있듯이 자바플랫폼 스탠다드 에디션은 새로운 특장점이 소개될 때마다 약 18개월의 주기로 업데이트 된다. 그 외에도 매 8주에서 16주를 주기로 버그 수정이 업데이트 된다.

썬은 지난 2005년 자바원에서 J2SE의 이름을 자바SE로 변경했다. 이후 5.1과 6.1이라는 추가 버전 없이 오는 2006년 10월 발표 예정인 자바SE 6 이후에는 버그 수정에서부터 특장점까지 커뮤니티 상에서의 기여 및 참여를 통해 업데이트 되며, 자바SE 7(코드명: 돌핀)은 2008년 하반기에 발표할 예정이다.

자바SE 6(코드명: 머스탱)는 지난 2004년 발표한 자바SE 5.0(코드명: 타이거)의 버그를 수정하고, 기존 프로그램을 운영할 수 있도록 했다. 또 질적 향상을 위해 소스와 바이너리를 활용하여 개발자들이 보다 오픈된 환경에서 개발할 수 있도록 개선되었다. 자바SE 6는 호환성, 안정성, 질적 향상에 중점을 두어 개발되었다.

<표 1> JDK
<표 2> 서버 벤치 마크:SPECjbb2000(기존 버전과 비교시 서버 벤치마크상 성능향상을 보여준다.)

자바SE 6는 지속적으로 자바 성능 향상에 중점을 두고, 클라이언트, 서버 양쪽의 성능 향상, 코어 JVM (Java Virtual Machine, 자바 버츄얼 머신)으로의 업그레이드, GC 스케일링과 패럴리즘, 라이브러리 튜닝, 자바2D그래픽 렌더링으로의 업그레이드와 구동 속도를 높이는 등의 기능이 강화되었다(<표 1>과 <표 2> 참조). 그 외에도 모니터링과 관리기능을 중점으로 보다 나은 JVM레벨의 테스팅, 메모리 핸들링 향상, JMX업그레이드,솔라리스 Dtrac와의 연계 기능 향상과 J콘솔 업그레이드 등 모니터링 기능이 한층 강화되었다. (<화면 1> 참조)

<화면 1> J컨솔 업그레이드

이렇듯 자바SE 6는 고객들의 자바SE 의존도가 높은 만큼 호환성, 안정성, 질적 향상을 중점으로 최신 기술과 개발자들의 니즈를 반영하여 다양한 특장점이 업데이트 되고 있다. J2SE 1.4 버전이후에 자바 스탠다드 에디션의 중심에 있는 XML은 주요 데이터 교환 기술로 웹서비스의 근간이 되고 있다. 자바SE 6는 클라이언트 중심뿐만 아니라 경량의 서버에까지 적용되는 클라이언트 스택으로(JSR 105, 173,181, 222,224) 업데이트된 코어XML 스택을 포함한다.

또한, 개발 용이성을 주제로 개발자들이 보다 생산적으로 작업을 단순화할 수 있도록 스크립트 언어 지원 (JSR-223), XML데이터 지원을 포함한 JDBC 4.0 (JSR-221), 기타 API 등이 향상
되었다. 이외에도 데스크톱 자바는 모든 자바 성공에 있어 중요한 만큼 Java SE 6 또한 이러한 기능 향상을 포함하고 있다. 아발론 룩앤필을 포함한 윈도우 비스타 지원과 그동안의 자바 기술의 단점으로 지적되어 온 GUI를 개선하기 위해 윈도우 시스템의 트레이 기능 지원, 스플래시 스크린 지원, 그래픽 파이프라인 부스트 등 많은 GUI기능을 업그레이드했다.

자바SE 6는‘보다 공개된 (Open)개발 환경’(소스와 바이너리면에서)을 목적으로 많은 개발자들의 참여와 기여를 기다리고 있다. 이 같은 자바SE 6 (머스탱)의 성공적인 런칭과 구현을 위해서는 개발자들의 적극적인 커뮤니티 참여가 절실히 필요하다.

이러한 공동 참여와 기여를 통해 현 개발환경에서 필요한 개발자들의 니즈를 반영하여 새로운 특장점을 업데이트하고 버그를 수정하여 자바SE 6가 완성된다. 이러한 시도는 기존의 베타 버전과는 다른 새로운 경험 및 발상의 전환이 될 것이다. 참여 URL은‘mustang.dev.java.net’이다.

  자바SE 6의 장점

자바SE 로드맵 상에서 볼 수 있는 자바SE 7(코드명: 돌핀)은 기존 자바SE 6(머스탱) 버전보다 더 개방된 환경에서 다이나믹한 언어를 지원할 뿐 아니라 확대된 XML 지원 기능과 보다 확대된 XML지원 등을 포함할 예정이다. 자바SE 7의 새로운 주요 특장점들은 10월 Java SE 6 출시이후에 보다 자세한 내용이 공개될 예정이다.

<그림 4>Mustang Features


  야생마 자바SE6 길들이기

이번에는 자바SE 6의 새로운 기능 중의 하나인 트레이(Tray) API를 이용한 예제를 살펴보자. 여기에서 트레이란 윈도우 GUI에서 태스크바(Taskbar) 오른쪽에 자리 잡는 작은 기능성 아이콘들이 들어가는 자리를 말한다. 보통 늘 떠 있어야 하는 애플리케이션들이 화면에 작게나마 현재 상태를 나타내고자 할 때 쓰인다. 예를 들어 메신저 창을 닫아도 메신저의 상태와 새 메시지 도착 등을 알리는 창구가 된다.

트레이에 아이콘을 두는 것은 윈도우 애플리케이션에 있어 사용자의 주목을 쉽게 끌 수 있고, 태스크바를 감추지 않는 이상 늘 떠 있어 자주 쓰는 기능의 호출에 대한 접근성을 향상시키는 장점이 있다. 따라서 자바 애플리케이션도 이런 트레이 아이콘을 가질 수 있다면 더욱 풍부한 사용자 경험을 제공할 수 있음은 두말할 나위도 없다. 원래 트레이 API는 JDIC(JDesktopI ntegration Components)라는 프로젝트의 일환으로, J2SE 1.4.2 이상이면 쓸 수 있는 부가 라이브러리로 시작했지만, 자바SE 6에는 기본 탑재되었다. 따라서 다음과 같이 패키지 이름만 다를 뿐 API 자체는 동일하므로 이제 소개할 예제를 간단히 포팅 할 수 있다.

JDIC Mustang
org.jdesktop.jdic.tray.SystemTray => java.awt.SystemTray
org.jdesktop.jdic.tray.TrayIcon => java.awt.TrayIcon

<리스트 1>은 본래 제우스 5를 부트.다운할 때 사용하려고 만든 것을 제우스 6 용으로 재구성한 것이다. 이 달의 디스켓 파일을 다운로드 받아서 압축을 푼 뒤에 핵심이 되는 코드를 살펴보자.

 <리스트 1> 제우스 부트.다운용 예제

우선 이렇게 짧은 코드로 트레이를 사용하는 애플리케이션을 만들 수 있다는 점이 놀랍다. 자바가 플랫폼에 독립적이기 때문이 트레이가 지원되는 OS(현재는 윈도우와 유닉스 일부)인지를 확인한 다음, 보통의 스윙(Swing) 애플리케이션처럼 아이콘용 이미지를 읽어 들이고 동작에 필요한 액션 리스너를 만다. 마지막으로 시스템 트레이에 트레이 아이콘을 추가하면 끝난다(아이콘 이미지는 예제 프로젝트 파일에 포함되어 있다). 이 애플리케이션을 실행시키는데 필요한 스크립트인 jeus-tray.bat은 다음과 같다.

%MUSTANG_HOME%\bin\java -cp bin ias.tray.JeusTray%JEUS_HOME% %*

이 배치 파일을 실행하려면 MUSTANG_HOME 환경 변수를 자바SE 6 설치 디렉토리로 잡아 줘야 한다. 예를 들어 다음과 같이 하면 된다.

set MUSTANG_HOME=c:\java\sdk\jdk6
JEUS_HOME은 제우스를 설치하면 자동으로 잡히므로 추가적으로 설정할 필요는 없다. jeus-tray.bat를 실행할 때에는 세 개의 인수를 넘겨주어야 한다.

jeus-tray xias jeus jeus

첫 번째 인수는 제우스가 설치된 노드 이름(보통은 컴퓨터 이름이다), 나머지 인수는 제우스 관리를 위한 어드민의 사용자 이름과 암호이다. 이렇게 제우스 제어를 위한 정보를 알려줘야 부트.다운이 가능하다. 실행시키면 다음과 같은 아이콘이 트레이에 표시된다.

<화면 2> 제우스 트레이가 뜬 상태
화면 2>에서 가장 왼쪽에 있는 것이 제우스 트레이 아이콘이다. 그 위로 마우스를 올리면 풍선 도움말이 표시된다. 이 아이콘
에서 마우스 오른쪽 버턴을 누르면 아래와 같은 메뉴가 나온다.

<화면 3> 제우스 트레이 명령 선택
oot는 부트, down은 다운, exit는 트레이 아이콘을 끝내는 것이다. Boot를 선택하면, <화면 4>와 같이 트레이 아이콘이 바뀐다.

<화면 4> 제우스가 부팅된 상태를 가리키는트레이 아이콘

앞서는 빨간색으로 X가 되던 부분이 부트가 되고 나면 녹색 진행 표시로 바뀐다. 물론 down을 선택하면 다시 X가 표시된다.

down 기능을 쓰기 위해서는 예제 프로젝트 파일에 동봉된 jeusdown. bat를 JEUS_HOME/bin에 넣어두어야 한다.

아주 간단한 예제지만, 그 활용처는 무궁무진하고 리눅스와 솔라리스에서도 쓸 수 있어 윈도뿐만 아니라 다양한 GUI 환경에서 트레이의 편리함을 누릴 수 있다는 점이 매력적이다. 앞으로 자바 메신저나 RSS 리더에서 멋지게 사용되기를 기대해본다.

  자바 브랜드 강화 및 미래

2005년으로 탄생 10주년을 맞은 자바는 이를 기념하기 위해 자바 브랜드를 새롭게 단장했다. 더욱 쉽고 심플한 디자인의 자바브랜드는‘한번 쓰면 어디에든 적용된다’는 주제 하에 전 세계적으로 PC와 휴대폰을 포함해 약 80%이상의 무선 애플리케이션 상에 적용되고 있다는 점이 많은 제조사와 컨슈머 고객사들이 자바 브랜드를 선호하는 이유다.

8세부터 80세까지, 200여개의 언어의 모든 사람들이 사용하는 자바 브랜드(로고)는 이러한 자바의 역사를 대변하고 있다. 보다 단순화된 자바 로고와 이름은 약 180여개의 자바 라이센서들의 샘플 집단에서 인터뷰를 거쳐 완성되었다.

프로젝트 탱고 (Tango)  
.net 플랫폼과 자바 플랫폼의 웹서비스 상호운영성 기술, WSIT:
프로젝트 탱고 (Tango)

지난 5월 16일 자바원 컨퍼런스에선 웹서비스를 위한 자바 플랫폼과 MS의 닷넷 플랫폼의 상호 운영성 기술(이하 WSIT)이 발표되었다. WSIT는 썬의 소프트웨어의 오픈소스 정책에 맞춰서 차세대 웹서비스 및 SOA구현을 위한 개발 환경 향상을 위한 기술로 MS와의 협력을 통한 프로젝트 탱고의 주요 컴포넌트이다. MS와 공동으로 발표한 WSIT 컴포넌트는 보안, 메시징, 서비스 질의 향상과 메타데이터 지원을 포함하는데, 프로젝트 글래스피시의 일환으로 Open JavaEE 커뮤니티에서 공개될 예정이다. 웹서비스상의 개발환경 향상을 위한 개발자들의 니즈를 반영하기 위해서 진행된 WSIT는 자바 기반 애플리케이션이 혁신적인 방식으로 닷넷 기술과 상호 운영되고 통합될 수 있게 한다.

WSIT는 MS 윈도우 커뮤니케이션 재단(WCF; Windows Communication Foundation)에서 광범위한 테스트를 거쳐서 자바 기술 기반의 애플리케이션과 연동할 수 있도록 신뢰성 높은 방법을 제시한다. WSIT는 썬이 기존에 추진해오던 MS 제품과의 상호 연동성을 향상시키기 위한 협력방안의 일환으로 썬의 자바 시스템 애플리케이션 서버의 다음 버전에도 포함될 예정이다.

WSIT 사용을 위한 JavaEE SDK 5.0 또는 썬 개발자 툴을 사용하는 개발자는 http://developers.sun.com/services에서 관련 정보 및 지원을 받을 수 있다.

썬과 MS는 이 외에도 단일 비즈니스 프로세스 디자인이 자바플랫폼과 닷넷 플랫폼 상 에서 연동될 수 있도록 WCF기반 서비스와자바EE 5 간의 상호연동을 위한 기술 협력을 추진하고 있다.


이렇듯 자바는 브랜드에서부터 기술의 발전에 이르기까지 개발자들의 참여와 기여를 통해 하나의 생태계로서 발전하고 있다.

<그림5> 자바 플랫폼의 성장과 미래

마지막으로 현재 개발환경이 스탠다드 에디션이든 엔터프라이즈 에디션이든 더욱더 단순하면서도 풍부한 자바 기술의 향상을 위해서는 자바 개발자라는 공동체내에서 개발자들의 커뮤니티 참여가 무엇보다도 중요하다(<그림 5> 내용 참조). @

* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다.
2009. 3. 17. 13:14

JDK 5.0 에서의 새로운 변화





 

JDK 5.0 에서의 새로운 변화

JDK 5.0에서는 프로그래밍의 유연함을 좀더 향상시키기 위해서 여러 가지 기능들이 개선되고 추가되었다. 이번 절에서는 JDK 5.0 에 오면서 변화된 여러 기능을 알아보자. 그렇지만 자바를 처음접하는 이들에게는 전혀 이해가 안 되는 얘기들일 것이다. 따라서 모든 내용을 이해할 생각을 하지 말고 그냥 "이런 것이 있구나!"하고 참고적으로 알아두길 바란다. 그렇다고 그냥 넘어가지 말자. 이전 버전과 비교해서 달라진 부분을 알고 있다면 향후 프로그래밍을 할 때 크게 도움을 받을 수 있기 때문이다.

less..

1. 제네릭타입(Generic Type)

예전부터 자바 커뮤니티의 기대를 받아왔던 제네릭 타입(Generic Type)이 드디어 JDK 5.0 버전의 일부로 포함되었다. 제네릭 타입이 적용된 것을 Collection API에서 가장 먼저 찾아볼 수 있다. Collection API 는 Linked List, ArrayList, 그리고 HashMap과 같이 하나 이상의 클래스가 공통으로 사용할 수 있는 기능들을 제공한다.

JDK 5.0 버전 이전의 Collection API 에서는 COllection 객체를 Onject 클래스에 저장하기 때문에 컴파일 타임에 발생하는 타입 오류(Type Mismatch)을 확인할 수 없다. 이러한 문제는 런타임할 때 ClassCastException이 발생하는 형태로 확인해야 하므로 Collection에 대입된 자료의 확인 여부를 어쩔 수 없이 런타임할 때까지 유도해야만 확인할 수 있었다.

하지만 JDK 5.0에서는 이러한 번거로움을 탈피하기 위해서 일반화된 API를 사용하려면 컴파일 할 때 단지 <> 사이에 사용할 자료 타입을 선언해주기만 하면 캐스팅은 더 이상 할 필요가 없다. 나아가서 사용된 Collection에 삽입되는 객체의 자료타입이 잘못 대입될 경우, 이젠 런타임 때가 아닌 컴파일 때에 확인할 수 있다. 예를 들어, 옷을 직접 입지 않고 옷의 사이즈를 확인하는 것만으로 내게 맞는 옷인지 확인할 수 있게 된 것이다.


2. 메타데이터

JDK 5.0에 포함된 ‘메타데이터(Metadata)' 기능을 통해 자바 클래스, 인터페이스, 메서드, 그리고 필드(변수나 상수) 등에 추가적인 정보들을 부과할 수 있게 되었다. 이 추가적인 정보는 javac 컴파일러 외에도 여러 툴로부터 읽어 들일 수 있으며 설정하기에 따라 클래스 파일에 저장할 수도 있고 Java Reflection API를 통해 런타임할 때에 접근할 수도 있다.

아무래도 메타데이터가 JDK 5.0에 추가된 주된 이유는 여러 개발 툴과 런타임 툴간의 공통된 인프라를 구축함으로써 프로그래밍과 개발에 소요되는 수고를 덜고자 하기 위함이라 볼 수 있다.


3. 개선된 루프

Collection API의 iterator는 상당히 자주 사용되는 객체다. 이는 Collection 내에서 이동하는 기능을 제공하는데, 개발을 하다보면 이런 Collection 들과 배열들을 반복적으로 사용할 때가 많다. 하지만 iterator와 인덱스 변수들로 인해 복잡함과 다소 불편함을 느낄 때가 많다. ‘개선된 루프(Enhanced for Loop)'는 그런 불편함에서 나올 수 있는 오류 발생 가능성을 줄여준다. 컴파일러는 필요한 루프 코드를 생성하게 되고, Generic Type을 함께 사용하게 되면 더 이상 일일이 자동 형 변환(casting)을 해주지 않아도 되므로 프로그래밍을 하는 데 더욱 더 편해진다.

예를 들어, 기차를 탈 때와 비슷하다. 기차를 이루는 각각의 호차들마다 번호표가 있게 마련인데, 호차마다 이 번호표가 없다고 가정해보자. 그렇다면 모든 승객들이 맨 앞의 기차부터 일일이 숫자로 세어 자신이 배정받은 호차에 타게 되는 불편함이 있게 된다. 그리고 숫자로 호차들을 세다가 중간에서 잘못 세게 되면 다른 사람 자리로 모든 호차들을 미리 나열화하고 그 호차들에게 번호표를 미리 붙여놓음으로써 불편함을 줄여놓은 것이다. JDK 5.0에서 얘기하는 ‘개선된 루프’가 바로 이와 비슷한 반복적 비교문을 강화한 것이다.

[이전버전]
 for(int i=0 ; i<10 ; i++)
  System.out.println(ar[i]);

[JDK 5.0]
 for(string n : ar)
  System.out.println(n);

위 예문과 같이 [이전버전]에서는 int형 변수 i와 같은 index값을 사용하여 배열의 요소들을 참조하는 데 사용한다. 하지만 배열의 길이가 만약 9개라면 6장에서 배우게 되는 예외 중 하나인 ArrayIndexOutBoundsException이 발생하게 된다. 하지만 [JDK 5.0]에서 제공되는 개선된 루프를 사용하게 되면 내부적으로 나열화 작업이 자동적으로 이루어지므로 [이전버전]의 예문과 같이 예외에 대한 우려는 하지 않아도 된다.


4. 오토박싱 / 언박싱

int 또는 bollean 과 같은 기본 자료형(primitive Type)과 이에 상응하는, 즉 Integer 또는 Boolean과 같은 Wrapper 클래스 타입간의 자료 변환 작업이 EO로는 불필요할 정도의 많은 코딩을 요구하는 경우 있다. 특히 Collection API 중 메서드의 인자로서 사용하기 위한 경우라면 더더욱 골치 아픈 일이 될 수 있었는데, JDK 5.0 에서는 int와 Integer같의 변환 작업을 컴파일러가 맡아서 처리하므로 불필요함을 크게 줄이게 되었다.

간단하게 예를 들자면 어느 날 집으로 새로운 컴퓨터가 배달되었다. 기분이야 두말하면 잔소리지만 포장을 뜯는 것이 여간 신격 쓰이는 것이 아니다. 물론, “기분이 날아갈 듯한데 그게 무슨 대수냐?”라는 생각이 들지도 모른다. 하지만 고객이 원한다면 이런 “포장까지 깔끔하게 서비스 해주는 택배회사가 있다면...”이라는 생각을 한 번쯤은 해봤다(아마 대박이 날지도 모를 일이다.) JDK 5.0 에서는 이런 귀찮은 변환 작업을 자동적으로 제공하는데, 이를 ‘오토박싱/언박싱(Autoboxing/Unboxing)'이라 하는 것이다.

이전 버전에서는 Ingeter 클래스의 내부값을 int로 가져오려면 그러한 일을 해주는 특정 메서드를 호출해야만 하는데, JDK 5.0으러 오면서 그런 수고를 덜었다.

[이전버전]
 Integer iTest = new Integer(500);
 int i = iTest.intValue();

[JDK 5.0]
 Integer iTest = new Integer(500);
 int i = iTest;

간단히 정리하면, 위 예문의 핵심은 마치 객체가 기본 자료형에 대입되는 것처럼 보이지만 이때 변환작업을 컴파일러가 알아서 해준다는 것이다.


5. static import

static import 기능은 ‘import static'의 형태로 사용되는데, 이를 통해 클래스로부터 상속받지 않고 또는 해당 클래스를 일일이 명시하지 않고도 static 상수들에 접근할 수 있다. 다시 말해 기존과 같이 BorderLayout.CENTER라고 사용할 필요 없이 JDK 5.0에서 ’import static'을 적용한 이제부터는 단순히 CENTER라고만 해도 사용할 수 있다는 것이다.

[이전버전]
 JPanel p1 = new JPanel(new FlowLayout(FlowLayout.RIGHT));

[JDK 5.0]
 import static java.awt.FlowLayout.*;
 ...
 JPanel p1 = new JPanel(new FlowLayout(RIGHT));

위의 [이전버전]과 [JDK 5.0]의 예문이 너무 간단하여 [JDK 5.0]에서 크게 유익한 점을 못 느낄 수도 있다. 하지만 실제 예제를 다루게 되면 그렇지가 않다. 하지만 실제 예제를 다루게 되면 그렇지가 않다. 또는 System.out.println("..."); 이라는 문장이 많이 사용될 경우 out까지를 static import로 미리 적용해두면 코드의 내용이 상당히 편리해질 것이다.


6. Formatter과 Scanner

이제 JDK 5.0 버전에서는 Formatter 클래스가 제공되는데, 이를 C style format imput / output 이라 하여 C 언어에서 사용되었던 printf 형태의 기능이 추가되어 정형화된 출력을 할 수 있게 되었다. 더 나아가 이로써 같은 텍스트 형태를 유지하면서 큰 수정 작업 없이 C 언어의 응용프로그램으로부터 소스를 물려받아 자바로의 변환(migration)도 쉽게 할 수 있다. 다음은 예문이다.

 System.out.printf("%s %5d%n", "총점:“, 500);

이 예문처럼 %로 시작하는 포맷 형식을 설정하고 그 형식에 맞도록 정형화된 출력을 목적으로 하는 것이 Formatter다.

Scanner 클래스는 기존에 사용되었던 Stream들에 없었던 파싱(parsing) 기능을 부여하여 효율적으로 자료를 유입하기 위한 클래스다. 기존의 Stream들은 Character 기반의 Stream인 BufferedReader를 쓴다하더라도 한 번에 한 줄씩 읽어 들이는 수 밖에 없었다. 하지만 Scanner 클래스를 사용하면 한 번에 전체를 읽어 들일 수도 있으며, 때에 따라서는 한 줄 단위로 읽을 수도 있다. 이러한 것들은 Pattern 클래스 또는 문자열로 되어 dLT는 구분자의 패턴을 useDelimiter()라는 메서드로 간단하게 변경하여 유연성을 높이고 있다.


7. Varargs(Variable Arguments)

JDK 5.0 이전의 버전에서는 특정 메서드를 정의할 때 인자(argument)의 타빙과 수를 정해두고 호출할 때 전달되는 인자의 수가 일치하지 않을 경우에는 메서드를 호출할 수 없었다. 이런 문제로 인해 많은 개발자들이 오버로딩법 또는 메서드에 배열객체로 인자를 지정하고 배열 객체를 사용하여 이 문제를 해결했지만 매번 호출할 때의 배열화 작업이 너무 불편했던 것이 사실이다.

이제 varargs 기능을 사용하여 여러 개의 인자를 매개변수(parameter)의 형태로 전달할 수 있다. 인자로 받는 메서드에서 ‘...’이라고 명시를 해주면 이를 통해 메서드를 수행하는데 필요한 인자의 수를 유연하게 구현할 수 있다.

[이전버전]
 class VaraTest {
  public static void test(Object args){
   System.out.println(args);
  }
  public static void main(String args[]){
   test("Java5");
   test("Java5", " Varargs", " Test!"); // 인자 전달에 대한 오류!
  }
 }

[JDK 5.0]
 class HelloWorld {
  public static void test(Object ... args){
   for(int i = 0 ; i < args.length ; i++) {
    System.out.print(args[i]+",");
   }
   System.out.println();
  }
  public static void main(String args[]){
   test("Java5");
   test("Java5", " Varargs", " Test!");
   test(new Integer(1000), new Float(3.14));
  }
 }

[출력결과]
 Java5,
 Java5, Varargs, Test!,
 1000,3.14,

이전 버전에서는 인자의 수만 달라도 컴파일 오류가 발생했다. 하지만 JDK 5.0 에서는위처럼 Varargs 법을 사용하게 되면 보는 것과 같이 인자가 한 개이든, 세 개이든 인자의 수를 유연하게 받아주는 특징이 있어 작업의 효율을 높일 수 있다.


8. Simpler RMI interface generation 기법

원격 인터페이스에 대한 stub를 생성하는 데 있어서 더 이상 rmi 컴파일러인 rmic를 사용할 필요가 없으므로 RMI 기법을 더욱 더 간편하게 사용할 수 있게 되었다. Dynamic Proxy가 소개됨에 따라 stub가 제공하는 정보는 이제 런타임할 때 찾을 수 있게 되었기 때문이다.

이외에도 JVM 관련 기능들과 프로토콜을 감시하는 모니터링 기능 그리고 JDBC RowSets 등 여러 기능이 추가되고 개선되었다. 하지만 이 책의 수준에 맞춰 이 정도의 소개로 마무리하며 JDBC Rowstes 과 같이 꼭 알아야 하는 것들은 해당되는 장에서 소개한다.

2009. 2. 5. 09:47

주민등록 검사의 원리




출생 년도 (YY = 00 - 99 사이의 값) 
      +--+ 
      |   |    출생월 (MM = 01 - 12 사이의 값) 
      |   |    +--+ 
      |   |    |   |   출생일 (DD = 01 - 31 사이의 값) 
      |   |    |   |   +--+ 
      |   |    |   |   |   |       남,여 구분 (1, 3 = 남자, 2, 4 = 여자 )  
      |   |    |   |   |   |           | 
      Y   Y   M   M   D   D   -   X                                    C 
  +------------------------------------------------------------------+ 
  |   0   1   2    3    4   5         6   7   8   9   10  11         12    | index 
  |   6   6   0    5    1   0   -    1   3   2    1   6    1            1    | data 
  |   2   3   4    5    6   7         8   9   2    3   4    5          KEY  | mask 
  +------------------------------------------------------------------+ 
(11- {12+ 18+ 0+  25+ 6+  0+      8+  27+ 4+  3+  24+ 5}%11)% 10 == 1 

* 주민등록 번호 검사 비트 검사 방법 

     1) 주민등록 번호에 앞에서부터 2,3,4,5,6,7,8,9,2,3,4,5를 차례로 곱한다.  {0..11}%8+2 
     2) 각각의 값을 모두 더한다.                                                                  sum 
     3) 이 합을 11로 나눈다. 그러면 나머지는 0,1,2,3,4,5,6,7,8,9,10 중의 하나이다.    
     4) 11에서 나머지를 뺀다.                                               
     5) 이때 나머지 값이 10을 넘으면 10를 빼고,                                           (11-sum%11)%10 

          그렇지 않으면 나머지 값이 주민등록 검사 비트이다. 

  

예) 660510-1321611 

     6 * 2 + 6 * 3 + 0 * 4 + 5 * 5 + 1 * 6 + 0 * 7 + 1 * 8 + 3 * 9 + 2 * 2 + 1 * 3 + 6 * 4 + 1 * 5 = 132  : 합계 
     132 % 11        = 0   : 나머지값 
     (11 - 0) % 10  = 1  : 결과값 
     따라서 키 플래그는 1이다.      그리고 마지막 자리의 체크플래그는 1이다. 
      
     키플래그 == 체크플래그  
2009. 2. 4. 15:27

jaxp를 이용한 xmlutil






jaxp를 이용하여 xml에 element 추가, attribute 추가, 제거등에 관련한 util를 제공한다.
그리고 작성한 document는 makeFile 함수를 사용하여 파일로 생성이 가능하다.
XmlUtil.java xml을 관리하는 util 파일이고
DDFileUtil.java는 XmlUtil.java를 이용하여 실제 xml을 컨트롤한 예제이다.

package com.gamizi.util;

import java.io.File;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Xml 파싱관련 클래스
 * @author 큐앤솔브 : 강성현
 *
 */
public class XmlUtil {
public XmlUtil(){
}
/**
* xml 파읽을 읽어들여 document 를 생성한다.
* @param filePath 파일 경로
* @param fileName 파일명
* @return
* @throws Exception
*/
public Document parse(String filePath, String fileName) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
factory.setNamespaceAware( true );
factory.setValidating( true );
Document doc = builder.parse(new File(filePath+fileName));
return doc;
}
/**
* 소스의 Element로 부터 key에 해당하는 자식node를 Element로 형전환하여 반환한다.
* @param srcElement 조회를 원하는 element
* @param key 자식 로드 element
* @return
*/
public Element getElement(Element srcElement, String key){
Element retEle = null;
NodeList ddNodeList = srcElement.getChildNodes();
for(int i = 0 ; i < ddNodeList.getLength() ; i++){
Node node =srcElement.getChildNodes().item(i);
String nodeName = node.getNodeName();
if(key.equals(nodeName)){
retEle = (Element)node;
}
}
return retEle;
}

/**
* Element를 추가한다.
* @param srcElement 추가를 원하는 소스의 element
* @param document //document 
* @param elementName //추가를 원하는 element 이름
* @param removeFlag //기존에 elementName 유무에 따라 Y이면 삭제, N이면 element 계속 추가.
*/
public void addElement(Element srcElement, Document document, String elementName, 
boolean removeFlag){

if(removeFlag){
//삭제할 element를 찾는다.
//조회할 element에 element가 없다면 null을 리턴
Element removeElement = getElement(srcElement,elementName);
if(removeElement!=null){
removeElement(srcElement,removeElement);
}
}
Element addElement = document.createElement(elementName);
srcElement.appendChild(addElement);
//
}

/**
* parentsElements 에 elementName으로 element를 추가 후
* key와 value를 이용하여 attribute를 추가한다.
* removeFlag를 이용하여 Y이면 기존에 elementName 있으면 해당 element를 삭제한다.
* @param parentsElements
* @param document
* @param elementName
* @param key
* @param value
* @param removeFlag
*/
public void addElementAttribute(Element parentsElements, Document document, String elementName, 
String key, String value, boolean removeFlag){
addElement(parentsElements,document,elementName,removeFlag);
addAttribute(getElement(parentsElements,elementName),key, value);
}
/**
* 소스의 요소중 key값에 해당하는 요소가 있는지 조회
* @param srcElement
* @param key
* @return
*/
public boolean isChildElement(Element srcElement, String key){
boolean retFlag = false;
NodeList ddNodeList = srcElement.getChildNodes();
for(int i = 0 ; i < ddNodeList.getLength() ; i++){
Node node =srcElement.getChildNodes().item(i);
String nodeName = node.getNodeName();
if(key.equals(nodeName)){
retFlag = true;
}
}
return retFlag;
}
/**
* @param srcElement
* @param removeElement
*/
public void removeElement(Element srcElement, Element removeElement){
if(isChildElement(srcElement,removeElement.getTagName()))
srcElement.removeChild(removeElement);
}
public void addAttribute(Element srcElement, String key, String value){
srcElement.setAttribute(key, value);
}
public void makeFile(Document document, String fileName, String encoding, String docTypeSystem) throws Exception{
//..변환기 생성
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
//..출력 포맷 설정
transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, docTypeSystem);
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
//..DOMSource 객체 생성
DOMSource source = new DOMSource(document);
//..StreamResult 객체 생성
StreamResult result = new StreamResult(new File(fileName));
//..파일로 저장하기
transformer.transform(source, result);
}
}