'2009/07'에 해당되는 글 11건

Getting Proxy Client IP with WebLogic :: 2009/07/28 10:04

웹서버와 웹로직 서버가 플러그인 셋팅이 되었을 경우, 클라이언트에서 웹서버를 통해 웹로직에 배포된 웹어플리케이션에서 request.getRemoteAddr() 하여 IP를 얻으면 디폴트로 웹서버의 IP를 돌려준다.

브라우저 클라이언트 IP를 얻으려면 웹로직 콘솔에서 다음과 같이 셋팅해주면 된다.

Server --> Configuration --> General --> WeblogicPluginEnabled 를 true로 해준다.

------------------
WeblogicPluginEnabled—If you set this attribute to true for a cluster or a Web application that receives requests from the HttpClusterServlet, the servlet will respond to getRemoteAddr calls with the address of the browser client from the proprietary WL-Proxy-Client-IP header, instead of returning the Web server address.

웹개발자가 알아야 할 10가지 :: 2009/07/26 22:46

1. 다양한 브라우저에서 테스트한다.

2. GET과 POST의 차이점을 알고 사용한다.
  - 기본적으로 Get은 데이터를 조회하는데, Post는 데이터를 변경하는데 사용한다.
  - Post 방식 자체를 보안 수단으로 사용하지 말고, 모든 요청은 서버쪽에서 처리전에 검증되어야 한다.
  - HTTP 프로토콜을 알고 사용한다.

3. HTML 문서내의 ID 값은 유일해야 한다.
  - 태그명 이외에 거의 대부분 태그에 'id'와 'class'를 사용하여, JavaScript를 통한 동적 기능과 스타일을 제공을 할 수 있다.

4. CSS와 JavaScript는 최대한 HTML 밖에 둔다.
  - 나쁜 예:

<a href="#" onclick="window.open('page.html')">my page</a>
  - 좋은 예:
<a href="page.html" class="popup">my page</a>
--------
jQuery(function ($) {
  $("a.popup").click(function () {
    window.open(this.href);
    return false;
  });
});

5. 문서 Heading을 위해서는 Heading 태그(<h1> ~ <h6>)를 사용한다.

6. Table은 데이터 표현에만 사용하고, Layout 용도로 사용하지 않는다.
  - 최근 CSS 기술은 아주 복잡한 레이아웃도 잘 표현할 수 있고, HTML 내 마크업은 표현에 독립적으로 철리 될 수 있다.

7. 의미있는 Markup을 사용하며, 대부분의 블럭은 List로 표현될 수 있다.
  - <ul>, <ol>, <dl> 등 의미에 맞게 사용한다.
  - 마이크로 포맷을 제외하고 헤딩, 단락, 테이블이 아닌 것들은 대부분 list 형태로 표현될 수 있다.

8. 브라우저간 JavaScript 비호환성 극복을 위해 JavaScript 프레임워크를 사용한다.
  - Prototype, Dojo, jQuery, YUI 등 다양한 JavaScript 프레임워크들은 멀티 브라우저 호환성을 잘 지원한다.

9. 브라우저간 CSS 비호환성 극복을 위해 조건 코멘트를 사용한다.

<link type="text/css" rel="stylesheet" href="/styles/site.css"/>
<!--[if IE]>
<link type="text/css" rel="stylesheet" href="/styles/site-ie.css"/>
<![endif]-->

10. 문자 인코딩 Character Set으로 유니코드만을 사용한다.
  - HTTP Content-Type 헤더:

Content-Type: text/html; charset=utf-8 
  - 문서 내 메타 태그:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

출처: http://www.mattryall.net/blog/2008/08/10-things-every-web-developer-should-know

DJ가 말하는 백남준의 빤스 사건 :: 2009/07/21 06:39

김대중 전 대통령이 고 김수환 추기경과의 일화를 얘기하시던 중에... 출처

A Simple Web App using Grails :: 2009/07/18 23:18

