오늘은 어디로 갈까...

템플릿을 이용한 메일 본문

낙서

템플릿을 이용한 메일

剛宇 2009. 4. 13. 19:48
 지난 시간(메일 템플릿에 대한 단상(斷想))에 이어서 템플릿을 이용한 메일 발송에 대해 좀 더 알아보도록 하자. 완전체(?)를 만들면 4월 한달 내내 우려먹을 수 있을거 같기는 하나, 저질 체력인 관계로 대충 끝내보도록 하자.
 프로젝트 명은 메일관련이므로.... POSTMAN이라 하겠다.

1. 테이블 만들기
  - 데이터베이스에서 전송할 자료를 가지고오는 구조이므로, 일단 테이블을 만들도록하자.

MAIL
MAIL_NO NUMBER Not Null 메일 번호
TEMPLATE_ID CHAR(3) Not Null 템플릿 아이디
STATUS_CODE CHAR(2) Not Null 상태 코드
TO_ADDRESS VARCHAR2(200) Not Null 받은이 주소
TO_NAME VARCHAR2(100)  받는이 이름
FROM_ADDRESS VARCHAR2(200) Not Null 보내는이 주소
FROM_NAME VARCHAR2(100)  보내는이 이름
SUBJECT_DATA VARCHAR2(200)  제목 데이터
COTENT_DATA VARCHAR2(4000)  본문 데이터
SENT_BY VARCHAR2(200)  발송자
SENT_DATE DATE  발송일
CREATED_BY VARCHAR2(200)  생성자
CREATION_DATE DATE  생성일
UPDATED_BY VARCHAR2(200)  수정자
UPDATED_DATE DATE  수정일

MAIL_TEMPLATE
TEMPLATE_ID CHAR(3) Not Null 템플릿 아이디
TEMPLATE_DIR VARCHAR2(500)  템플릿 경로
TEMPLATE_FILENAME VARCHAR2(200)  템플릿 파일명
MAIL_SUBJECT VARCHAR2(1000)  메일 제목
DATA_MODEL_TYPE CHAR(1) Not Null 데이터 모델 종류(J:JSON,X:XML,P:PARAMETER)
USE_FLAG CHAR(1) Not Null 사용 유무(Y/N)
CREATED_BY VARCHAR2(200)  생성자
CREATION_DATE DATE  생성일
UPDATED_BY VARCHAR2(200)  수정자
UPDATED_DATE DATE  수정일


구조는 간단하다. 메일 템플릿 정보를 가지고 있는 MAIL_TEMPLATE 테이블과, 전송한 메일이 들어있는 MAIL 테이블 2개이다. 데이터베이스는 오라클을 사용했지만 걱정마라. 여기서는 실제 디비를 사용하지 않겠다. 그냥 구조가 이렇게 생겨먹었다는 것을 설명하기 위해서 만들었다. (사실은 코드 생성기로 Value Object를 자동 생성하기 위해 만들었지만.. 흠흠 ^^; 다음엔 코드생성관련 낙서나 해볼까나...)

2. Value Object 만들기
 - 의미가 조금씩 다르지만, VO(Value Object), EO(Entity Object), DO(Domain Object) 그리고 심지어 DTO(Data Transfer Object)라 불리우는 클래스를 만들어보자.
package kr.kangwoo.postman.domain;

import java.io.Serializable;
import java.util.Date;

import kr.kangwoo.util.ObjectUtils;


public class Mail implements Serializable {

    /**
	 * 
	 */
	private static final long serialVersionUID = -6530998244150521663L;
	private Long mailNo;
    private String templateId;
    private String statusCode;
    private String toAddress;
    private String toName;
    private String fromAddress;
    private String fromName;
    private String subjectData;
    private String cotentData;
    private String sentBy;
    private Date sentDate;
    private String createdBy;
    private Date creationDate;
    private String updatedBy;
    private Date updatedDate;


    /**
     * <p>메일 번호를 가져온다.</p>
     * 
     * @return 메일 번호
     */
    public Long getMailNo() {
        return this.mailNo;
    }

    /**
     * <p>메일 번호를 설정한다.</p>
     * 
     * @param mailNo 메일 번호
     */
    public void setMailNo(Long mailNo) {
        this.mailNo = mailNo;
    }

    /**
     * <p>템플릿 아이디를 가져온다.</p>
     * 
     * @return 템플릿 아이디
     */
    public String getTemplateId() {
        return this.templateId;
    }

    /**
     * <p>템플릿 아이디를 설정한다.</p>
     * 
     * @param templateId 템플릿 아이디
     */
    public void setTemplateId(String templateId) {
        this.templateId = templateId;
    }

    /**
     * <p>상태 코드를 가져온다.</p>
     * 
     * @return 상태 코드
     */
    public String getStatusCode() {
        return this.statusCode;
    }

    /**
     * <p>상태 코드를 설정한다.</p>
     * 
     * @param statusCode 상태 코드
     */
    public void setStatusCode(String statusCode) {
        this.statusCode = statusCode;
    }

    /**
     * <p>받은이 주소를 가져온다.</p>
     * 
     * @return 받은이 주소
     */
    public String getToAddress() {
        return this.toAddress;
    }

    /**
     * <p>받은이 주소를 설정한다.</p>
     * 
     * @param toAddress 받은이 주소
     */
    public void setToAddress(String toAddress) {
        this.toAddress = toAddress;
    }

