최초작성일 : 2005-02-22
최종수정일 : 2005-02-23
이번에는 웹어플리케이션 예제를 한번 분석해보려고 합니다.
다음번에는 이 예제를 조금 더 발전시켜보는 방향으로 해보죠
일단 velocity에서 제공해주는 예제중에서 forumdemo라는 예제를 분석해보도록 하겠습니다.
기능은 간단한 forum의 기능을 제공해주는 작은 어플리케이션입니다.
물론 DB는 사용하지 않습니다.
이 어플리케이션은 다음과 같은 폴더 구조를 가지고 있습니다.

우리가 중요하게 봐야할 곳은 template폴더의 vm들과 WEB-INF/web.xml, WEB-INF/conf/velocity.properties 그리고 소스파일들이지요.
template은 명칭 그대로 템플릿을 모아둔 폴더 입니다.
list.vm은 message들의 목록을 출력할 때 쓰이는 템플릿이고,
reply.vm은 해당 message에 reply할 때 사용되는 템플릿이며,
view.vm은 해당 message와 그 하위의 reply들을 화면에 출력할 때 사용되는 템플릿입니다.
일단 예제를 한번 돌려보면서 해보는게 좋겠지요?
이전에 ant examples를 했다면 아마도 forumdemo도 모두 컴파일이 되어 있을겁니다.
(안하셨다면 이전 게시물을 참고하시기 바랍니다.)
이 forumdemo폴더 자체를 tomcat이설치된폴더/webapp 밑으로 복사를 합니다.
(물론 ant를 이용하여 자동으로 deploy해도 되겠지만 지금은 일단 복사하는 방법을 선택합니다.)
tomcat을 기동하고, 브라우저창에 http://localhost:8080/forumdemo 를 입력하시면 다음과 같은 화면이 나옵니다. 아무거나 클릭을 해볼까요?

당황스럽게도 404에러, 즉 페이지를 찾을 수 없다는 에러가 나는데, 이것은 web.xml에서 servlet을 mapping시켜주지 않아서 생기는 현상입니다.

WEB-INF/web.xml 파일에 다음을 추가시켜 줍니다.
<servlet-mapping>
<servlet-name>forum</servlet-name>
<url-pattern>/servlet/forum</url-pattern>
</servlet-mapping>
자 tomcat을 재시작 시킨후에 다시 한번 아무 메뉴나 클릭해봅시다.