User inserted image
Grails는 Java 플랫폼 기반의 다이나믹 프로그래밍 언어인 Groovy의 장점을 취하고, 스프링, 하이버네이트, Ant, JUnit 등 여러 프레임워크를 기반으로 완전한 WAR 형태의 웹어플리케이션을 구축할 수 있는 오픈소스 웹어플리케이션 프레임워크이다. Grails는 개발 생산성의 향상을 위해 COC(Convention over Configuration)DRY(Don't Repeat Yourself)와 같은 사상을 적용하고 있다.

기본 사상이 같은 Ruby 언어 기반의 Ruby on Rails와 종종 비교되어 지는데, 자바 개발자들에게는 기존에 작성된 Java 라이브러리를 그대로 사용할 수 있는 Grails가 더 매력적인 것 같다. 예전에 관심을 가지고 Rails를 학습했던 적인 있었는데, 나름 매력을 느꼈었지만 그냥 새로운 기술에 대한 학습으로 만족해야만 했던 적이 있다.

참고로, Grail의 원래 의미는 the Holy Grail(성배)로 예수님의 최후에 만찬에서 사용한 잔으로 기적적인 힘을 가졌다고 알려져 왔으며 복수개가 존재하지는 않는다. 초기에 'Groovy on Rails'라고 부르다가 Ruby on Rails의 창시자인 David Heinemeier Hansson의 요청에 대한 응답으로 Grails로 바꾸어 사용했다고 한다.

Grails를 이용하여 초간단 웹어플리케이션을 만들어 보자.

1. Grails 설치하기
http://www.grails.org/Download에서 최신 버전을 다운로드한 후 설치할 디렉토리를 정해 푼다. 이 설치 디렉토리를 GRAILS_HOME 환경변수로 지정하고, 서브디렉토리인 bin 디렉토리를 PATH에 잡아주면 된다. 물론 JAVA_HOME도 환경변수로 지정되어 있어야 한다. 쉘에서 grails 커맨드를 실행해서 정상적인 메시지가 나오면 설치가 완료된 것이다. 아래는 하나의 설정 예이다.
SET JAVA_HOME=C:\Java\jdk1.5.0
SET GRAILS_HOME=C:\Tools\grails-1.1.1
SET PATH=.;%JAVA_HOME%\bin;%GRAILS_HOME%\bin;%PATH%;

2. 어플리케이션 생성하기
빌드인 타겟(target) 중 하나인 create-app 을 실행하여 어플리케이션 프로젝트를 생성한다. 기본적으로 이클립스 프로젝트를 만들며, 아래와 같이 프로젝트 기본 디렉토리들을 생성하게 된다. 예로서 AutoMart라는 어플리케이션을 생성한 모습이다.
C:\Works>grails create-app AutoMart
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: C:\Tools\grails-1.1.1

Base Directory: C:\Works
Running script C:\Tools\grails-1.1.1\scripts\CreateApp_.groovy
Environment set to development
    [mkdir] Created dir: C:\Works\AutoMart\src
    [mkdir] Created dir: C:\Works\AutoMart\src\java
    [mkdir] Created dir: C:\Works\AutoMart\src\groovy
    [mkdir] Created dir: C:\Works\AutoMart\grails-app
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\controllers
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\services
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\domain
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\taglib
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\utils
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\views
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\views\layouts
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\i18n
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\conf
    [mkdir] Created dir: C:\Works\AutoMart\test
    [mkdir] Created dir: C:\Works\AutoMart\test\unit
    [mkdir] Created dir: C:\Works\AutoMart\test\integration
    [mkdir] Created dir: C:\Works\AutoMart\scripts
    [mkdir] Created dir: C:\Works\AutoMart\web-app
    [mkdir] Created dir: C:\Works\AutoMart\web-app\js
    [mkdir] Created dir: C:\Works\AutoMart\web-app\css
    [mkdir] Created dir: C:\Works\AutoMart\web-app\images
    [mkdir] Created dir: C:\Works\AutoMart\web-app\META-INF
    [mkdir] Created dir: C:\Works\AutoMart\lib
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\conf\spring
    [mkdir] Created dir: C:\Works\AutoMart\grails-app\conf\hibernate
......
Created Grails Application at C:\Works/AutoMart

이클립스에서 프로젝트를 임포트하면 다음과 같은 모습이다. 프로젝트 속성의 Java Build Path에서 GRAILS_HOME이라는 변수를 추가해서 설치디렉토리를 지정을 해줘야 참조 라이브러리들을 연결할 수 있다.
User inserted image

Grails가 제공하는 빌드인 타겟을 보려면 쉘에서 grails hlep 명령을 사용하면 아래와 같이 볼 수 있다.
C:\Works>grails help
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: C:\Tools\grails-1.1.1

Base Directory: C:\Works\AutoMart
Running script C:\Tools\grails-1.1.1\scripts\Help_.groovy
Environment set to development

Usage (optionals marked with *):
grails [environment]* [target] [arguments]*

Examples:
grails dev run-app
grails create-app books

Available Targets (type grails help 'target-name' for more info):
grails bootstrap
grails bug-report
grails clean
grails compile
grails console
grails create-app
grails create-controller
grails create-domain-class
grails create-filters
grails create-integration-test
grails create-plugin
grails create-script
grails create-service
grails create-tag-lib
grails create-unit-test
grails doc
grails generate-all
grails generate-controller
grails generate-views
grails help
grails init
grails install-plugin
grails install-templates
grails list-plugins
grails package
grails package-plugin
grails plugin-info
grails release-plugin
grails run-app
grails run-war
grails schema-export
grails set-proxy
grails set-version
grails shell
grails stats
grails test-app
grails uninstall-plugin
grails upgrade
grails war

3. 도메인 클래스 생성하기
어플리케이션 디렉토리로 이동하여 create-domain-class 타겟을 수행하여 도메인 클래스(domain class)를 생성할 수 있다. 에로서 Car 클래스를 생성한 모습이다.
C:\Works\AutoMart>grails create-domain-class Car
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: C:\Tools\grails-1.1.1

Base Directory: C:\Works\AutoMart
Running script C:\Tools\grails-1.1.1\scripts\CreateDomainClass.groovy
Environment set to development
Created DomainClass for Car
Created Tests for Car

${어플리케이션}/grails-app/domain 디렉토리에 도메인 클래스명으로 하나의 .groovy 파일(Car.groovy)과 ${어플리케이션}/test/unit 디렉토리에 해당 클래스에 대한 단위 테스트 파일(CarTests.groovy)이 생성된다. 도메인 클래스인 Car.groovy 파일을 열어 다음과 같이 필드를 추가해주고 저장한다.
class Car {

    String make
    String model
    String year
    Double price

    static constraints = {
    }
}

4. 컨트롤러와 뷰 생성하기
generate-all
타겟을 실행하여 도메인 클래스에 대한 컨트롤러(controller)와 뷰(view)를 생성한다. 해당 도메인 클래스에 대한 CRUD(Create/Read/Update/Delete) 기능을 구현한 하나의 컨트롤러와 뷰들을 생성하는데, Grails는 Rails에서와 마찬가지로 이러한 Scaffolding을 지원한다.
C:\Works\AutoMart>grails generate-all Car
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: C:\Tools\grails-1.1.1

Base Directory: C:\Works\AutoMart
Running script C:\Tools\grails-1.1.1\scripts\GenerateAll.groovy
Environment set to development
...
Generating views for domain class Car ...
Generating controller for domain class Car ...
Finished generation for domain class Car

${어플리케이션}/grails-app/controllers 밑에 도메인 클래스에 대한 하나의 컨트롤러 파일(CarController.groovy)이 생성되고, ${어플리케이션}/grails-app/views/car 와 같은 뷰 디렉토리가 생기며 그 곳에 뷰 처리용 GSP(Groovy Server Pages) 파일들이 생성이 된다. 이렇게 간단하지만 완전한 하나의 웹어플리케이션을 만든 것이다.

5. 웹어플리케이션 실행하기
다음과 같이 프로젝트 디렉토리에서 run-app 타겟을 사용하여 웹어플리케이션을 바로 실행해 볼 수 있다. 서블릿 컨테이너로는 Jetty를 내부적으로 사용한다. http://localhost:8080/AutoMart 주소로 접근하면 어플리케이션 시작 페이지를 볼 수 있다. 기본 포트인 8080 포트를 다른 포트로 바꾸려면 -Dserver.port=8081과 같은 실행 옵션에 추가해 주면 된다.
C:\Works\AutoMart>grails run-app
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: C:\Tools\grails-1.1.1

Base Directory: C:\Works\AutoMart
Running script C:\Tools\grails-1.1.1\scripts\RunApp.groovy
Environment set to development
Running Grails application..
Server running. Browse to http://localhost:8080/AutoMart

다음은 Car 객체를 생성하여 신규로 등록하는 화면이다. Grails는 개발과 테스트 모드에서 기본적으로 hsqldb를 In-memory 형태로 사용하여 데이터를 저장하고 종료시 저장 데이터는 사라진다.
User inserted image

다음은 등록된 Car 정보들에 대한 리스트 화면이다.
User inserted image

6. WAR 파일로 배포하기
어플리케이션 프로젝트 디렉토리에서 grails war 명령을 실행하면 웹어플리케이션 .war 파일이 생성된다. 표준 자바 웹어플리케이션으로 대부분의 어플리케이션 서버에 배포되어 질 수 있다. (여기 참조)
C:\Works\AutoMart>grails war
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: C:\Tools\grails-1.1.1

Base Directory: C:\Works\AutoMart
Running script C:\Tools\grails-1.1.1\scripts\War.groovy
Environment set to production
...
Done creating WAR C:\Works\AutoMart/AutoMart-0.1.war

Progress Bar using AJAX + DWR :: 2009/07/16 23:18

AJAX는 웹어플리케이션과 사용자간의 상호작용을 높히고, RIA 어플리케이션을 만드는 데 유용한 웹개발 기술 중 하나이다. AJAX를 이용하여 웹개발을 쉽게 할 수 있도록 여러 기능을 모아놓은 것이 AJAX 프레임워크이다. 자바스크립트 기반의 클라이언트 사이드 AJAX 프레임워크에는 Dojo, Ext JS, jQuery, Prototype, Script.aculo.us, Yahoo! UI Library 등으로 매우 다양하다. 또한 서버 사이드 AJAX 동작을 위해 JAVA 기반으로 구현된 AJAX 프레임워크에는 DWR, GWT 등이 있다.

AJAX는 본질적으로 비동기 요청을 수행하므로 서버쪽의 시간이 오래 걸리는 대량의 배치처리 등의 작업을 Invoke하는데 유용하게 사용되어 질 수 있다. 이러한 작업의 진행 상태를 모니터링하는데 필요한 진행상태 바 (Progress Bar)이다. AJAX 프레임워크로 DWR을 이용하여 다음과 같은 진행상태 바를 만들어 볼 수 있다.

Ajax Progress Bar

1. DWR 서블릿 설정하기
  - /WEB-INF/web.xml에 다음과 같은 DWR 서블릿 설정을 해준다.
  - 참고로 org.directwebremoting.spring.DwrSpringServlet를 사용할 경우 스프링 어플리케이션 컨텍스트 설정에 dwr 네임스페이스를 등록하고, 그 곳에 DWR 처리용 클래스를 직접 등록하여 dwr.xml 없이 사용할 수도 있다.
<servlet>
    <servlet-name>dwr-invoker</servlet-name>
    <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>dwr-invoker</servlet-name>
    <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

2. 서버 쪽 Ajax 처리용 Java 클래스 작성
  - 예로서 다음과 같은 서버쪽 배치업무를 수행할 DWR Ajax 처리용 클래스를 작성한다.
  - HeavyLoadTaskExecutor는 Ajax 처리를 위해 내부 쓰레드의 시작, 중지를 수행하며 처리건수, 진행상태 등을 얻을 수 있게 작성되어 질 수 있다.
package net.java2go.demo.ajax.dwr;

import java.util.HashMap;
import java.util.Map;

public class HeavyLoadTaskExecutor {

    private HeavyLoadTask heavyLoadTask;

    public HeavyLoadTaskExecutor(HeavyLoadTask heavyLoadTask) {
        this.heavyLoadTask = heavyLoadTask;
    }

    public void startTask(String taskId) {
        heavyLoadTask.start(taskId);
    }

    public void stopTask() {
        heavyLoadTask.stop();
    }

    public boolean isTaskRunning() {
        return heavyLoadTask.isRunning();
    }

    public int getTaskProgressRate() {
        return heavyLoadTask.getProgressRate();
    }

    public Map getTaskStatus() {
        Map map = new HashMap();
        map.put("running", new Boolean(heavyLoadTask.isRunning()));
        map.put("total", new Integer(heavyLoadTask.getTotalCount()));
        map.put("done", new Integer(heavyLoadTask.getDoneCount()));
        map.put("progress", new Integer(heavyLoadTask.getProgressRate()));
        map.put("message", heavyLoadTask.getMessage());        
        return map;
    }
}

  - HeavyLoadTask는 실제 업무처리를 위한 테스트용 클래스로 스프링에 의해 기본적으로 Singleton으로 인스턴스화되며 한 번에 한 개의 쓰레드의 실행만 허용한다.
package net.java2go.demo.ajax.dwr;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class HeavyLoadTask {

    private final Log log = LogFactory.getLog(getClass());

    private Thread taskThread;

    private boolean running;

    private int totalCount;

    private int doneCount;

    private String message;

    private String taskId;

    public HeavyLoadTask() {
        log.info("HeavyLoadTask created.");
    }

    public synchronized void start(String taskId) {
        this.taskId = taskId;
        if (!running) {
            this.running = true;
            this.totalCount = 0;
            this.doneCount = 0;
            this.message = null;
            taskThread = new Thread(new TaskThread());
            taskThread.start();
            log.info("Thread started.");
        } else {
            log.info("Thread has already been running.");
        }
    }

    public synchronized void stop() {
        if (running) {
            this.running = false;
            taskThread.interrupt();
            log.info("Thread interrupted.");
        }
    }

    public boolean isRunning() {
        return running;
    }

    public int getTotalCount() {
        return totalCount;
    }

    public int getDoneCount() {
        return doneCount;
    }

    public String getMessage() {
        return message;
    }

    public int getProgressRate() {
        return totalCount == 0 ? 0 : doneCount * 100 / totalCount;
    }

    /**
     * TaskThread
     */
    private class TaskThread implements Runnable {

        public TaskThread() {
        }

        public void run() {
            try {
                log.debug("--- Thread Start ---");
                log.debug("taskId=" + taskId);

                totalCount = 30;
                log.debug("totalCount=" + totalCount);

                for (int i = 0; i < totalCount; i++) {
                    if (Thread.currentThread().isInterrupted()) {
                        break;
                    }
                    // An intentional exception
                    if ((taskId == null || taskId.length() == 0) && i == 10) {
                        throw new RuntimeException("Test error occurred!");
                    }
                    Thread.sleep(1000);
                    log.debug("Task " + i + " done.");
                    doneCount++;
                }
                // Thread.sleep(200);

            } catch (InterruptedException ex) {
                // Ignore.
            } catch (Exception ex) {
                log.error(ex.getMessage(), ex);
                message = ex.getMessage();
            } finally {
                running = false;
                log.info("Thread stopped.");
                log.debug("--- Thread Stop ---");
            }
        }
    }
}

3. 스프링 빈 설정
  - 스프링을 이용하여 DI을 적용한다면 다음과 같은 스프링 빈 설정을 applicationContext.xml에 추가한다.
<bean id="heavyLoadTask" class="net.java2go.demo.ajax.dwr.HeavyLoadTask" />

<bean id="heavyLoadTaskExecutor" 
            class="net.java2go.demo.ajax.dwr.HeavyLoadTaskExecutor">
    <constructor-arg>
        <ref bean="heavyLoadTask" />
    </constructor-arg>
</bean>

4. DWR 클래스 등록하기
  - /WEB-INF/dwr.xml에 다음과 같이 AJAX 서비스로 노출할 클래스를 등록한다.
  - 클라이언트에서 사용하도록 노출할 메소드를 포함하거나 제외 할 수도 있다.
<!DOCTYPE dwr PUBLIC
    "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN"
    "http://getahead.ltd.uk/dwr/dwr20.dtd">
<dwr>
    <allow>
        <!--
        <convert converter="bean" match="net.java2go.demo.ajax.dwr.Country" />
        <create creator="new" javascript="CountryManager">
            <param name="class" value="net.java2go.demo.ajax.dwr.CountryManager" />
            <include method="getCountryCodes" />
            <include method="getCountry" />
        </create>
        -->
        <create creator="spring" javascript="HeavyLoadTaskExecutor">
            <param name="beanName" value="heavyLoadTaskExecutor" />
            <include method="startTask" />
            <include method="stopTask" />
            <include method="isTaskRunning" />
            <include method="getTaskProgressRate" />
            <include method="getTaskStatus" />
        </create>
    </allow>
</dwr>

5. DWR 클래스 테스트
  - 웹어플리케이션을 서버에 배포 후 <웹어플리케이션 컨텍스트>/dwr/ 디렉토리에 접근하면 AJAX 처리용으로 등록된 클래스들을 확인 할 수 있다.
User inserted image

  - 해당 클래스의 노출된 메소드들의 목록을 볼 수 있고, 이곳에서 클라이언트의 요청처럼 바로 실행해 볼 수 있다. 페이지 상단에 있는 JS 스크립트 주소는 클라이언트 페이지 작성시 포함되어 사용되어질 스크립트이다.
User inserted image

  - 서버 배치업무 쓰레드를 시작하고, 진행 중에 상태값을 조회한 결과화면이다.
User inserted image

6. 클라이언트 페이지 작성하기
  - 먼저 AJAX 요청할 서버 클래스에 대한 해당 자바스크립트를 포함한다.
  - CSS를 이용하여 화면 모양을 구성하고, 아래와 같은 자바스크립트를 사용하여 서버의 배치업무에 대한 시작, 중지, 상태 조회 등을 비동기로 요청할 수 있다.
  - 시작 요청 후 window 객체의 setTimeout()을 이용하여 주기적(1초간격)으로 상태 조회를 하고 화면에 진행상태 값을 업데이트한다.
<script type='text/javascript' src='<c:url 
        value='/dwr/interface/HeavyLoadTaskExecutor.js'/>'></script>
<script type='text/javascript' src='<c:url value='/dwr/engine.js'/>'></script>
<script type='text/javascript' src='<c:url value='/dwr/util.js'/>'></script>

<h1>Progress Bar</h1>  
<p>Progress Bar using Ajax + DWR</p>

<div id="executor">
  Task ID: <input type="text" id="taskId" style="width:100px;"/>
  <input type="button" id="btnStart" value="Start" onclick="startTask()"/>    
  <br/>
  <div id="progress" style="display:none;">
    <div id="progCnt">Processing...</div>
    <div id="progBar">
      <img id="progImg" src="<c:url value="/images/sizebar.gif" />"/>
      <span id="progRate"></span>        
    </div>      
    <div id="message" style="display:none;"></div>
    <input type="button" id="btnStop" value="Stop" onclick="stopTask()"/>
  </div>
</div>

<script>
function startTask() {
  var taskId = $('taskId').value;
  HeavyLoadTaskExecutor.startTask(taskId, reply1);
}

function stopTask() {
  HeavyLoadTaskExecutor.stopTask(reply2);
}

function getTaskStatus() {
  HeavyLoadTaskExecutor.getTaskStatus(reply3);
}

function checkTaskRunning() {
  HeavyLoadTaskExecutor.isTaskRunning(reply4);
}

function reply1(data) {
  $('btnStart').disabled = true;      
  $('btnStop').style.display = 'block';
  $('progCnt').innerHTML = "Processing...";
  $('progImg').style.width = 0;
  $('progRate').innerHTML = '';
  $('progress').style.display = 'block';
  $('message').innerHTML = '';
  $('message').style.display = 'none';
  statusTimeout = window.setTimeout('getTaskStatus()', 1000);
}

function reply2(data) {
  $('btnStart').disabled = false;      
  $('btnStop').style.display = 'none';
  $('message').innerHTML = "Stopped";
  $('message').style.display = 'block';    
  window.clearTimeout(statusTimeout);
}

function reply3(data) {
  var running = data['running'];
  var total = data['total'];
  var done = data['done'];
  var progress = data['progress'];
  var message = data['message'];
  $('progImg').style.width = progress * 2;
  $('progRate').innerHTML = progress + '%';        
  if (running == false) {
    $('progCnt').innerHTML = "Processed: <strong>"+done+"</strong>";
    if (message != null && message != '') {
      $('message').innerHTML = message;
      $('message').style.display = 'block';
    } else {
      $('btnStart').disabled = false;      
      $('btnStop').style.display = 'none';
    }            
    window.clearTimeout(statusTimeout);
  } else {
    $('progCnt').innerHTML = "Processing... <strong>"+done+"/"+total+"</strong>";
    $('btnStart').disabled = true;      
    $('btnStop').style.display = 'block';
    statusTimeout = window.setTimeout('getTaskStatus()', 1000);
  }
}

function reply4(data) {
  if (data == true) {
    $('progCnt').innerHTML = "Processing...";
    $('btnStart').disabled = true;      
    $('progress').style.display = 'block';
    statusTimeout = window.setTimeout('getTaskStatus()', 1000);
  }
}

checkTaskRunning();
</script>

7. 클라이언트 실행 결과
  - 서버에 업무처리 시작 요청을 한 후에 주기적으로 진행상태 값을 조회하여 진행 상태를 표시한다
User inserted image
  - 서버의 업무처리가 모두 완료된 경우
User inserted image
  - 업무 처리 중간에 서버에 중지 요청을 한 경우
User inserted image
  - 서버 업무 처리 중간에 오류가 발생하여 중지된 경우
User inserted image

"The quality of any [software] is ultimately determined by the quality of the interaction between one human and one system." - Jef Raskin -

IE에서 View Source Editor 바꾸기 :: 2009/07/15 22:41

Internet Explorer의 View Source Editor을 변경하려면, 레지스트리 편집기(regedit)을 실행 후 다음 경로에서 키 값을 바꾸면 된다.

HKEY_LOCAL_MACHINE
|- Software
|-- Microsoft
|--- Internet Explorer
|---- View Source Editor
|----- Editor Name (Default) = C:\windows\notepad.exe


DIV 블럭을 중앙에 정렬하기 :: 2009/07/11 15:49

DIV 블럭안의 내용을 중앙에 정렬하려면 다음과 같이 한다. align="center"는 deprecated 되어 지원되지 않는다.
<div style="text-align:center;">...</div>

특정 너비(width)를 가진 DIV 블럭 자체를 중앙에 정렬하려면 다음과 같이 좌우 margin을 auto로 하면 된다.
<div style="text-align:center;">
    <div style="margin:0 auto; width:500px;">...</div>
</div>

CSS 포지셔닝 10단계 배우기 참조:
http://www.barelyfitz.com/screencast/html-training/css/positioning/

Custom Error Pages + SiteMesh :: 2009/07/10 23:30

웹서버나 서블릿 컨테이너에서 에러 페이지들을 핸들링 할 수 있도록 지원하는데, 이 외에 표준 자바 웹어플리케이션(war)에서 <webapp>/WEB-INF/web.xml 파일안에 에러 코드 또는 자바 예외 타입에 대한 오류 페이지를 설정할 수 있다. 다음과 같은 2가지 방법이 있다.

- 에러 코드를 이용한 설정 방법
<error-page>
    <error-code>500</error-code>
    <location>/error.jsp</location>
</error-page>

<error-page>
    <error-code>403</error-code>
    <location>/403.jsp</location>
</error-page>

<error-page>
    <error-code>404</error-code>
    <location>/404.jsp</location>
</error-page>

- 자바 예외 타입을 이용한 설정 방법
<error-page>
    <exception-type>java.io.IOException</exception-type>
    <location>/errors/IOException.jsp</location>
</error-page>

- 아래는 커스텀 404 오류 폐이지 화면 예이다. SiteMesh에서 decorators.xml에 에러 페이지의 URL 패턴이 해당되더라도 decorator가 적용되지 않는다. 에러 페이지에 decorator를 적용하려면 페이지 내에서 다음과 같이 SiteMesh이 page 태그라이브러리의 applyDecorator 태그를 이용해 직접 적용해 줄 수 있다.
404.jsp
 
    <404.jsp>
<%@ taglib uri="http://www.opensymphony.com/sitemesh/page" prefix="page"%>
<page:applyDecorator name="default">
	...
</page:applyDecorator>

- 다음은 커스텀 500 오류 페이지 화면 예이다. 에러를 발생시킨 해당 JSP 페이지 내에서 JSP @page directive에 errorPage="/error.jsp" (error.jsp는 isErrorPage="true"로 설정)와 같이 지정을 하면 내부적으로 error.jsp 페이지로 리다이렉트되면서 SiteMesh의 decorator가 적용이 된다. 그렇지 않으면 다른 에러 페이지와 마찬가지로 직접 decorator를 적용시켜줘야 한다.
error.jsp

Page Layout & Decoration with SiteMesh :: 2009/07/08 23:14

자바 웹어플리케이션에서 페이지 레이아웃 프레임워크로 주로 사용되는 것 중에는 OpenSymphony의 SiteMesh와 Apache의 Tiles가 있다. 페이지를 구성하는 방식에 차이가 있는데 SiteMesh는 Decorator 패턴을, Tiles는 Composite View 패턴을 사용한다. 이 두 방싱의 비교는 이곳에서 참조할 수 있다. 화면 레이아웃 구성이 어느정도 정형화 되어 있다면, 개별적으로 설정을 할 필요 없이 전체 어플리케이션에 Decorator를 한 번에 적용할 수 있는 SiteMesh가 나을 것이다.

SiteMesh를 이용한 페이지 레이아웃과 동적 메뉴 구성 방법을 정리해 본다.

1. 웹어플리케이션에 SiteMesh 설정하기
  - /WEB-INF/web.xml 파일에  다음과 같이 SiteMesh Filter를 설정해 준다.
<filter>
    <filter-name>sitemesh</filter-name>
    <filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>sitemesh</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2. SiteMesh 커스텀 설정하기
  - 다음과 같은 내용의 sitemesh.xml 파일을 WEB-INF 밑에 둔다. 이 파일이 없으면 JAR 패키지내에 포함된 sitemesh-default.xml 설정파일을 사용하여 기본 decorators.xml 위치 및 기본 Parser, Mapper를 사용한다.
<sitemesh>
  <property name="decorators-file" value="/WEB-INF/decorators.xml"/>
  <excludes file="${decorators-file}"/>
   
  <page-parsers>
    <parser default="true" 
	    class="com.opensymphony.module.sitemesh.parser.FastPageParser"/>
    <parser content-type="text/html"
	    class="com.opensymphony.module.sitemesh.parser.FastPageParser"/>
  </page-parsers>

  <decorator-mappers>
    <mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
      <param name="config" value="${decorators-file}"/> 
    </mapper>
  </decorator-mappers>
</sitemesh>

3. Decorator 설정하기
  - decorators.xml 파일에 SiteMesh decorator 설정 정보를 지정한다. 각 decorator를 적용시킬 페이지들에 대한 URL 패턴을 지정한다. 적용을 제외시킬 URL 패턴도 지정할 수 있다.
<decorators defaultdir="/decorators">
    <excludes>
        <pattern>/40*.jsp</pattern>
        <pattern>/includes/*</pattern>
        <pattern>/resources/*</pattern>
    </excludes>
    <!--
    <decorator name="printable" page="printable.jsp" />
    <decorator name="popup" page="popups.jsp">
        <url-pattern>/popups/*</url-pattern>
    </decorator>
    -->    
    <decorator name="default" page="default.jsp">
        <pattern>/*</pattern>
    </decorator>
</decorators>

4. Decorator 작성하기
  - 페이지 레이아웃을 위해 decorator를 작성한다. 아래는 Header, Menu, Content (Left + Main), Footer 구성을 가지는 화면 레이아웃 예이다.
  - SiteMesh의 decorator 태크 라이브러리를 지정한다.
  - 접근하는 최종 HTML 페이지의 <head> 태크 내용을 <decorator:head/> 부분에 삽입하고, <body> 태그의 내용을 <decorator:body/> 부분에 삽입한다. 또한 <decorator:title/>을 이용해 HTML의 <title> 값을 가져올 수 있다.
  - 자세한 SiteMesh Tag Reference는 이곳에서 참조할 수 있다.
  - Decorator의 <body>에 포함된 <decorator:getProperty property="." [default="."] [writeEntireProperty="." ]/>는 HTML의 <body> 태크 자체에 포함된 속성(id, onload 등)을 그대로 포함하기 위한 것이다.
  - <meta> 태그의 값을 가져오기 위해서 'meta.이름' 형태로 프로퍼티를 가져올 수 있다.
<%@ page language="java" errorPage="/error.jsp" pageEncoding="UTF-8"
        contentType="text/html;charset=utf-8"%>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title><decorator:title/></title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <link rel="stylesheet" type="text/css" media="all" 
        href="<c:url value='/styles/default.css'/>" />
  <script type="text/javascript" src="<c:url value='/scripts/global.js'/>"></script>
  <decorator:head/>
</head>
<body<decorator:getProperty property="body.id" writeEntireProperty="true"
    /><decorator:getProperty property="body.onload" writeEntireProperty="true"/>>
  <div id="page">
    <div id="header">
      <jsp:include page="/includes/header.jsp"/>
    </div>
    <div id="menu">
      <c:set var="currentMenu" scope="request"><decorator:getProperty
          property="meta.menu"/></c:set>
      <jsp:include page="/includes/menu.jsp" />
    </div>
    <div id="content">      
      <div id="left">
        <jsp:include page="/includes/left.jsp" />
      </div>
      <div id="main">
        <div id="indicator">
          <jsp:include page="/includes/indicator.jsp" />
        </div> 
        <decorator:body/>
      </div>
    </div>
    <div id="footer">
      <jsp:include page="/includes/footer.jsp"/>
    </div>
  </div>
</body>
</html>

5. 동적 메뉴 구성하기
  - HTML 페이지의 head에 다음과 같이 meta 태그를 셋팅한다.  서브 메뉴가 존재할 경우 유사한 방식으로 추가 지정할 수 있다.
<%@ page language="java" errorPage="/error.jsp" pageEncoding="UTF-8"
        contentType="text/html;charset=utf-8"%>
<%@ include file="/includes/taglibs.jsp"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
    <title>Menu1 Title</title>
    <meta name="menu" content="menu1" />
</head>
<body>
    <h1>Page Title</h1>    
    <p>Main content goes here...</p>
</body>
</html>
  - Decorator에서 다음과 같이 JSTL을 사용하여 HTML 페이지에서 선언한 meta 태그 값을 변수로 정의한다.
<c:set var="currentMenu" 
        scope="request"><decorator:getProperty property="meta.menu"/></c:set>
  - Decorator에 포함할 메뉴 JSP 파일을 다음과 같이 작성한다. 해당 메뉴에 대한 meta 태크 값일 경우 해당 메뉴에 대해 현재 선택된 메뉴로 적용할 CSS 클래스가 지정되게 한다.
<%@ page language="java" pageEncoding="UTF-8" contentType="text/html;charset=utf-8"%>
<%@ include file="/includes/taglibs.jsp"%>
<ul>
  <li><a 
    class="<c:if test="${currentMenu == 'home'}"><c:out value="current" /></c:if>"
    href="<c:url value='/'/>">Home</a></li>
  <li><a 
    class="<c:if test="${currentMenu == 'menu1'}"><c:out value="current" /></c:if>"
    href="<c:url value='/menu1.htm'/>">Menu1</a></li>
  <li><a 
    class="<c:if test="${currentMenu == 'menu2'}"><c:out value="current" /></c:if>"
    href="<c:url value='/menu2.htm'/>">Menu2</a></li>
  <li><a 
    class="<c:if test="${currentMenu == 'menu3'}"><c:out value="current" /></c:if>"
    href="<c:url value='/menu3.htm'/>">Menu3</a></li>
</ul>
  - CSS 파일에 해당 스타일 클래스를 작성한다. 아래는 한 예시이다.
div#menu ul li a.current {
    color: #fff;
    background: #fff url("images/current-bg.gif") top left repeat-x;
    padding: 10px 15px 0;
}

6. SiteMesh를 이용하여 페이지 레이아웃과 동적 메뉴가 적용된 화면 예시
User inserted image

User inserted image

7. 라이브러리 구성
  - SiteMesh는 서블릿 필터의 구현체로 JDK 1.4 이상, 서블릿 스펙 2.3 이상의 환경에서 사용할 수 있다.
  - JSP 스펙 1.2 (서블릿 스펙 2.3)인 경우에 JSTL 1.0이 필요하며, JDK 1.4를 이용할 경우 Apache Xerces가 추가로 필요할 것이다.
  - JSP 스펙 2.0 (서블릿 스펙 2.4) 이상인 경우에는 JSTL 1.1을 사용할 수 있으며, JDK 1.5 이상인 경우 Xerces를 별도로 추가할 필요가 없다.
User inserted image

CSS Menu Generator :: 2009/07/07 15:57

CSS를 이용한 웹페이지 메뉴 구성을 참조할 수 있는 싸이트들이다.