    /**
     * <p>받는이 이름을 가져온다.</p>
     * 
     * @return 받는이 이름
     */
    public String getToName() {
        return this.toName;
    }

    /**
     * <p>받는이 이름을 설정한다.</p>
     * 
     * @param toName 받는이 이름
     */
    public void setToName(String toName) {
        this.toName = toName;
    }

    /**
     * <p>보내는이 주소를 가져온다.</p>
     * 
     * @return 보내는이 주소
     */
    public String getFromAddress() {
        return this.fromAddress;
    }

    /**
     * <p>보내는이 주소를 설정한다.</p>
     * 
     * @param fromAddress 보내는이 주소
     */
    public void setFromAddress(String fromAddress) {
        this.fromAddress = fromAddress;
    }

    /**
     * <p>보내는이 이름을 가져온다.</p>
     * 
     * @return 보내는이 이름
     */
    public String getFromName() {
        return this.fromName;
    }

    /**
     * <p>보내는이 이름을 설정한다.</p>
     * 
     * @param fromName 보내는이 이름
     */
    public void setFromName(String fromName) {
        this.fromName = fromName;
    }

    /**
     * <p>제목 데이터를 가져온다.</p>
     * 
     * @return 제목 데이터
     */
    public String getSubjectData() {
        return this.subjectData;
    }

    /**
     * <p>제목 데이터를 설정한다.</p>
     * 
     * @param subjectData 제목 데이터
     */
    public void setSubjectData(String subjectData) {
        this.subjectData = subjectData;
    }

    /**
     * <p>본문 데이터를 가져온다.</p>
     * 
     * @return 본문 데이터
     */
    public String getCotentData() {
        return this.cotentData;
    }

    /**
     * <p>본문 데이터를 설정한다.</p>
     * 
     * @param cotentData 본문 데이터
     */
    public void setCotentData(String cotentData) {
        this.cotentData = cotentData;
    }

    /**
     * <p>발송자를 가져온다.</p>
     * 
     * @return 발송자
     */
    public String getSentBy() {
        return this.sentBy;
    }

    /**
     * <p>발송자를 설정한다.</p>
     * 
     * @param sentBy 발송자
     */
    public void setSentBy(String sentBy) {
        this.sentBy = sentBy;
    }

    /**
     * <p>발송일을 가져온다.</p>
     * 
     * @return 발송일
     */
    public Date getSentDate() {
        return this.sentDate;
    }

    /**
     * <p>발송일을 설정한다.</p>
     * 
     * @param sentDate 발송일
     */
    public void setSentDate(Date sentDate) {
        this.sentDate = sentDate;
    }

    /**
     * <p>생성자를 가져온다.</p>
     * 
     * @return 생성자
     */
    public String getCreatedBy() {
        return this.createdBy;
    }

    /**
     * <p>생성자를 설정한다.</p>
     * 
     * @param createdBy 생성자
     */
    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    /**
     * <p>생성일을 가져온다.</p>
     * 
     * @return 생성일
     */
    public Date getCreationDate() {
        return this.creationDate;
    }

    /**
     * <p>생성일을 설정한다.</p>
     * 
     * @param creationDate 생성일
     */
    public void setCreationDate(Date creationDate) {
        this.creationDate = creationDate;
    }

    /**
     * <p>수정자를 가져온다.</p>
     * 
     * @return 수정자
     */
    public String getUpdatedBy() {
        return this.updatedBy;
    }

    /**
     * <p>수정자를 설정한다.</p>
     * 
     * @param updatedBy 수정자
     */
    public void setUpdatedBy(String updatedBy) {
        this.updatedBy = updatedBy;
    }

    /**
     * <p>수정일을 가져온다.</p>
     * 
     * @return 수정일
     */
    public Date getUpdatedDate() {
        return this.updatedDate;
    }

    /**
     * <p>수정일을 설정한다.</p>
     * 
     * @param updatedDate 수정일
     */
    public void setUpdatedDate(Date updatedDate) {
        this.updatedDate = updatedDate;
    }

//    @Override
//    public String toString() {
//        return ObjectUtils.reflectionToString(this);
//    }
}

package kr.kangwoo.postman.domain;

import java.io.Serializable;
import java.util.Date;


public class MailTemplate implements Serializable {

    /**
	 * 
	 */
	private static final long serialVersionUID = 6718305040557630417L;
	private String templateId;
    private String templateDir;
    private String templateFilename;
    private String mailSubject;
    private String dataModelType;
    private String useFlag;
    private String createdBy;
    private Date creationDate;
    private String updatedBy;
    private Date updatedDate;


    /**
     * <p>템플릿 아이디를 가져온다.</p>
     * 
     * @return 템플릿 아이디
     */
    public String getTemplateId() {
        return this.templateId;
    }

    /**
     * <p>템플릿 아이디를 설정한다.</p>
     * 
     * @param templateId 템플릿 아이디
     */
    public void setTemplateId(String templateId) {
        this.templateId = templateId;
    }

    /**
     * <p>템플릿 경로를 가져온다.</p>
     * 
     * @return 템플릿 경로
     */
    public String getTemplateDir() {
        return this.templateDir;
    }

    /**
     * <p>템플릿 경로를 설정한다.</p>
     * 
     * @param templateDir 템플릿 경로
     */
    public void setTemplateDir(String templateDir) {
        this.templateDir = templateDir;
    }

    /**
     * <p>템플릿 파일명을 가져온다.</p>
     * 
     * @return 템플릿 파일명
     */
    public String getTemplateFilename() {
        return this.templateFilename;
    }