이번에는 500에러가 나타납니다. 왜그럴까요? 에러로그를 잘 살펴보시면 template을 가져오지 못했기 때문에 에러를 발생시킨것을 알 수 있습니다.
이것도 역시 WEB-INF/web.xml파일이 잘못되어 있어서 발생하는 현상입니다.
<init-param>
<param-name>properties</param-name>
<param-value>/WEB-INF/conf/velocity.properties</param-value>
</init-param>
를 다음과 같이 바꿔줍니다.
<init-param>
<param-name>org.apache.velocity.properties</param-name>
<param-value>/WEB-INF/conf/velocity.properties</param-value>
</init-param>
다시 tomcat을 재시작한 후에 클릭해보면 제대로 나오는것을 확인 할 수 있습니다.
일단 예제가 돌아가기 시작하고 있으니 이제 실제적으로 이 어플리케이션을 분석해보도록 하겠습니다.
이 어플리케이션은 모든 서블릿요청은 org.apache.velocity.demo.ControllerServlet 이 담당합니다.
get방식으로 parameter를 받아서 그에 해당하는 action을 org.apache.velocity.demo.action 하위의 클래스들로 실행시켜주는 것이지요.
ControllServlet.java
(import는 생략했습니다.)
public class ControllerServlet extends VelocityServlet
{
private static String ERR_MSG_TAG = "forumdemo_current_error_msg";
/**
* template path에 대한 정보를 가져오기 위한 메소드 입니다.
*/
protected Properties loadConfiguration(ServletConfig config )
throws IOException, FileNotFoundException
{
String propsFile = config.getInitParameter(INIT_PROPS_KEY);
/*
* webapp root에 대한 절대경로를 통해서 template파일의 위치를 찾습니다.
* tomcat과 같은 servlet 2.2 이상의 지원하는 container에서 가능하다고 하네요.
*/
if ( propsFile != null )
{
String realPath = getServletContext().getRealPath(propsFile);
if ( realPath != null )
{
propsFile = realPath;
}
}
// 설정파일을 load합니다. 500에러가 발생했던 이유가 여기에 있는거지요.
// 내부적으로는 org.apache.velocity.properties로 정의되어 있었는데,
// web.xml에서는 properties로 정의되어 있었기때문에 찾지못해서 에러가 발생했던 것이죠.
Properties p = new Properties();
p.load( new FileInputStream(propsFile) );
/**
* template의 경로와 log의 경로를 설정합니다.
*/
// 설정파일에 정의되어있는 resource loader(차후에 다시 얘기나옵니다.)의 경로(path)를 찾아서 실제 경로로 바꾸어 다시 정의합니다. [file.resource.loader.path = /template]
String path = p.getProperty("file.resource.loader.path");
if (path != null)
{
path = getServletContext().getRealPath( path );
p.setProperty( "file.resource.loader.path", path );
}
// 역시 설정파일에 정의되어있는 runtime.log의 경로를 실제 경로로 바꾸어 다시 설정합니다. [runtime.log = /forumdemo_velocity.log]
path = p.getProperty("runtime.log");
if (path != null)
{
path = getServletContext().getRealPath( path );
p.setProperty("runtime.log", path );
}
return p;
}
/**
* 서블릿을 제어하기 위해서 VelocityServlet의 handleRequest메소드를 확장하여 사용합니다.
* @param VelocityServlet에서 생성된 Context
* @return template
*/
public Template handleRequest( Context ctx )
{
HttpServletRequest req = (HttpServletRequest)ctx.get(VelocityServlet.REQUEST);
HttpServletResponse resp = (HttpServletResponse)ctx.get(VelocityServlet.RESPONSE);
Template template = null;
String templateName = null;
HttpSession sess = req.getSession();
sess.setAttribute(ERR_MSG_TAG, "all ok" );
try
{
// processRequest를 통해서 파라미터의 command(list, reply 등등)에 해당하는
// template의 이름을 받아옵니다.
templateName = processRequest( req, resp, ctx );
// template이름을 이용하여 template을 가져옵니다.
template = getTemplate( templateName );
}
catch( ResourceNotFoundException rnfe )
{
String err = "ForumDemo -> ControllerServlet.handleRequest() : Cannot find template " + templateName ;
sess.setAttribute( ERR_MSG_TAG, err );
System.out.println(err );
}
catch( ParseErrorException pee )
{
String err = "ForumDemo -> ControllerServlet.handleRequest() : Syntax error in template " + templateName + ":" + pee ;
sess.setAttribute( ERR_MSG_TAG, err );
System.out.println(err );
}
catch( Exception e )
{
String err = "Error handling the request: " + e ;
sess.setAttribute( ERR_MSG_TAG, err );
System.out.println(err );
}
return template;
}
/**
* command패턴을 이용하여 request에서 넘어온 command에 해당하는 template의 이름을 반환합니다.
* 각 command는 org.apache.velocity.demo.action의 하위의 클래스들에 구현되어 있습니다.
* exec메소드에 각 command가 담당한 부분에 대한 처리가 들어있습니다.
* 메세지들을 context에 담는다던지하는 것들 말이지요...
* @param the request
* @param the response
* @param the context
* @return 사용할 템플릿의 이름
*/
private String processRequest( HttpServletRequest req, HttpServletResponse resp, Context context )
throws Exception
{
Command c = null;
String template = null;
String name = req.getParameter("action");
if ( name == null || name.length() == 0 )
{
throw new Exception("Unrecognized action request!");
}
if ( name.equalsIgnoreCase("list") )
{
c = new ListCommand( req, resp);
template = c.exec( context );
}
else if ( name.equalsIgnoreCase("post") )
{
c = new PostCommand( req, resp );
template = c.exec( context );
}
else if ( name.equalsIgnoreCase("reply") )
{
c = new ReplyCommand( req, resp );
template = c.exec( context );
}
else if ( name.equalsIgnoreCase("postreply") )
{
c = new PostReplyCommand( req, resp );
template = c.exec( context );
}
else if ( name.equalsIgnoreCase("view") )
{
c = new ViewCommand( req, resp );
template = c.exec( context );
}
return template;
}
/**
* 에러가 발생했을때 브라우저 상에 출력하기 위한 메소드입니다.
* VelocityServlet의 error메소드를 override하여 사용합니다.
*/
protected void error( HttpServletRequest request, HttpServletResponse response, Exception cause )
throws ServletException, IOException
{
HttpSession sess = request.getSession();
String err = (String) sess.getAttribute( ERR_MSG_TAG );
StringBuffer html = new StringBuffer();
html.append("<html>");
html.append("<body bgcolor=\"#ffffff\">");
html.append("<h2>ForumDemo : Error processing the request</h2>");
html.append("<br><br>There was a problem in the request." );
html.append("<br><br>The relevant error is :<br>");
html.append( err );
html.append("<br><br><br>");
html.append("The error occurred at :<br><br>");
StringWriter sw = new StringWriter();
cause.printStackTrace( new PrintWriter( sw ) );
html.append( sw.toString() );
html.append("</body>");
html.append("</html>");
response.getOutputStream().print( html.toString() );
}
}