    /**
     * <p>템플릿 파일명을 설정한다.</p>
     * 
     * @param templateFilename 템플릿 파일명
     */
    public void setTemplateFilename(String templateFilename) {
        this.templateFilename = templateFilename;
    }

    /**
     * <p>메일 제목을 가져온다.</p>
     * 
     * @return 메일 제목
     */
    public String getMailSubject() {
        return this.mailSubject;
    }

    /**
     * <p>메일 제목을 설정한다.</p>
     * 
     * @param mailSubject 메일 제목
     */
    public void setMailSubject(String mailSubject) {
        this.mailSubject = mailSubject;
    }

    /**
     * <p>데이터 모델 종류(J:JSON,X:XML,P:PARAMETER)을 가져온다.</p>
     * 
     * @return 데이터 모델 종류(J:JSON,X:XML,P:PARAMETER)
     */
    public String getDataModelType() {
        return this.dataModelType;
    }

    /**
     * <p>데이터 모델 종류(J:JSON,X:XML,P:PARAMETER)을 설정한다.</p>
     * 
     * @param dataModelType 데이터 모델 종류(J:JSON,X:XML,P:PARAMETER)
     */
    public void setDataModelType(String dataModelType) {
        this.dataModelType = dataModelType;
    }

    /**
     * <p>사용 유무(Y/N)을 가져온다.</p>
     * 
     * @return 사용 유무(Y/N)
     */
    public String getUseFlag() {
        return this.useFlag;
    }

    /**
     * <p>사용 유무(Y/N)을 설정한다.</p>
     * 
     * @param useFlag 사용 유무(Y/N)
     */
    public void setUseFlag(String useFlag) {
        this.useFlag = useFlag;
    }

    /**
     * <p>생성자를 가져온다.</p>
     * 
     * @return 생성자
     */
    public String getCreatedBy() {
        return this.createdBy;
    }

    /**
     * <p>생성자를 설정한다.</p>
     * 
     * @param createdBy 생성자
     */
    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    /**
     * <p>생성일을 가져온다.</p>
     * 
     * @return 생성일
     */
    public Date getCreationDate() {
        return this.creationDate;
    }

    /**
     * <p>생성일을 설정한다.</p>
     * 
     * @param creationDate 생성일
     */
    public void setCreationDate(Date creationDate) {
        this.creationDate = creationDate;
    }

    /**
     * <p>수정자를 가져온다.</p>
     * 
     * @return 수정자
     */
    public String getUpdatedBy() {
        return this.updatedBy;
    }

    /**
     * <p>수정자를 설정한다.</p>
     * 
     * @param updatedBy 수정자
     */
    public void setUpdatedBy(String updatedBy) {
        this.updatedBy = updatedBy;
    }

    /**
     * <p>수정일을 가져온다.</p>
     * 
     * @return 수정일
     */
    public Date getUpdatedDate() {
        return this.updatedDate;
    }

    /**
     * <p>수정일을 설정한다.</p>
     * 
     * @param updatedDate 수정일
     */
    public void setUpdatedDate(Date updatedDate) {
        this.updatedDate = updatedDate;
    }

//    @Override
//    public String toString() {
//        return ObjectUtils.reflectionToString(this);
//    }

}

 - 디비 테이블과 똑같이(?) 생겼으니 설명은 생략하겠다. (오라클은 컬럼에 Comment를 달수있어서, 코드를 자동 생성할때 넣어주면 그럴싸한~ 코드가 만들어진다... ^^;)



3. DAO(Data Access Object) 만들기
 - 실제 디비에서 데이터를 가져오게 만들어야하지만, 시간 관계상 직접 코딩한 데이터를 반환하도록하겠다.
package kr.kangwoo.postman.repository;

import java.util.List;

import kr.kangwoo.postman.domain.Mail;

public interface MailDao {

	public List<Mail> getMailList();
	
	public int updateMail(Mail mail);
}

package kr.kangwoo.postman.repository;

import java.util.List;

import kr.kangwoo.postman.domain.MailTemplate;

public interface MailTemplateDao {

	public List<MailTemplate> getMailTemplateList();
}

- 앞에서 설명한 것처럼 가짜(?) 구현체를 만들어보자.

package kr.kangwoo.postman.repository;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import kr.kangwoo.postman.domain.Mail;

public class SimpleMailDao implements MailDao {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	public List<Mail> getMailList() {
		List<Mail> list = new ArrayList<Mail>();
		Mail mail = new Mail();
		mail.setMailNo(1L);
		mail.setToAddress("badnom@kangwoo.kr");
		mail.setToName("랑우");
		mail.setFromAddress("rangwoo@kangwoo.kr");
		mail.setFromName("케로");
		mail.setTemplateId("001");
		mail.setCotentData("name=랑우&score=75&subject=인문학");
		list.add(mail);
		
		mail = new Mail();
		mail.setMailNo(2L);
		mail.setToAddress("badnom@kangwoo.kr");
		mail.setToName("바보");
		mail.setFromAddress("rangwoo@kangwoo.kr");
		mail.setFromName("케로");
		mail.setTemplateId("001");
		mail.setCotentData("name=바보&score=99&subject=수학");
		list.add(mail);
		
		mail = new Mail();
		mail.setMailNo(3L);
		mail.setToAddress("badnom@kangwoo.kr");
		mail.setToName("바보");
		mail.setFromAddress("rangwoo@kangwoo.kr");
		mail.setFromName("케로");
		mail.setTemplateId("002");
		mail.setCotentData("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<user><name></name><score subject=\"혼돈학\">100</score><score subject=\"인문학\">82</score><score subject=\"좌절학\">65</score></user>");
		list.add(mail);
		
		return list;
	}

	public int updateMail(Mail mail) {
		logger.debug("updateMail()");
		return 0;
	}

}

package kr.kangwoo.postman.repository;

import java.util.ArrayList;
import java.util.List;

import kr.kangwoo.postman.domain.MailTemplate;

public class SimpleMailTemplateDao implements MailTemplateDao {

	public List<MailTemplate> getMailTemplateList() {
		List<MailTemplate> list = new ArrayList<MailTemplate>();
		MailTemplate mailTemplate = new MailTemplate();
		mailTemplate.setTemplateId("001");
		mailTemplate.setTemplateDir("G:/workspace/java/kr.kangwoo.postman/template");
		mailTemplate.setTemplateFilename("001.ftl");
		mailTemplate.setDataModelType("P");
		mailTemplate.setMailSubject("첫번째 테스트용 메일입니다.");
		mailTemplate.setUseFlag("Y");
		list.add(mailTemplate);
		
		mailTemplate = new MailTemplate();
		mailTemplate.setTemplateId("002");
		mailTemplate.setTemplateDir("G:/workspace/java/kr.kangwoo.postman/template");
		mailTemplate.setTemplateFilename("002.ftl");
		mailTemplate.setDataModelType("X");
		mailTemplate.setMailSubject("두번째 테스트용 메일입니다.");
		mailTemplate.setUseFlag("Y");
	
		list.add(mailTemplate);
		return list;
	}

}

 - 경로(TemplateDir) 및 파일명(TemplateFilename)은 당연히(?) 본인의 것을 사용해야한다.
(제대로 해보실분을 직접 디비에 데이터를 넣고 멋진 구현체(?)를 만들어주시길 바랍니다. 본인은 iBatis를 즐겨쓰는데, 여기서 사용했다가는 글이 길어질까 두려워서 생략하겠습니다. )
 - 템플릿에 데이터를 넣어야 최종 결과물이 나온는데, 지난번 낙서에는 Map 인스턴스를 만들어서 사용했다. 여기서는 디비 MAIL 테이블의 CONTENT_DATA에서 문자열을 읽어와서 데이터화 하는 방식을 사용한다. 물론 자바 객체를 디비 컬럼에 넣을 수는 있지만, 보기 안좋은관계로 문자열을 사용하였다. 데이터를 문자열로 표현하는 방법중 가장 널리 쓰이는게, XML, JSON이고, 아주 간단한 경우는 키=값의 파라메터 형태로 사용한다. XML은 FreeMaker에서 파싱하는 부분이 구현되어있고, JSON은 자체 구현해야하는데, 생략하도록 하겠다. 키=값의 파라메터 형태는 간단하므로 구현해보자.

4. 템플릿 파일 만들기
 -  여기서는 001.ftl, 002.ftl 그리고 footer.html을 사용하겠다. 자신의 애용(?) 디렉토리에 파일을 만들어주자.
 - 템플릿 001은 키=값 파라메터의 형태의 데이터를 표현하기 위해 만들었다. 간단히 ${키} 란 표현식으로 데이터를 나타낼수 있다. 여기서는 조건문도 사용했는데, 주의해야할 점이 두가지 있다. 첫번째는, FreeMarker의 경우 조건문에 사용된 좌측, 우측 데이터 타입이 일치해야 정상적으로 처리된다. 여기서는 숫자를 비교했는데, score 값이 문자인 관계로 score?number 란 bulilt-in 함수를 사용해서 숫자형으로 변환하였다. 두번째는, <>의 괄호를 사용할 경우 ()로 묶어주던지, &lt; &gt; 로 사용을 해예한는 것이다.

- 001.ftl
<html>
<head>
  <title>안녕하세요 ${name}님</title>
</head>
<body>
  <h1>안녕하세요 ${name}님</h1>
  <p>${subject} 과목의 점수는 ${score}점입니다.</p>
  <p>등급은 <#if (score?number >= 90)>A<#elseif (score?number >= 80)>B<#elseif (score?number >= 70)>C<#else>F</#if>입니다</p>
<#include "footer.html" >
</body>
</html>

- 002 템플릿이 001 템플릿과 다는 점은 반복문을 사용하든는 것이다. 단순 키=값 파라메터일 경우 동일한 키 값을 배열 형식으로 표현할수 없기에 사용을 안했지만, XML는 그게 가능함므로 반복문을 사용해본것이다.(키=값 파라메터 일 경우도 구현을 요상하게 하면 가능하지만, 직관성이 떨어지므로 안되는것으로 했음)
- 002.ftl
<html>
<head>
  <title>안녕하세요 ${doc.user.name}님</title>
</head>
<body>
  <h1>안녕하세요 ${doc.user.name}님</h1>
  <#list doc.user.score as score>
  <li>${score.@subject} 과목의 점수는 ${score}점, 등급은 <#if (score?number >= 90)>A<#elseif (score?number >= 80)>B<#elseif (score?number >= 70)>C<#else>F</#if>입니다
  </#list>
<#include "footer.html" >
</body>
</html>

- footer.html
<hr />
<div>발송 전용 메일입니다.문의 사항은 보내지 마시길 바랍니다.</div>



5. 기본(?) 클래스 만들기
 - 순서가 잘못된감도 있지만, 기본(?) 클래스를 만들어보자. 상태 코드 상수를 선언한 MailStatusCode 인터페이스와 Exception 클래스를 만들겠다.
package kr.kangwoo.postman.core;

public interface MailStatusCode {

	public static final String CREATED = "00";
	public static final String ACCEPTED = "10";
	public static final String SEND_READY = "20";
	public static final String SEND_OK = "90";
	public static final String UNUSED = "E0";
	public static final String TEMPLATE_INFO_NOT_FOUND = "E1";
	public static final String TEMPLATE_FILE_NOT_FOUND = "E2";
	public static final String TEMPLATE_ERROR = "E3";
	public static final String UNKOWN_DATA_MODEL_TYPE = "E4";
	public static final String DATA_MODEL_ERROR = "E5";
	public static final String BAD_CONTENT = "E6";
	public static final String SEND_FAIL = "E7";
	
	public static final String UNKOWN_ERROR= "EF";
	
}

package kr.kangwoo.postman.core;

public class MessageException extends RuntimeException {
	

	/**
	 * 
	 */
	private static final long serialVersionUID = -1022516825842789659L;

	public MessageException() {
		super();
	}

	public MessageException(String message) {
		super(message);
	}

	public MessageException(String message, Throwable cause) {
		super(message, cause);
	}

	public MessageException(Throwable cause) {
		super(cause);
	}
}

package kr.kangwoo.postman.core;

public class MailException extends MessageException {
	

	/**
	 * 
	 */
	private static final long serialVersionUID = -8936319408119097697L;
	private String statusCode;
	

	public MailException(String statusCode) {
		super();
		this.statusCode = statusCode;
	}

	public MailException(String statusCode, String message) {
		super(message);
		this.statusCode = statusCode;
	}

	public MailException(String statusCode, String message, Throwable cause) {
		super(message, cause);
		this.statusCode = statusCode;
	}

	public MailException(String statusCode, Throwable cause) {
		super(cause);
		this.statusCode = statusCode;
	}

	public String getStatusCode() {
		return statusCode;
	}
}

 - MailException 클래스는 메일 상태를 저장하기 위해 statusCode 변수를 가지고 있다.

6. 서비스 클래스 만들기
- 메일 관리자(?), 메일 템플릿 관리자, 메일 발송 관리자 3개를 만들겠다.
1) 메일 관리자 : 실제적으로 구현할려면, 디비에서 발송이 안된 데이터를 가져오고, 가져온 데이터들을 발송 대기 상태로 변경, 발송, 상태 변경등을 처리해야하나, 시간 관계상 간단히 데이터가 가져오게 구현하였다.
package kr.kangwoo.postman.service;

import java.util.List;

import kr.kangwoo.postman.domain.Mail;

public interface MailManager {

	public List<Mail> getSendList(String daemonName);
	
	public int updateMail(Mail mail);
}

package kr.kangwoo.postman.service;

import java.util.List;

import kr.kangwoo.postman.domain.Mail;
import kr.kangwoo.postman.repository.MailDao;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimpleMailManager implements MailManager {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	private MailDao mailDao;

	public void setMailDao(MailDao mailDao) {
		this.mailDao = mailDao;
	}

	public List<Mail> getSendList(String daemonName) {
		logger.debug("getSendList({})", daemonName);
		return mailDao.getMailList();
	}

	public int updateMail(Mail mail) {
		logger.debug("updateMail()");
		return mailDao.updateMail(mail);
	}

}

2) 메일 템플릿 관리자 : 템플릿 정보를 읽어오고, 해당 메일의 본문 데이터와 템플릿을 결합하여, 발송할 내용을 만든다.
package kr.kangwoo.postman.service;

import kr.kangwoo.postman.core.MailException;
import kr.kangwoo.postman.domain.Mail;

public interface MailTemplateManager {
	
	void reload() throws MailException;
	
	String getSubject(Mail mail) throws MailException;
	String getContent(Mail mail) throws MailException;
}

package kr.kangwoo.postman.service;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import kr.kangwoo.postman.core.MailException;
import kr.kangwoo.postman.core.MailStatusCode;
import kr.kangwoo.postman.core.MessageException;
import kr.kangwoo.postman.domain.Mail;
import kr.kangwoo.postman.domain.MailTemplate;
import kr.kangwoo.postman.repository.MailTemplateDao;
import kr.kangwoo.util.MessageUtils;
import kr.kangwoo.util.StrTokenizer;
import kr.kangwoo.util.StringUtils;

import org.xml.sax.InputSource;

import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class FreemarkerMailTemplateManager implements MailTemplateManager {

	private MailTemplateDao mailTemplateDao;
	
	private Map<String, MailTemplate> mailTemplateMap;
	private Configuration configuration;
	
	public FreemarkerMailTemplateManager() {
	}
	
	public void setMailTemplateDao(MailTemplateDao mailTemplateDao) {
		this.mailTemplateDao = mailTemplateDao;
	}

	public void reload() {
		try {
			mailTemplateMap = new HashMap<String, MailTemplate>();
			configuration = new Configuration();
			
			List<MailTemplate> templateList = mailTemplateDao.getMailTemplateList();
			if (templateList != null) {
				List<TemplateLoader> loaderList = new ArrayList<TemplateLoader>();
				Set<String> templateDirs = new HashSet<String>();
				String dir = null;
				for (MailTemplate mailTemplate : templateList) {
					dir = mailTemplate.getTemplateDir();
					if (StringUtils.isNotBlank(dir)) {
						mailTemplateMap.put(mailTemplate.getTemplateId(), mailTemplate);
						if (!templateDirs.contains(dir)) {
							// 중복 디렉토리 제거
							loaderList.add(new FileTemplateLoader(new File(dir)));
							templateDirs.add(dir);						
						}
					} else {
						throw new MessageException(MessageUtils.format("{0} 템플릿의 경로가 지정되어 있지 않습니다.", mailTemplate.getTemplateId()));
					}
				}
				TemplateLoader[] loaders = new TemplateLoader[loaderList.size()];
				MultiTemplateLoader multiTemplateLoader = new MultiTemplateLoader(loaderList.toArray(loaders));
				configuration.setTemplateLoader(multiTemplateLoader);
			}
		} catch (MessageException e) {
			throw e;
		} catch (Exception e) {
			throw new MessageException("메일 템플릿 정보를 가져오는 중 문제가 발생했습니다.", e);
		}
	}
	
	public String getSubject(Mail mail) throws MailException {
		// TODO 제목도 파라메터를 가져올 수 있게 수정할것. 
		MailTemplate mailTemplate = mailTemplateMap.get(mail.getTemplateId());
		if (mailTemplate == null) {
			throw new MailException(MailStatusCode.TEMPLATE_INFO_NOT_FOUND, MessageUtils.format("메일템플릿 정보가 존재하지 않습니다. (TEMPLATE_ID={0})", mail.getTemplateId()));
		}
		return mailTemplate.getMailSubject();
	}
	
	public String getContent(Mail mail) throws MailException {
		MailTemplate mailTemplate = mailTemplateMap.get(mail.getTemplateId());
		if (mailTemplate == null) {
			throw new MailException(MailStatusCode.TEMPLATE_INFO_NOT_FOUND, MessageUtils.format("메일템플릿 정보가 존재하지 않습니다. (TEMPLATE_ID={0})", mail.getTemplateId()));
		}
		Template template;
		try {
			template = configuration.getTemplate(mailTemplate.getTemplateFilename());
		} catch (FileNotFoundException e) {
			throw new MailException(MailStatusCode.TEMPLATE_FILE_NOT_FOUND, MessageUtils.format("템플릿 파일을 가져올 수 없습니다.(TEMPLATE_ID={0}, TEMPLATE_DIR={1}, TEMPLATE_FILE={2})", mailTemplate.getTemplateId(), mailTemplate.getTemplateDir(), mailTemplate.getTemplateFilename()));
		} catch (IOException e) {
			throw new MailException(MailStatusCode.TEMPLATE_ERROR, MessageUtils.format("템플릿을 가져오는 중 에러가 발생했습니다.(TEMPLATE_ID={0}, TEMPLATE_DIR={1}, TEMPLATE_FILE={2})", mailTemplate.getTemplateId(), mailTemplate.getTemplateDir(), mailTemplate.getTemplateFilename()));
		}
		
		Map<String, Object> root = new HashMap<String, Object>();
		String dataModelType = mailTemplate.getDataModelType();
		if ("X".equals(dataModelType)) {
			// XML
			InputSource input = new InputSource(new StringReader(mail.getCotentData()));
			try {
				root.put("doc", freemarker.ext.dom.NodeModel.parse(input));
			} catch (Exception e) {
				throw new MailException(MailStatusCode.DATA_MODEL_ERROR, MessageUtils.format("데이터 모델을 분석하는중 에러가 발생했습니다. (MAIL_NO={0}, TEMPLATE_ID={1})", mail.getMailNo(), mail.getTemplateId()), e);
			}
		} else if ("J".equals(dataModelType)) {
			// TODO JSON 구현
		} else if ("P".equals(dataModelType)) {
			// Parameter
			StrTokenizer tokenizer = new StrTokenizer(mail.getCotentData(), "&");
			String token = null;
			while (tokenizer.hasMoreTokens()) {
				token = tokenizer.nextToken();
				root.put(StringUtils.substringBefore(token, "="), StringUtils.substringAfter(token, "="));
			}
		} else {
			throw new MailException(MailStatusCode.UNKOWN_DATA_MODEL_TYPE, MessageUtils.format("{0}은 사용할 수 없는 데이터 모델 타입(DATA_MODE_TYPE)입니다. ", dataModelType));
		}
		Writer out = new StringWriter();
		try {
			template.process(root, out);	
		} catch (IOException e) {
			throw new MailException(MailStatusCode.BAD_CONTENT, MessageUtils.format("메일 본문 내용을 작성하는 중 에러가 발생했습니다. (MAIL_NO={0}, TEMPLATE_ID={1})", mail.getMailNo(), mail.getTemplateId()), e);
		} catch (TemplateException e) {
			throw new MailException(MailStatusCode.BAD_CONTENT, MessageUtils.format("메일 본문 내용을 작성하는 중 에러가 발생했습니다. (MAIL_NO={0}, TEMPLATE_ID={1})", mail.getMailNo(), mail.getTemplateId()), e);
		}
		return out.toString();
	}

}

3) 메일 발송 관리자 : 실제적으로 메일 발송을 담당하는 역할을 해야하지만, 여기서는 단순히 로그만 남기게 구현하였다. 나중에 시간날때 자바메일로 직접 발송을 해보자.
package kr.kangwoo.postman.service;

public interface MailSendManager {

	void send(String toAddress, String toName, String fromAddress,
			String fromName, String subject, String content);

}

package kr.kangwoo.postman.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimpleMailSendManager implements MailSendManager {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	public void send(String toAddress, String toName, String fromAddress,
			String fromName, String subject, String content) {
		StringBuilder sb = new StringBuilder();
		sb.append("[메일 발송 시작]").append("\n");
		sb.append("TO_ADDRESS   = ").append(toAddress).append("\n");
		sb.append("TO_NAME      = ").append(toName).append("\n");
		sb.append("FROM_ADDRESS = ").append(fromAddress).append("\n");
		sb.append("FROM_NAME    = ").append(fromName).append("\n");
		sb.append("SUBJECT      = ").append(subject).append("\n");
		sb.append("CONTENT      = ").append(content).append("\n");
		sb.append("[메일 발송 완료]");
		logger.info(sb.toString());
	}

}


7. 메인 클래스 만들기
 - 이제 실행할 클래스를 만들어보자.
package kr.kangwoo.postman.core;

import java.util.Date;
import java.util.List;

import kr.kangwoo.postman.domain.Mail;
import kr.kangwoo.postman.repository.MailDao;
import kr.kangwoo.postman.repository.MailTemplateDao;
import kr.kangwoo.postman.repository.SimpleMailDao;
import kr.kangwoo.postman.repository.SimpleMailTemplateDao;
import kr.kangwoo.postman.service.FreemarkerMailTemplateManager;
import kr.kangwoo.postman.service.MailManager;
import kr.kangwoo.postman.service.MailSendManager;
import kr.kangwoo.postman.service.MailTemplateManager;
import kr.kangwoo.postman.service.SimpleMailManager;
import kr.kangwoo.postman.service.SimpleMailSendManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PostMan {

	private Logger logger = LoggerFactory.getLogger(PostMan.class);
	
	private String daemonName = getClass().getName();
	
	private MailManager mailManager;
	private MailTemplateManager mailTemplateManager;
	private MailSendManager mailSendManager;
	
	private boolean isRunning = true;
	private long lastTemplateReloadTimeMillis = 0;
	private long templateReloadTime = 0;
	private long mailCheckTime = 60 * 1000;
	
	
	
	public void setDaemonName(String daemonName) {
		this.daemonName = daemonName;
	}

	public void setMailManager(MailManager mailManager) {
		this.mailManager = mailManager;
	}

	public void setMailTemplateManager(MailTemplateManager mailTemplateManager) {
		this.mailTemplateManager = mailTemplateManager;
	}

	public void setMailSendManager(MailSendManager mailSendManager) {
		this.mailSendManager = mailSendManager;
	}

	public void setTemplateReloadTime(long templateReloadTime) {
		this.templateReloadTime = templateReloadTime;
	}

	public void setMailCheckTime(long mailCheckTime) {
		this.mailCheckTime = mailCheckTime;
	}

	public void run() {
		try {
			long currentTimeMillis = System.currentTimeMillis();
			long tempDeplyTimeMillis = mailCheckTime;

			logger.info("메일 템플릿 정보 적재 시작");
			mailTemplateManager.reload();
			lastTemplateReloadTimeMillis = currentTimeMillis;
			logger.info("메일 템플릿 정보 적재 완료");
			
			while (isRunning) {
				currentTimeMillis = System.currentTimeMillis();
				tempDeplyTimeMillis = mailCheckTime;
				
				try {
					// 템플릿 정보 읽어오기
					if (templateReloadTime > 0 && (lastTemplateReloadTimeMillis + templateReloadTime) < currentTimeMillis) {
						logger.info("메일 템플릿 정보 적재 시작");
						mailTemplateManager.reload();
						lastTemplateReloadTimeMillis = currentTimeMillis;
						logger.info("메일 템플릿 정보 적재 완료");
					}
					
					List<Mail> sendList = mailManager.getSendList(daemonName);
					logger.info("{}개의 메일을 가져왔습니다.", sendList != null ? sendList.size() : 0);
					
					if (sendList != null) {
						String subject = null;
						String content = null;
						for (Mail mail : sendList) {
							try {
								subject = mailTemplateManager.getSubject(mail);
								content = mailTemplateManager.getContent(mail);
								mail.setStatusCode(MailStatusCode.SEND_READY);
								
								mailSendManager.send(mail.getToAddress(), mail.getToName(), mail.getFromAddress(), mail.getFromName(), subject, content);
								mail.setStatusCode(MailStatusCode.SEND_OK);
								mail.setSentDate(new Date());
							} catch (MailException e) {
								mail.setStatusCode(e.getStatusCode());
								logger.warn("메일 발송 중 에러가 발생했습니다.", e);
							} catch (MessageException e) {
								mail.setStatusCode(MailStatusCode.UNKOWN_ERROR);
								logger.warn("메일 발송 중 에러가 발생했습니다.", e);
							} catch (Exception e) {
								mail.setStatusCode(MailStatusCode.UNKOWN_ERROR);
								logger.warn("메일 발송 중 에러가 발생했습니다.", e);
							} finally {
								mail.setUpdatedBy(daemonName);
								mail.setUpdatedDate(new Date());
								mailManager.updateMail(mail);
							}
						}
					}
				} finally {
					tempDeplyTimeMillis = mailCheckTime - (System.currentTimeMillis() - currentTimeMillis);
					if (tempDeplyTimeMillis > 0) {
						Thread.sleep(tempDeplyTimeMillis);	
					}
				}
			} // end while
		} catch (InterruptedException e) {
			logger.debug(e.getMessage(), e);
			isRunning = false;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}
	
	public static void main(String[] args) {
		PostMan man = new PostMan();
		MailDao mailDao = new SimpleMailDao();
		MailTemplateDao mailTemplateDao = new SimpleMailTemplateDao();
		SimpleMailManager mailManager = new SimpleMailManager();
		mailManager.setMailDao(mailDao);
		FreemarkerMailTemplateManager mailTemplateManager = new FreemarkerMailTemplateManager();
		mailTemplateManager.setMailTemplateDao(mailTemplateDao);
		SimpleMailSendManager mailSendManager = new SimpleMailSendManager();
		
		man.setMailManager(mailManager);
		man.setMailTemplateManager(mailTemplateManager);
		man.setMailSendManager(mailSendManager);
		
		man.run();
	}

}

- 실행결과
18:10:56.140 [INFO ] kr.kangwoo.postman.core.PostMan - 메일 템플릿 정보 적재 시작
18:10:56.218 [INFO ] kr.kangwoo.postman.core.PostMan - 메일 템플릿 정보 적재 완료
18:10:56.218 [DEBUG] kr.kangwoo.postman.service.SimpleMailManager - getSendList(kr.kangwoo.postman.core.PostMan)
18:10:56.218 [INFO ] kr.kangwoo.postman.core.PostMan - 3개의 메일을 가져왔습니다.
18:10:56.359 [INFO ] kr.kangwoo.postman.service.SimpleMailSendManager - [메일 발송 시작]
TO_ADDRESS   = badnom@kangwoo.kr
TO_NAME      = 랑우
FROM_ADDRESS = rangwoo@kangwoo.kr
FROM_NAME    = 케로
SUBJECT      = 첫번째 테스트용 메일입니다.
CONTENT      = <html>
<head>
  <title>안녕하세요 랑우님</title>
</head>
<body>
  <h1>안녕하세요 랑우님</h1>
  <p>인문학 과목의 점수는 75점입니다.</p>
  <p>등급은 C입니다</p>
<hr />
<div>발송 전용 메일입니다.문의 사항은 보내지 마시길 바랍니다.</div>
</body>
</html>
[메일 발송 완료]
18:10:56.359 [DEBUG] kr.kangwoo.postman.service.SimpleMailManager - updateMail()
18:10:56.359 [DEBUG] kr.kangwoo.postman.repository.SimpleMailDao - updateMail()
18:10:56.359 [INFO ] kr.kangwoo.postman.service.SimpleMailSendManager - [메일 발송 시작]
TO_ADDRESS   = badnom@kangwoo.kr
TO_NAME      = 바보
FROM_ADDRESS = rangwoo@kangwoo.kr
FROM_NAME    = 케로
SUBJECT      = 첫번째 테스트용 메일입니다.
CONTENT      = <html>
<head>
  <title>안녕하세요 바보님</title>
</head>
<body>
  <h1>안녕하세요 바보님</h1>
  <p>수학 과목의 점수는 99점입니다.</p>
  <p>등급은 A입니다</p>
<hr />
<div>발송 전용 메일입니다.문의 사항은 보내지 마시길 바랍니다.</div>
</body>
</html>
[메일 발송 완료]
18:10:56.359 [DEBUG] kr.kangwoo.postman.service.SimpleMailManager - updateMail()
18:10:56.359 [DEBUG] kr.kangwoo.postman.repository.SimpleMailDao - updateMail()
18:10:56.375 [INFO ] kr.kangwoo.postman.service.SimpleMailSendManager - [메일 발송 시작]
TO_ADDRESS   = badnom@kangwoo.kr
TO_NAME      = 바보
FROM_ADDRESS = rangwoo@kangwoo.kr
FROM_NAME    = 케로
SUBJECT      = 두번째 테스트용 메일입니다.
CONTENT      = <html>
<head>
  <title>안녕하세요 님</title>
</head>
<body>
  <h1>안녕하세요 님</h1>
  <li>혼돈학 과목의 점수는 100점, 등급은 A입니다
  <li>인문학 과목의 점수는 82점, 등급은 B입니다
  <li>좌절학 과목의 점수는 65점, 등급은 F입니다
<hr />
<div>발송 전용 메일입니다.문의 사항은 보내지 마시길 바랍니다.</div>
</body>
</html>
[메일 발송 완료]
18:10:56.375 [DEBUG] kr.kangwoo.postman.service.SimpleMailManager - updateMail()
18:10:56.375 [DEBUG] kr.kangwoo.postman.repository.SimpleMailDao - updateMail()


8. 넋두리
- 시간 관계상 대충 글을 작성한데에 대해서 양해의 말씀을 구합니다.
- 점심 시간 + 야근(여긴 5시 퇴근이랍니다.)까지 하는데도, 시간이 부족하군요. ^^;(주말&퇴근이후는 가족과 함께~~ 퍽!퍽!)
- 로깅 라이브러리는 slf4j & log4j를 사용했습니다. (로그 부분은 무시하셔서도 상관없습니다.)
- 이상한(?) 유틸(StringUtils & StrTokenizer)를 사용하긴 했는데,  이전 강좌에 있는거라서 생략(?)했습니다.
- 급조품이라 너무 엉성하고 부끄럽지만, 대충 이런것도 있구나라는데 만족해 주시면 감사하겠습니다.