2009-12-10

Java EE 6.0

Java EE 6.0이 공식적으로 완결되었다.

Introducing the Java EE 6 Platform



한마디로 Java EE 6.0을 정의하면 Ruby on Rails 따라하기가 되지 않을까...

짧아도 2년 주기로 버전 업그레이드가 되니 지금 좁혀진 간격은 (몇달 안에) 다시금 넓어지겠지...



그래도 톰켓 7.0은 기대가 된다. 빨리 나와라^^

Mark Thomas on Apache Tomcat 7

2009-12-07

아파치 더비 - 칼럼 크기 수정

ALTER TABLE USER ALTER NAME SET DATA TYPE VARCHAR(100);

2009-11-29

Ruby on rails에서 테이블없이 모델 사용하기

Ruby on rails에서 테이블에 매핑되지 않는 입력 폼을 검증할 필요가 있었다.

구글링에서 얻은 가장 만족스러운 방법은 다음이다.

Tableless models in RailsTableless models in Rails


플러그인이나 라이브러리도 필요 없고, 직접 칼럼을 선언하면 된다. 자바에서 get/set을 만드는 걸 생각하면 아무 것도 아니다.

class OnlineDemoRequest < ActiveRecord::Base
  def self.columns() @columns ||= []; end
 
  def self.column(name, sql_type = nil, default = nil, null = true)
    columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
  end
 
  column :email, :string
  column :first_name, :string
  column :last_name, :string
  column :company, :string
 
  validates_presence_of :email
  validates_format_of :email, :with => /^$|^\S+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,4}|[0-9]{1,4})(\]?)$/ix
  validates_presence_of :first_name
  validates_presence_of :last_name
  validates_presence_of :company

end


* 2.3.4에서 테스트를 했다.

Procedure와 Function

메소드는 Procedure와 Function으로 구분된다.

기계적이거나 배타적인 구분을 의미하지는 않는다.

Procedure는 상태의 변화(side effect)를 야기한다.

Function은 무언가를 반환한다.

2009-11-27

Option, Preference, Parameter, Attribute, Property

애플리케이션을 설정할 수 있어야 한다. 이를 Option, Preference, Parameter, Attribute, Property 등의 용어 중에서 무엇을 설명할까?


Parameter

웹 애플리케이션이라면 HTTP 파라미터와의 혼돈으로 사용하지 않는 것이 좋다.


Attribute

모델(혹은 객체)의 속성으로 사용하는 쪽이 더 좋다.


Property

자바라면 System.getProperty 메소드와의 혼돈으로 사용하지 않는 것이 좋다.


Preference 혹은 Option

둘 중에 무엇을 선택해야 할지는 모르겠다. Preference는 사용자와 관련된 느낌을 준다. 누군가와 대화를 할 때는 Option이 더 좋다. 사용자와 애플리케이션에 대해서 따로 설정한다면 둘 다 사용하면 된다. 그렇지만 둘을 굳이 분리해야 하는가?

구현을 java.util.prefs 패키지로 한다면 Preference를 사용하여야 겠지만...



Option/Preference는 컨텍스트에 따라서 달라저야 하는가?

예를 들어 파일 업로드 최대 크기를 보자.

- 전체 애플리케이션에 설정할 수 있다
- 모듈에 따라서 다르게 설정할 수 있다. 모듈은 계층적이다.
- 사용자 혹은 그룹에 따라서 다르게 설정할 수 있다. 그룹은 계층적일 수도 있다.

이렇게 하면 유연성을 얻지만 복잡해진다.

2009-11-26

AJAX를 사용할 수 없을 때

도메인이 다르면 AJAX를 사용할 수 없다. 자바 스크립트로 이를 흉내내는 방법이 있다.

var HTTP = {};

HTTP.getTextWithScript = function(url, callback) {
    var script = document.createElement("script");
    document.body.appendChild(script);
    var funcname = "func" + HTTP.getTextWithScript.counter++;
    HTTP.getTextWithScript[funcname] = function(text) {
                                           callback(text);
                                           document.body.removeChild(script);
                                           delete HTTP.getTextWithScript[funcname];
                                       }
    var emptyImage = new Image();
    emptyImage.onerror =
        function() {
            Progress.hide();
            callback("<div>Error Happened</div>");
        };
    emptyImage.onload =
        function() {
            script.src = HTTP.url + url + "&func=" + encodeURIComponent("HTTP.getTextWithScript." + funcname);
        };
    emptyImage.src = HTTP.url + "/empty.gif";
}

HTTP.getTextWithScript.counter = 0;

해당 서버에 empty.gif 이미지가 존재해야 한다. 서버가 응답하면 문제가 없지만 그렇지 않으면 행이 걸릴 수 있다. 이를 방지하기 위해서 이미지 파일를 이용한다.



사용 방법은 다음과 같다.

HTTP.url = url;
Progress.show("result");
HTTP.getTextWithScript("/some_request",
                       function(text) {
                           $("result").innerHTML = text;
                           Progress.hide();
                           DocResize.adjustHeight();
                       }
                       );



자바(서버 사이드)에서는 다음과 같이 처리한다.

JavascriptCallResponse javascriptResponse = new JavascriptCallResponse(response);
include(request, javascriptResponse, "actual_page.jsp");

response.setContentType("text/javascript; charset=UTF-8");
String func = request.getParameter("func");
PrintWriter writer = response.getWriter();
String text = StringUtils.strip(javascriptResponse.getContent().toString()).replaceAll("(?m)\\s+", " ");
writer.write(func + "('" + text + "');");

실제 화면 로직 처리는 actual_page.jsp에 있다.


JavascriptCallResponse 클래스는 다음과 같다.

package com.dimdol.example;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class JavascriptCallResponse extends HttpServletResponseWrapper {

    private PrintWriter writer;

    private StringWriter stringWriter;

    public ReportServletResponse(HttpServletResponse response) {
        super(response);
    }

    public String getContentType() {
        return "text/html; charset=UTF-8";
    }

    public PrintWriter getWriter() throws IOException {
        if (writer == null) {
            stringWriter = new StringWriter();
            writer = new PrintWriter(stringWriter);
        }
        return writer;
    }

    public StringBuffer getContent() {
        return stringWriter == null ? new StringBuffer() : stringWriter.getBuffer();
    }

}



* 보안 문제일까? 일부 웹 브라우저에서는 동작하지 않는다. 그래서 이 방법보다는 서버에서 프락시 방식으로 처리하는 걸 선호한다.


* JavaScript: The Definitive Guide에 있는 내용을 참조

Guard Clause

이 코드보다는

void initialize() {
  if (!isInitialized()) {
    ...
  }
}


이 코드가 좋다

void initialize() {
  if (isInitialized()) {
   return;
  }
  ...
}



이 코드를 보면 더 명확하다.

void compute {
  Server server = getServer();
  if (server != null) {
    Client client = server.getClient();
    if (client != null) {
      Request current = client.getRequest();
      if (current != null) {
        processRequest(current);
     }
  }
}


void compute {
  Server server = getServer();
  if (server == null)
    return;
  Client client = server.getClient();
  if (client == null)
    return;
  Request current = client.getRequest();
  if (current == null)
    return;
  processRequest(current);
}



* 켄트 백의 구현 패턴 7장 Behavior에서 발췌

2009-11-25

내가 사용하는 소프트웨어

내가 사용하는 소프트웨어들이다.

운영 체계는 윈도우 XP ㅠㅠ... 우분투 사용을 시도했지만 IE 테스트 때문에...

웹 브라우저는 파이어폭스...  주요 웹 브라우저들을 대부분 설치했지만 호환성 테스트가 아니면 사용하지는 않는다.

파이어폭스 플러그은 파이어버그만... 웹 개발자의 축복...


메일은 썬더버드(지메일에 붙어서)를 사용하는데... 지메일을 직접 사용하는 것도 괜찮을 듯...

메신저는 구글 톡만(친구가 별로 없다^^) 사용한다. 파일 전송을 하려면 영어판을 사용해야 한다.


백신은 회사에서 라이센스를 구매한 알약을 쓴다. ClamWin을 사용하기도 했는데...


동영상은 KMP를, PDF 문서는 Adobe Acrobat Reader로...

텍스트 편집 도구로는 Notepad++를...

간단한 그래픽 작업을 위해서 Paint.NET를... 닷넷을 설치해야 한다.

압축 프로그램으로는 빵집을...

텔넷/ssh 클라이언트로는 putty를...

CD를 구울 때는 InfraRecorder...

로그를 볼 때는 TailMe...



개발을 위해서 자바(JDK는 1.5와 1.6 모두, JRE는 1.6만)와 이클립스를...

플렉스 개발을 위해서 Flex SDK도...


기타 등등으로

- 클릭 투 트윅(알약에도 비슷한 기능이 있어서 지금은 사용하지 않는다.)
- 오라클 인스턴트 클라이언트
- Microsoft Script Debugger
- Linuxnew

패키지 소프트웨어 갖추어야 하는 것들

패키지 소프트웨어가 갖추어야 하는 것들을 나름대로 정리해봤다.

PC에 설치하는 개인용 소프트웨어가 아닌 서버에 설치하는 웹 기반 소프트웨어를 대상으로 한다.


자동 업데이트

Saas가 갖는 장점들이다.

What are the Advantages of Web-Based Software?

그리고 이는 패키지 소프트웨어의 단점이 된다. 모든 것을 극복할 수 없겠지만 PC 용 소프트웨어 처럼 자동 업데이트 기능만 있다면 만족할 수 있다.

패키지가 단순할 수록 자동 업데이트 구현이 쉽다.

이 때 데이터베이스 구조와 같은 내부적인 것들도 자동으로 변경해야 한다.


데이터 백업 및 복원

데이터가 날아가면 끝이다. 백업은 필수다. 백업을 하면 복원도 덩달아 필요하다.

손쉬운 건 일자별 백업이다. 일자별 백업을 하면 오늘 발생한 데이터는 어떻게? 결국 트랜잭션 로그를 남겨야 한다.

하드 디스크는 무조건 2개 이상이 필요하다.


이벤트 처리

무슨 일이 일어나는지 기록하면 좋다. 꼭 필요한 건 아니다.


스케줄러

주기적으로 데이터 백업을 수행하려면 스케줄러가 필요하다. Crontab 수준일 필요는 없다. 그러나 Crontab 만큼 별다른 고민없이 구현 가능한 것도 없다.

예외 처리

이 코드보다는

try {
   ...
} catch (Exception e) {
   // ignore exception
}


이 코드가

try {
    ...
} catch (Exception ignore) {}

좋다.

2009-11-24

패키지와 프레임워크

예전 회사에서는 프레임워크 개발을 했다. 스프링과 같은 자바 프레임워크였고, 이를 기반으로 많은 SI 프로젝트들이 진행되었다.

지금 회사에서는 패키지 솔루션 개발을 한다. 말로만 패키지지 실제로는 SI인 솔루션들도 많지만 설치 이후에 거의 수정을 하지 않는 진정한(?) 솔루션을 개발한다.


프레임워크 개발은 익명의 다수의 개발자를 대상으로 하기 때문에 제약이 많다. 학습과 경험에 의해서 이것 저것 고쳐야 할 것들이 보여도 수정하기가 어렵다. 이 문제를 해결하려고 잡다한 시도들을 했다.

컴포넌트 기반 개발을 축으로 무조건적으로 인터페이스와 구현을 분리하고, IoC 패턴으로 컴포넌트 간의 상관 관계를 쉽게 수정할 수 있도록 했다. 결과적으로 늘어나는건 적용된 디자인 패턴 숫자와 XML 파일... 그리고 이에 비례한 복잡도...

프레임워크 개발을 할 때는 중요하지 않은 문제를 중요하다고 간주하며 살던 시기였다.


솔루션을 개발하면서는 가능한 쉽게 가는 쪽을 선택했다. 골격은 MVC 패턴을 준수했지만 설정 부분을 다 하드코딩으로 처리햇다. XML 파일을 수정하나 코드를 수정하나 차이도 없고, 컴파일러가 오류를 잡아 주기 때문에  오타 가능성이 현격하게 낮아진다는 장점도 있다.

HiveMind와 같은 IoC 컨테이너도 쓰레기 통으로 던졌다. 필요한 객체를 직접 생성해서 쓰면 되지 굳이 set 메소드를 만들고, 이를 XML 파일에 설정해야 할 이유를 찾을 수 없었다.


물론 솔루션 개발에서 이런 태도로 전환할 수 있었던 것은 내가 작성한 코드를 다른 것에 영향을 주거나 받지 않고 쉽게 수정할 수 있기 때문이다. 현재의 나와 지금 함께 개발하고 있는 개발자들, 그리고 미래의 나와 미래에 나와 함께할 개발자들 혹은 내가 떠난 후 내 일을 인수 인계받을 개발자들만을 염두하면 된다.

결론적으로 어떤 코드든 쉽게 수정할 수 있다는 것이 솔루션 개발의 장점이다.

물론 패키지 솔루션에서도 데이터베이스와 파일 구조를 변경하거나 외부 개발자들에게 공개한 오픈 API를 변경하는 것은 곤란한 일이다.

 
요약하면 유연성에 대한 태도도 바뀌었다. 전에는 고도의 기법을 통한 확장 가능성을 유연성으로 이해했다면 지금은 쉽게 이해할 수 있도록 간결하게 작성하는 것을 유연성을 확보하는 통로로 받아들이고 있다. 쉽게 이해할 수 있도록 간결하다면 수정하는 건 어렵지 않다.



2009-11-22

Ruby on rails에서 Gmail을 이용해서 메일 보내기

Ruby on rails에 기반한 애플리케이션에서 Gmail SMTP를 이용해서 메일을 보내야 했다.

구글링 결과 플러그인을 사용하면 된단다. 시도 때도 없이 플러그인(혹은 라이브러리)을 쓰는 건 피하고 싶기에 머뭇거렸다.

그런데 댓글들을 읽다가 플러그인 없이도 가능하다는 정보에 반색하며 시도해봤다.

우선, config/environment.rb 파일에 다음 내용을 추가한다.

ActionMailer::Base.smtp_settings = {
  :enable_starttls_auto => true,
  :address => "smtp.gmail.com",
  :port => 587,
  :domain => "mycompany.com",
  :authentication => :plain,
  :user_name => "username@mycompany.com",
  :password => "password"
}

enable_starttls_auto를 true로 설정해야 한다는 것을 제외하면 일반 설정과 다르지 않다.

그런데 이렇게 해도 메일이 발송되지 않았다. 에러 메시지는 없고 메일이 정상적으로 발송된 것 처럼 로그가 기록된다.


댓글들을 자세하게 읽어본 결과 루비 1.8.7 이상을 사용해야 한다고 한다. 내가 사용하는 윈도우 개발 환경은 1.8.6이었다.

루비 윈도우 인스톨 버전이 1.8.6이다.


루비 1.8.7이 설치되어 있는 리눅스 운영 환경에는 정상적으로 메일이 전송되는 것을 확인했다.


* Rails 버전은 2.3.2이다.

소프트웨어 개발이 추구해야 하는 방향

소프트웨어 개발 조직은 개발 비용을 절감해야 한다. 이는 개발 생산성을 높이는 것의 다른 이름이다. 그렇다면 다양한 요소로 이루어진 비용에서 어떤 비용을 줄이는데 초점을 맞추어야 하는가?

소프트웨어 개발 비용(total)은 이렇게 구성된다.
cost
total = costdevelop + costmaintain

일반적인 견해대로

costdevelop < costmaintain

이라면 유지보수(maintain) 비용 절감이 우순 순위를 갖는다.


유지보수 비용 구성은 아래와 같다.

costmaintain = costunderstand + costchange + costtest + costdeploy


켄트 백은 이중에서 기존 코드를 이해(understand)하는데 소요되는 비용이 가장 큰 비중을 차지한다고 주장한다.

그 주장에 동의한다면 소프트웨어 개발 비용(total)을 최소화하려면 코드 이해에 소요되는 비용을 줄어야 한다는 결론에 도다른다.



초기 개발 단계에서 유지 보수 비용을 회피하거나 최소화하는 방법으로 고도의(혹은 복잡한) 개발 기법을 이용해서 확장 가능한 소스를 개발하는 구현 전략은 유효할까?

이 시도가 갖는 첫번째 문제점은 시도 자체가 미완성으로 끝날 가능성이 높다는 점이다. 미래는 예측하기 어렵다. 불확실성을 제어할 방법이 마땅치 않다.

두번째로 일어날 일을 예측하기 어려운 점도 있지만, 예측한 일이 일어나지 않는다는 점도 문제점이다. 확장을 위해서 개발해 놓은 것들이 사용되지 않는다면 그 비용을 회수할 방법이 없다.

세번째로 돈의 시간 가치를 생각했을 때 비용 발생 시점을 현재보다는 미래로 연기해야 한다는 재무적인 측면에서의 문제점도 있다.




* 이 글은 켄트 벡이 쓴 구현 패턴(Implementation Patterns)을 인용하고 해석한 것이다. 그 중에서도 4장 Motivation을 다룬다.

2009-11-09

자바 2D와 CPU 사용률

자바 2D로 액티브한 프로그램을 작성한 경우에 CPU 사용률을 몇 가지 옵션으로 최적화할 수 있다.

특히 하드웨어 가속 기능을 사용하는 경우에 역설적으로 더 느려지는 현상도 관찰된다. 하드웨어 가속을 OFF할 수 없는 환경에서 다음 옵션으로 자바만을 제어할 수 있다.

-Dsun.java2d.d3d=false

이렇게 설정하면 Direct3D를 사용하지 않게 되면서 하드웨어 가속 기능을 OFF한 것과 동일한 CPU 사용률을 보여준다.

* Direct3D 드라이버를 업데이트하면 하드웨어 가속기를 사용하면서도 성능 향상을 얻을 수도 있다.


또한 다음과 같이 설정하면 (Swing Back Buffer에 대한 DirectDraw와 Direct3D를 사용하지 않도록 설정)

-Dsun.java2d.ddoffscreen=false

CPU 사용률을 더 줄일 수 있다.


다음 옵션은 DirectDraw와 Direct3D를 아예 사용하지 않도록 하는 것인데 CPU 사용률 측면에서는 이전과 큰 차이가 없다.

-Dsun.java2d.noddraw=true (false가 아닌 true)


코딩 방법과 하드웨어에 따라서 상이한 결과가 나타날 수 있다.



[참고 자료]

http://java.sun.com/javase/6/docs/technotes/guides/2d/flags.htm

2009-10-27

USB로 우분투 설치하기

어제 DVD/CD 플레이어가 없는 컴퓨터에 우분투를 USB로 설치했다.

우선 우분투(8.04 LTS 서버/32 비트)를 여기에서 ISO 파일로 다운로드한다.

그리고 여기에서 UNetbootin, Universal Netboot Installer(unetbootin-windows-372.exe)를 다운로드한다.

* 윈도우즈 XP 기준


USB를 넣고 UNetbootin를 실행하여 다운로드 받은 우분투 ISO 파일로 설치 파일을 작성한다.


그리고 우분투를 설치할 컴퓨터에 USB를 넣고 부팅 디스크로 USB를 지정해주면 된다.

ant와 씨름하기

회사에서 사용하는 빌드 서버 디스크가 고장나면서 빌드를 다시 구성해야 했는데 처음 구성했을 때 문서로 잘 정리해 놓지 않아서 고생했다.

우선 우분투 서버 8.0.4를 다시 설치했고... 여러 가지 작업을 했는데 그 중에서 ant와 관련한 부분을 정리해봤다.

apt로 ant로 설치했는데 ant 버전은 1.7.0이었다.

sudo apt-get install sun-java5-jdk
sudo apt-get install ant


ftp 태스크를 사용하려면 ANT_LIB(/usr/shared/ant/lib) 디렉토리에 다음 파일들을 추가한다.

ant-commons-net.jar
commons-net-1.4.1.jar

* apt로 설치하면 ant-commons-net.jar 파일이 포함되지 않기 때문에 별도로 설치해주어야 한다.


MailLogger를 사용했는데 이게 문제가 많았다. MailLogger를 사용하려면 다음과 같이 ant를 실행해야 한다.

ant -logger org.apache.tools.ant.listener.MailLogger -DMailLogger.properties.file=mail.properties


일단 어떤 문제가 있는지를 잘 알 수가 없었다. -v 옵션과 -diagnostics 옵션을 사용해야 그나마 의미있는 정보를 알 수가 있었다.

그리고 메일 발송을 위해서 다음 파일들을 추가해야 했다.

mail.jar
activation.jar

이 파일들을 먼저 설치하지 않으면 무슨 문제가 있는지 알 수가 없다. 이 파일들이 필요하다는 메시지가 없었다.

설정을 별도의 파일(mail.properties)에 해야 했다. 그렇지 않으면 예외가 발생한다.

# mail.properties
# gmail을 사용

MailLogger.mailhost=smtp.gmail.com
MailLogger.port=465
MailLogger.ssl=true
MailLogger.user=mailer@company.com
MailLogger.password=password
MailLogger.from=mailer@company.com
MailLogger.success.to=to@company.com
MailLogger.success.subject=Build Success
MailLogger.failure.to=admin@company.com,builder@company.com
MailLogger.failure.subject=Build Failure


이후에도 java.lang.NullPointerException이 발생한다. 예외가 발생하는 소스 코드 위치는 다음과 같다.

org.apache.tools.ant.taskdefs.email.MimeMailer.java 206번째 줄

for (Iterator iter = headers.iterator(); iter.hasNext();) {
  Header h = (Header) iter.next();
  msg.addHeader(h.getName(), h.getValue());
}

headers는 상위 클래스(Mailer.java)에 있는 멤버 필드인데 null로 초기화가되어 있다.

그래서 이 코드를 다음과 같이 수정해야 한다.

if (headers != null) {
  for (Iterator iter = headers.iterator(); iter.hasNext();) {
    Header h = (Header) iter.next();
    msg.addHeader(h.getName(), h.getValue());
  }
}

그리고 이 코드만을 컴파일하여 _ant.jar 파일로 묶어서 ANT_LIB 디렉토리에 복사한다.

* 이클리스에서 사용하는 ant 버전은 1.7.1이었는데 여기서도 동일한 예외가 발생했다.



2009-10-26

우분투 명령어

서버 종료

sudo init 0


서버 재부팅

sudo init 6


2009-10-14

Debt Metaphor

경제 위기 탓일까. 소프트웨어 개발 분야에서도 빚이 응용되는 것이...

Martin Fowler가 쓴 TechnicalDebtQuadrant을 재미있게 읽었다.


코드를 잘 짜는 것보다는 구현 자체가 미덕인 경우가 있다. 이 것이 개발자 의식 속에서 이루어진 선택인지 아니면 타성에 젖어 일어나는 일상적인 현상인지에 대해서 관찰하는 것이 필요하다.

책임감 있는 개발자라면 언젠가는 돌아봐야 하기 때문에...

2009-10-11

자바 1.4 이상에서 스택트레이스 기록

자바 1.4 이상에서 임의의 위치에서 스택트레이스를 기록하는 간단한 방법이다.

StringBuffer stacktrace = new StringBuffer();
StackTraceElement[] stackTrace = new Exception().getStackTrace();
for (int i = 0; i < stackTrace.length; i++) {
  stacktrace.append(stackTrace[i].toString() + "\n");
}

주로 디버그 목적으로 유용하게 사용할 수 있다.

2009-09-30

톰켓 6.0 + JSTL

톰켓 6.0에서 JSTL 1.2를 사용하는 방법이다.

우선 JSTL 라이브러리를 여기에서 다운로드한 후에, WEB-INF/lib 디렉토리에 복사한다.

  • jstl-api-1.2.jar
  • jstl-impl-1.2.jar

그리고 JSTL을 사용하는 JSP 앞쪽에 다음 내용을 추가한다.

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

* sql, xml, fmt 태그 등도 비슷한 방법으로 사용한다.


그런데 JSP가 임의의 클래스를 상속하는 경우에는 c:forEach 태그가 정상적으로 동작하지 않을 수 있다. 이를 해결하려면 JSP 상위 클래스에서 ServletConfig를 올바르게 처리해야 한다.

package com.dimdol.web;

import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.HttpJspPage;

public abstract class BasePage implements HttpJspPage {

  private ServletConfig config;

  public ServletConfig getServletConfig() {
    return config;
  }

  public String getServletInfo() {
    return config.getServletName();
  }

  public void init(ServletConfig config) throws ServletException {
    this.config = config;
    jspInit();
  }

  public void jspInit() {
    _jspInit();
  }

  public void _jspInit() {
    // Implemented by Web Container
  }

  public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    _jspService(httpRequest, httpResponse);
  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response) throws ServletException,
      IOException {
    // Implemented by Web Container
  }

  public void destroy() {
    jspDestroy();
  }

  public void jspDestroy() {
    // Implemented by Web Container
  }

}



2009-09-25

JSP 태그 라이브러리

JSP 태그 라이브러리에 대한 생각들이다.


JSTL은 좋지만 지양하고 싶다. JSTL을 5년 이상 사용했는데 지금 생각하면 사용하지 않는 것이 좋은 것 같다. 그냥 자바 구분을 이용하는게 더 효과적인것 같다. EL 정도만 사용하면 될 듯...


SimpleTag를 사용하면 Body에 JSP를 사용할 수 없다.ㅠㅠ


속성에서 <%=%> 사용이 혼란스럽다.

<attribute>
  <name>name</name>
  <required>false</required>
  <rtexprvalue>true</rtexprvalue>
</attribute>

다음은 가능하다.

name="<%= customer.getName() %>"

다음도 가능하다.

name="${customer.name}"

그러나 다음은 불가능하다.

name="Customer_<%= customer.getName() %>"



2009-09-24

Prototype 라이브러리

Prototype 라이브러리 사용과 관련한 주의사항이다.


AJAX를 이용해서 화면 특정 영역을 업데이트하는 경우가 있다.

new Ajax.Updater("update_area", "/update.html");

이 때 화면에 포함된 자바 스크립트를 수행하려면 다음과 같이 한다.

new Ajax.Updater("update_area", "/update.html", {evalScripts: true});


그런데 이 경우 IE 6.0에서는 자바 스크립트를 주석처리하면 안된다.

아래 같이 하면 자바 스크립트가 실행되지 않는다.

<script type="text/javascript"><!--

--></script>

아래 같이 해야 자바 스크립트가 실행된다.

<script type="text/javascript">

</script>


또한 함수를 선언할 때는 다음과 같이 해야 한다.

<script type="text/javascript">
  processRequest = function() {
    ...
  }
</script>

이 때 다음과 같이 var를 붙여서도 안된다.

  var processRequest = function() {
   ...
  }

2009-09-16

자바 - Reflection으로 static 필드 정보 java.util.Map에 담기

자바에서 Reflection을 이용해서 특정 클래스의 static 필드 정보를 java.util.Map에 담는 방법이다.


다음은 java.sql.Types 클래스에 포함된 int 유형의 데이터베이스 칼럼 타입 정보를 처리하는 예제이다.

Map<String, Integer> typeMapping = new HashMap<String, Integer>();

Field[] fields = java.sql.Types.class.getDeclaredFields();
for (Field field : fields) {
  if (Modifier.isStatic(field.getModifiers()) && field.getType() == int.class) {
    typeMapping.put(field.getName(), (Integer) field.get(null));
  }
}


2009-09-14

톰켓 6.0 + JPA

톰켓 6.0에서 JPA를 사용하는 방법이다.

우선 톰켓 6.0 깨끗하게 하기를 참고하여 톰켓을 설치하고, 톰켓 6.0 데이터소스 설정를 참고하여 아파치 더비를 데이터소스로 설정한다.


JPA 구현부로는 오라클이 제공하는 TopLink Essentials을 다운로드한다.

다운로드한 glassfish-persistence-installer-v2-b41.jar 파일이 있는 디렉토리로 이동하여 다음 명령어를 실행한다. 자바가 사용할 수 있어야 한다.

java -jar glassfish-persistence-installer-v2-b41.jar


아래 화면에서 스크롤을 맨 아래까지 내린 다음에 Accept 버튼을 누른다.



압축이 풀리면서 디렉토리가 생기는데 다음 2 파일을 webapps/ROOT/WEB-INF/lib 디렉토리에 복사한다.

  • toplink-essentials.jar
  • toplink-essentials-agent.jar

그리고 webapps/ROOT/WEB-INF/classes/META-INF/persistence.xml 파일을 작성하고 다음 내용을 기술한다.

<?xml version="1.0" encoding="UTF-8"?>

<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="ExampleUnit" transaction-type="JTA">
    <provider>oracle.toplink.essentials.PersistenceProvider</provider>
    <jta-data-source>java:comp/env/jdbc/Base</jta-data-source>
    <class>com.dimdol.example.model.Document</class>
    <class>com.dimdol.example.model.User</class>
    <class>com.dimdol.example.model.Element</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>
      <!--
      <property name="toplink.ddl-generation" value="create-tables" />
      -->
      <property name="toplink.session.customizer" value="com.dimdol.example.util.ToplinkSessionCustomizer" />
    </properties>
  </persistence-unit>
</persistence>

  • persistence-unit 태그 name 속성에 모델 그룹을 지칭하는 적절한 이름(ExampleUnit)을 설정한다.
  • provider 태그에 JPA 구현 클래스 이름(oracle.toplink.essentials.PersistenceProvider)을 설정한다.
  • jta-data-source 태그에 데이터소스 JNDI 이름(java:comp/env/jdbc/Base)을 설정한다.
  • class 태그로 모델 클래스들을 설정한다.

property 태그로 toplink.session.customizer 이름에 다음 클래스를 설정한다. 이렇게 해야 하는 정확한 이유는 알수 없지만 이렇게 하지 않으면 정상적으로 동작하지 않는다.

package com.dimdol.example.util;

import oracle.toplink.essentials.jndi.JNDIConnector;
import oracle.toplink.essentials.sessions.Session;
import oracle.toplink.essentials.tools.sessionconfiguration.SessionCustomizer;

public class ToplinkSessionCustomizer implements SessionCustomizer {

    public void customize(Session session) throws Exception {
        JNDIConnector connector = (JNDIConnector) session.getLogin().getConnector();
        connector.setLookupType(JNDIConnector.STRING_LOOKUP);
    }

}


아파치 더비 - 테이블 스페이스 정리

아파치 더비를 사용할 때 테이블 스페이스를 정리하는 방법이다. 이를 Reorganization이라고 한다.


테이블 스페이스와 관려한 정보를 포함한 테이블 목록은 다음 SQL로 조회한다.

  SELECT TABLENAME,
         (SELECT SUM(NUMALLOCATEDPAGES * PAGESIZE)
            FROM new org.apache.derby.diag.SpaceTable('DB', T.TABLENAME) X),
         (SELECT SUM(ESTIMSPACESAVING)
            FROM new org.apache.derby.diag.SpaceTable('DB', T.TABLENAME) X)
    FROM SYS.SYSTABLES T
ORDER BY TABLENAME

SELECT 절 두번째 칼럼은 해당 테이블이 사용하고 있는 테이블 스페이스 크기를 의미하고, 세번째 칼럼은 테이블 스페이스 정리를 통해서 확보할 수 있는 크기를 의미한다.


실제 테이블 스페이스 정리는 Stored Procedure로 처리한다.

CALL SYSCS_UTIL.SYSCS_COMPRESS_TABLE('DB', ?, 1)

바인딩 파라미터에 테이블 이름을 사용한다.

이를 수행하는 자바 코드는 다음과 같다.

String tableName = ...
if (tableName != null) {
  Connection con = null;
  CallableStatement cstat = null;
  try {
    con = getConnection();
    cstat = con.prepareCall("CALL SYSCS_UTIL.SYSCS_COMPRESS_TABLE('DB', ?, 1)");
    cstat.setString(1, tableName);
    cstat.execute();
  } catch (SQLException e) {
    throw new RuntimeException(e);
  } finally {
    closeResource(con, cstat);
  }
}


* DB 대신에 데이터베이스 이름을 사용한다.

톰켓 6.0 JSESSIONID 충돌

동일 도메인이나 IP에 포트 번호를 달리해서 여러 개의 자바 애플리케이션 서버를 운영하면 세션 유지를 위해 사용하는 JSESSIONID 쿠키가 충돌한다.

예를 들어 한 사용자가 동일 브라우저에서 이 애플리케이션들을 동시에 사용하면 계속해서 세션이 상실되는 현상이 발생한다.


웹로직같은 경우에는 JSESSIONID 대신에 다른 이름을 사용하도록 변경할 수 있지만 톰켓 5.5에서는 이를 지원하지 않았다. 문제 해결을 위해서 구글링하다가 톰켓 개발자가 쓴 글을 보고 톰켓 코드를 수정해서 사용한 적이 있다.


그런데 톰켓 6.0.20 버전에서는 그 내용이 그대로 반영되어서 코드 수정없이 JSESSIONID 대신에 다른 이름을 사용할 수 있다. (정확하게 톰켓 6.0 어떤 버전부터인지는 모르겠다)


다음 자바 시스템 환경 변수로 JSESSIONID를 수정한다.

-Dorg.apache.catalina.SESSION_COOKIE_NAME=JSESSIONID
-Dorg.apache.catalina.SESSION_PARAMETER_NAME=jsessionid
-Dorg.apache.catalina.authenticator.Constants.SSO_SESSION_COOKIE_NAME=JSESSIONIDSSO

  • 첫번째는 JSESSIONID 쿠키 이름이다.
  • 두번째는 URL Rewriting을 할 때 사용하는 파라미터 이름으로 톰켓에서는 JSESSIONID에 대한 소문자를 사용한다.
  • 세번째는 SSO 처리와 관련된 것으로 보이는데 사용해본 적이 없어서 자세히는 모르겠다.

따라서 위의 값에 대해서 JSESSIONID 대신에 다른 걸 사용하면 세션 충돌을 방지할 수 있다.



동일 도메인에서 서로 다른 자바 애플리케이션 서버가 같은 HTTP 포트 번호를 사용할 수는 없다. 그래서 JSESSIONID + HTTP 포트 번호 조합을 사용하는 것이 가장 바람직하다.


먼저 conf/server.xml 파일을 다음과 같이 수정한다.

<?xml version="1.0" encoding="UTF-8"?>

<Server port="${http.shutdown.port}" shutdown="SHUTDOWN">

  <Service name="Catalina">

    <Connector port="${http.startup.port}" protocol="HTTP/1.1" connectionTimeout="20000"
      redirectPort="8443" URIEncoding="UTF-8"/>

    <!-- 나머지 부분 생략 -->

톰켓 설정 파일에 ${}로 설정하면 자동으로 자바 환경 변수로 치환된다.

  • http.startup.port - HTTP 시작 포트
  • http.shutdown.port - HTTP 종료를 위한 포트

윈도우에서는 bin/catalina.bat 파일 앞부분에 다음 내용을 추가한다.

if "%STARTUP_PORT%" == "" SET STARTUP_PORT=8080
if "%SHUTDOWN_PORT%" == "" SET SHUTDOWN_PORT=8005

set JAVA_OPTS=%JAVA_OPTS%
-Xmx512m
set JAVA_OPTS=%JAVA_OPTS% -Dhttp.startup.port=%STARTUP_PORT%
set JAVA_OPTS=%JAVA_OPTS% -Dhttp.shutdown.port=%SHUTDOWN_PORT%
set JAVA_OPTS=%JAVA_OPTS% -Dorg.apache.catalina.SESSION_COOKIE_NAME=JSESSIONID%STARTUP_PORT%
set JAVA_OPTS=%JAVA_OPTS% -Dorg.apache.catalina.SESSION_PARAMETER_NAME=jsessionid%STARTUP_PORT%
set JAVA_OPTS=%JAVA_OPTS% -Dorg.apache.catalina.authenticator.Constants.SSO_SESSION_COOKIE_NAME=JSESSIONIDSSO%STARTUP_PORT%


* 띄어쓰기 문제로 맨 마지막 줄이 잘려보인다.  =JSESSIONIDSSO%STARTUP_PORT%이 잘려 보이는 것이다.


유닉스/리눅스에서는 bin/catalina.sh 파일 앞부분에 다음 내용을 추가한다.

STARTUP_PORT="${STARTUP_PORT}"
SHUTDOWN_PORT="${SHUTDOWN_PORT}"

if [ "${STARTUP_PORT}" = "" ]
then
    STARTUP_PORT="7900"
fi

if [ "${SHUTDOWN_PORT}" = "" ]
then
    SHUTDOWN_PORT="7999"
fi

JAVA_OPTS="$JAVA_OPTS -Xmx512m"
JAVA_OPTS="$JAVA_OPTS -Dhttp.startup.port=${STARTUP_PORT}"
JAVA_OPTS="$JAVA_OPTS -Dhttp.shutdown.port=${SHUTDOWN_PORT}"
JAVA_OPTS="$JAVA_OPTS -Dorg.apache.catalina.SESSION_COOKIE_NAME=JSESSIONID${STARTUP_PORT}"
JAVA_OPTS="$JAVA_OPTS -Dorg.apache.catalina.SESSION_PARAMETER_NAME=jsessionid${STARTUP_PORT}"
JAVA_OPTS="$JAVA_OPTS -Dorg.apache.catalina.authenticator.Constants.SSO_SESSION_COOKIE_NAME=JSESSIONIDSSO${STARTUP_PORT}"

* 띄어쓰기가 없어서 맨 마지막 줄이 잘려보인다.  =JSESSIONIDSSO%STARTUP_PORT%이 잘려 보이는 것이다.


이렇게 설정하면 HTTP 포트 번호가 8080일때 JSESSIONID8080이 세션 유지를 위한 쿠키 이름으로 사용된다.

톰켓 6.0 데이터소스 설정

톰켓 6.0에 데이터소스를 설정하는 방법이다. 우선 톰켓 6.0 깨끗하게 하기를 참조한다.


conf/server.xml 파일에 데이터소스를 설정하고 JDBC 드라이버는 lib 디렉토리에 복사해야 한다.


아파치 더비

<Context ...>
  <Resource name="jdbc/Base" auth="Container"
    type="javax.sql.DataSource" maxActive="100" maxIdle="30"
    maxWait="10000" username="username" password="password"
    driverClassName="org.apache.derby.jdbc.EmbeddedDriver"
    url="jdbc:derby:db;create=true" />
</Context>

JDBC 드라이버 : derby.jar

* 외부에서도 아파치 더비에 접근하려면 derbynet.jar 파일도 복사해야 한다.


오라클


<Context ...>
  <Resource name="jdbc/Base" auth="Container"
    type="javax.sql.DataSource" maxActive="100" maxIdle="30"
    maxWait="10000" username="username" password="password"
    driverClassName="oracle.jdbc.driver.OracleDriver"
    url="jdbc:oracle:thin:@127.0.0.1:1521:db" />
</Context>

JDBC 드라이버 : ojdbc14.jar


DB2

<Context ...>
  <Resource name="jdbc/Base" auth="Container"
   type="javax.sql.DataSource" maxActive="100" maxIdle="30"
   maxWait="10000" username="username" password="password"
   driverClassName="com.ibm.db2.jcc.DB2Driver"
   url="jdbc:db2://127.0.0.1:50000/db" />
</Context>

JDBC 드라이버 : db2jcc.jar, db2jcc_license_cu.jar


MS SQL 2005


<Context ...>
  <Resource name="jdbc/Base" auth="Container"
   type="javax.sql.DataSource" maxActive="100" maxIdle="30"
   maxWait="10000" username="username" password="password"
   driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
   url="jdbc:sqlserver://127.0.0.1:1433;databaseName=db" />
</Context>

JDBC 드라이버 : 자바 1.6을 사용하면 sqljdbc4.jar, 자바 1.5를 사용하면 sqljdbc.jar


web.xml 파일 수정

이 설정이 끝나면 web.xml 파일에 다음 내용을 추가한다.

<resource-ref>
  <description>Base DB Connection</description>
  <res-ref-name>jdbc/Base</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
  <res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

* jsp-config 태그 다음에 추가한다. 일반적으로 거의 마지막에^^

톰켓 6.0 깨끗하게 하기

톰켓 6.0을 자주 사용하는데 불필요한 파일과 설정들이 많이 있다. 그래서 항상 다음과 같이 정리한다.


1. 다운로드

exe 파일이 아닌 zip이나 tar.gz 파일을 다운받은 후에 톰켓을 운영할 디렉토리에 압축을 푼다.


2. 지우기

다음 파일 혹은 디렉토리를 지운다.
  • conf/context.xml
  • conf/tomcat-users.xml
  • webapps/docs 디렉토리
  • webapps/examples 디렉토리
  • webapps/host-manager 디렉토리
  • webapps/manager 디렉토리
  • webapps/ROOT 디렉토리에서 WEB-INF/web.xml 파일을 제외한 나머지 파일을 모두 삭제한다.


3. conf/server.xml 파일 정리

취향이겠지만 난 주석을 좋아하지 않고, 애플리케이션 개발에 당장 필요하지 않은 설정은 일단 지우고 필요하면 다시 설정하는 쪽이다.

conf/server.xml 파일은 다음과 같이 정리한다.

<?xml version="1.0" encoding="UTF-8"?>

<Server port="8005" shutdown="SHUTDOWN">

  <Service name="Catalina">

    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
      redirectPort="8443" URIEncoding="UTF-8" />

    <Engine name="Catalina" defaultHost="localhost">
      <Host name="localhost" appBase="webapps"
            unpackWARs="true" autoDeploy="false"
            xmlValidation="false" xmlNamespaceAware="false">
        <Context docBase="${catalina.base}/webapps/ROOT" path="" reloadable="true" backgroundProcessorDelay="3">
        </Context>
      </Host>
    </Engine>

  </Service>
</Server>

  • 한글 인코딩 처리를 위해서 Connector 태그에 URIEncoding 속성을 지정했다
  • 톰켓 문서에서는 Context 설정은 별도 파일에 하는 것을 권장하지만 한두개의 애플리케이션을 배치할 때는 굳이 그럴 필요성을 느끼지 못한다. ROOT 디렉토리를 명시적으로 설정하였다. 개발시에는 Context 태그의 reloadable 속성을 true로 설정한다.


4. 로그 설정 정리

전에는 log4j를 선호했지만 지금은 자바 라이브러리를 줄이는 쪽에 가중치를 준다. 그래서 자바 표준 라이브러리에 있는 로그 모듈을 사용한다. 톰켓도 기본 설정이 그렇다.

conf/logging.properties 파일로 로그 설정을 하는데 관리 애플리케이션을 지웠기 때문에 이 파일에 그 내용을 반영해야 한다. 그렇지 않으면 무의미한 로그 파일이 만들어진다.

handlers = 1catalina.org.apache.juli.FileHandler, 2localhost.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler

.handlers = 1catalina.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler

1catalina.org.apache.juli.FileHandler.level = FINE
1catalina.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.FileHandler.prefix = catalina.

2localhost.org.apache.juli.FileHandler.level = FINE
2localhost.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
2localhost.org.apache.juli.FileHandler.prefix = localhost.

java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.FileHandler

2009-09-13

[책] 웹 이후의 세계

김국현 씨가 쓴 웹 이후의 세계를 읽었다.

SasS에 대한 관심 때문에 우연하게 서점에서 접한 이 책을 구매했다.


현실계, 이상계, 환상계라는 세계 구분은 좀 뜬금없게 느껴지고, 그 맥락 속에서 서술되는 이야기들이 별로 와닿지 않는다. 아마도 뻔한 이야기라는 느낌이거나 너무 무게를 잡았다는 인상 때문일거다.

아마도 이 책을 구매한 이유가 정보에 대한 빠른 이해를 기대하는 것이었기 때문일 게다.


그래도 정보에 대한 빠른 이해에는 큰 도움이 되었다. 이 바닥에서 일하지만 IT 트랜드에 대한 무지함 혹은 무관심이 야기한 이해 부족을 조금이나마 해소할 수 있었으니깐...

  • SasS와 클라우드의 차이를 이해했다. 난 클라우드를 SasS의 또다른 이름 정도로 치부했는데...
  • 가상화는 워낙 무지한 분야라 읽기 전이나 후나 큰 차이는 없지만... 각론으로 들어가면 이견이 많을 것 같다. 물론 무지함이 관심없음에 기인하기 때문에 각론으로 파들어갈 일은 없겠지만^^
  • 그린 IT는 MB 때문에 무조건 정이 안간다. 궁극적으로 혐오가 망가트리는 건 대상이 아니라 나 자신이라는 게 다시 증명되었다.
  • RIA는 아마도 내 업무와 관련성이 많은 분야인데 플래시나 실버라이트가 가지고 있지 못한 장점을 자바가 가지고 있다고 생각한다. 플래시나 실버라이트로 제니퍼를 구현할 수 있었을까? 닷넷과 결합한 버전업되었다는 실버라이트는 모르겠지만 플래시로는 어림도 없다. 물론 자바 FX는 나도 모른다.
  • 디지털 미디어나 모바일과 관련한 논의가 어떤 이슈를 중심으로 일어나고 있고, 어떤 문제점들이 있으며, 대안들에는 어떤 것들이 있을까를 잘 이해할 수 있었다. 핸드폰을 사용하기 시작한게 3년도 안된 나로서는 도움이 많이 되었다.
  • 제 3장 웹 주의 선언도 각론에서는 이견이 있겠지만 큰 맥락은 100% 동의한다. 하지만 나의 독서 필요성에서는 조금 벗어나 있다.


결론적으로 뜬금없어도 좋은 책이다.

2009-09-10

직각 삼각형의 각도 구하기

직각 삼각형 각도는 다음과 같이 구한다.


tan(각도 b)  = 높이(B) / 밑변(A)

각도 b = inverse tan(높이 / 밑변)

각도 a = 90 - 각도 b


자바에서는 다음과 같이 처리한다.

double angleInRadians = Math.atan(높이 / 밑변);

double degreeA = Math.toDegrees(angleInRadians);

double degreeB = 90 - degreeA;

2009-09-09

자바 HTTP POST 요청

자바(1.4 이상)로 HTTP POST 요청을 하는 방법이다.

먼저 URLConnection 객체를 생성한다.

URL url = new URL("http://example.dimdol.com/example.jsp");
URLConnection con = url.openConnection();

URLConnection 객체 setRequestProperty 메소드로 HTTP 헤더 정보를 설정한다.

con.setRequestProperty("Accept-Language",  "ko-kr,ko;q=0.8,en-us;q=0.5,en;q=0.3");

URLConnection 객체 setDoOutput 메소드로 POST 요청임을 설정한다.

con.setDoOutput(true);


요청 파라미터는 name=value 패턴으로 지정하고 두 개 이상은 &을 사용해서 연결한다.

String parameter = URLEncoder.encode("name", "UTF-8") + "=" URLEncoder.encode("dimdol", "UTF-8");

parameter += "&" + URLEncoder.encode("age", "UTF-8") + "=" URLEncoder.encode("35", "UTF-8");

HTTP 요청을 한다.

OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream());
wr.write(parameter);
wr.flush();  // 꼭 flush를 호출해야 한다.


응답 결과가 텍스트인 경우에는 다음과 같이 처리한다.

BufferedReader rd = null;

rd = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8"));
String line = null;
while ((line = rd.readLine()) != null) {
  // 로직 처리
}

응답 결과가 XML인 경우에는 다음과 같이 처리한다.

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document document = builder.parse(con.getInputStream());

// 이하 document를 이용해서 로직 처리

응답 헤더는 다음과 같이 확인할 수 있다.

String cookie = con.getHeaderField("Set-Cookie");


전체를 try/finally로 묶고 finally 절에서 OutputStreamWriter과 BufferedReader를 close한다.



2009-09-06

ALM 애플리케이션 목록

ALM(Application Lifecycle Management) 애플리케이션 목록이다.


YouTrack

IntelliJ IDEA로 유명한 JetBrains에서 만든 버그 이슈 트래커
아직은 베타 상태
키보드 단축키를 통한 빠른 사용을 장점으로 함

codeBeamer

기능적인 완성도는 모르겠지만 ALM 전체를 커버

tinyPM

Agile에 기반한 프로젝트 관리 도구

파이어폭스 3.5 느리다

파이어폭스 3.5 초기 부팅 과정이 느리다. 내 컴퓨터에서만 그런지는 모르겠지만 느리다.

느리다는 건 다른 웹 브라우저와 비교해도 파이어폭스 이전 버전하고 비교해도 그렇다.


프로그램 시작 자체도 느리지만, 즐겨 찾기 대신 Ctr + L 단축키로 URL을 직접 입력하는 내 사용 패턴이 더욱 답답하게 한다. 주소 입력 창에 URL을 입력할 때의 그 버벅임이란...

마이크로소프트 익스프레션

오늘 오후에 종각 영풍 문고에서 시간을 보내다가 두꺼움에 혹해서 크리에이티브 웹이라는 잡지를 구매했다.

서점을 지나가면서 표지를 본적이야 있겠지만 실제 내용을 보기는 처음이었다. 우리나라에 이렇게 많은 웹 에이전시 회사가 있다는 사실을 알았다.


그러다가 뒷면 광고에 나온 마이크로소프트 REMOIX09 행사 내용에 포함되어 있는 마이크로소프트 익스프레션이라는 솔루션에 눈길이 갔다.


그 중에 SketchFlow가 있었는데 일종의 와이어프레임 솔루션인 것 같다.

2009-09-02

홈페이지 메인 화면

http://www.gracesmith.co.uk





이런 디자인이 사람에게 주는 의미가 다양하겠지만 나는 좋다^^


http://vaadin.com


텍스트가 얼마나 중요한 요소인지를 보여준다.

2009-09-01

와이어프레임

구글 리더로 구독하는 Smashing Magazine에서 와이어프레임(Wireframes)과 관련한 35 Excellent Wireframing Resources을 보았다.


처음 알았는데 화면 스케치를 와이어프레임이라고 하는 것 같다. 혹은 프로토타입이라고 하거나...


파워포인트나 모델링 도구와는 달리 스케치 느낌을 살린 화면 스케치를 프로그램으로 구현할 수 있을까 생각한 적이 있는데 이를 구현한 제품(iPlotz)도 볼 수 있었다.

종이로 그린 후에 디지털 카메라나 스케너로 스캔하는 것과 이런 애플리케이션을 사용하는 것 중에서 어느 것이 더 생산적일까?



요구사항 관리 애플리케이션이 텍스트 편집기와 와이어프레임 편집기를 함께 제공하면 좋을 듯 하다. 여기디가 유스케이스 모델링 정도까지 추가해서...


와이어프레임 도구

Balsamiq Mockups

인상적임

Mockup Screens


관련 글

MockupScreens Review

2009-08-30

display: block과 inline

아래 태그는

<div>div1</div>
<div>dIv2</div>
<div>div3</div>

웹 브라우저에 아래와 같이 표시된다.
반면에 아래 태그는

<span>span1</span>
<span>span2</span>
<span>span3</span>

웹 브라우저에 아래와 같이 표시된다.

이 둘 사이에 나타나는 가장 큰 차이는 줄 바꿈 여부이다. div 태그는 줄바꿈되고, span 태그는 줄바꿈되지 않는다.

div 태그가 줄바꿈되는 이유는 CSS display 속성 기본 값이 block이기 때문이고, span 태그가 줄바꿈되지 않는 이유는 CSS display 속성 기본 값이 inline이기 때문이다.


아래처럼 반대로 설정하면

div {
  display: inline;
}

span {
  display: block;
}

웹 브라우저에 아래와 같이 표시된다.

block과 inline의 차이점은 줄바꿈만은 아니다.
  • block으로 설정하면 줄바꿈 되고, inline으로 설정하면 줄바꿈이 되지 않는다.
  • block으로 설정하면 상/하 margin과 padding 속성을 사용할 수 있지만, inline으로 설정하면 상/하 margin과 padding 속성을 사용할 수 없다.
  • block으로 설정하면 width, height 속성을 사용할 수 있지만, inline으로 설정하면 width, height 속성을 사용할 수 없다.

2009-08-28

드래그 앤 드랍

드래그 앤 드랍을 자바 스크립트로 구현하는 예제이다.

<div>드래그 앤 드랍</div>

위의 div 태그를 드래그 앤 드랍하려면 우선 onmousedown 속성을 설정한다.

<div onmousedown="drag(this, event);">드래그 앤 드랍</div>

사용자가 div 태그를 마우스로 누른 순간에 onmousedown 속성으로 선언한 자바 스크립트 함수(drag - 아래에서 설명)가 수행된다.

drag 함수는 다음과 같다.

/*
drag 함수의 첫번째 파라미터(element)는 드래그할 대상이고, 두번째 파라미터(event)는 이벤트이다.
*/


function drag(element, ev) {
}


다음은 drag 함수 구현부이다. 우선 마우스 커서를 드래그를 의미하는 것으로 변경한다.

document.body.style.cursor = "move";

/*
마우스 커서는 드래그할 대상(<div>드래그 앤 드랍</div>)이 아닌 body에 설정해야 한다. 마우스를 드래그를 하면 마우스 포인터가 대상 밖으로 벗어나는 것이 일반적이기 때문이다.
*/


그리고 document에 대해서 onmousemove 이벤트 리스너를 설정한다.

/*
onmousemove로 설정한 함수는 마우스가 움직일 때마다 반복적으로 계속 호출된다.  마우스 커서와 동일한 이유로 드래그할 대상(<div>드래그 앤 드랍</div>)이 아닌 document에 대해서 onmousemove 이벤트 리스너를 등록한다.
*/

document.onmousemove = function(ev) {
    // IE 6.0은 해당 함수 파라미터로 이벤트 객체를 전달하지 않는다.
    // 대신 window.event 변수로 전달한다.
    ev = ev || window.event;

    // prototype이 제공하는 API로 현재 마우스 커서의 위치 값을 추출한다.
    var top = Event.pointerY(ev);
    var left = Event.pointerX(ev);

    // 드래그할 대상을 움직이게 하려면 onmousemove 이벤트 리스너가 호출될 때마다
    // CSS 속성으로 위치 값을 변경한다.
    // position 속성을 absolute로 설정하고 left와 top 속성으로 위치를 지정한다.
    element.style.position = "absolute";
   element.style.left = left + "px";
   element.style.top = top + "px";
    // left, top 속성을 설정할 때 px(단위)를 명기해야 한다.

    // prototype이 제공하는 API로 이벤트를 종료하고 false를 반환한다. 그렇지 않으면
    // 주변에 있는 텍스트를 선택하는 것과 같은 현상이 일어난다.
    Event.stop(ev);
    return false;
  };

마우스 커서 위치 값 추출에 Prototype 라이브러리를 사용했다. 자세한 사항은 다음을 참조한다.


그리고 document에 대해서 onmouseup 이벤트 리스너를 설정한다.

/*
onmouseup으로 설정한 함수는 마우스 누른 것을 해제할 때(드랍) 호출된다. 마우스 커서와 동일한 이유로 드래그할 대상(<div>드래그 앤 드랍</div>)이 아닌 document에 대해서 onmousemove 이벤트 리스너를 등록한다.
*/

document.onmouseup = function(ev) {
    ev = ev || window.event;
    // 마우스 커서를 초기화한다.
    document.body.style.cursor = "auto";
   
   // 여기에서 필요한 작업을 한다. 이는 드래그 앤 드랍 성격에 따라서 달라진다.
   // 예를 들어, AJAX를 이용해서 현재 위치 값을 서버에 저장할 수도 있다.

    // 드래그앤 드랍과 관련한 이벤트 리스너를 초기화한다.
    document.onmousemove = null;
    document.onmouseup = null;
    Event.stop(ev);
    return false;
  };


최종적인 drag 함수는 다음과 같다.

function drag(element, ev) {
  document.body.style.cursor = "move";

  document.onmousemove = function(ev) {
    ev = ev || window.event;
    var top = Event.pointerY(ev);
    var left = Event.pointerX(ev);
    element.style.position = "absolute";
    element.style.left = left + "px";
    element.style.top = top + "px";
    Event.stop(ev);
    return false;
  };

  document.onmouseup = function(ev) {
    ev = ev || window.event;
    document.body.style.cursor = "auto";

    // 필요한 작업 수행

    document.onmousemove = null;
    document.onmouseup = null;
    Event.stop(ev);
    return false;
  };
  Event.stop(ev);
  return false;
}


2009-08-27

간단한 사용자 가입 폼 - Mint/Basecamp

Mint Account Center 화면이다.



사용자는 최소한의 정보만을 입력하면 된다.

  • 아이디 대신 이메일 주소를 사용한다.
  • 패스워드 뿐만 아니라 이메일 주소도 올바르게 입력했는지 확인한다.
  • 시간대를 입력받는다.


Basecamp Signup 화면이다.



이름, 이메일, 아이디(Username)을 모두 입력받는다.

2009-08-25

탭 메뉴 작성

CSS와 ul/li 태그로 탭 메뉴를 작성하는 방법이다.


기본 설정

XHTML 1.0을 준수하여 ul, li 태그로 아래처럼 작성한다.

<ul>
  <li><span>소프트웨어</span></li>
  <li><span>하드웨어</span></li>
  <li><span>네트워크</span></li>
</ul>

웹 브라우저에 아래처럼 표시된다.


탭 메뉴 1

CSS를 이용해서 ul, li 태그의 margin과 padding 값을 조정한다. 그리고 ul 태그의 list-style 속성 값을 none으로 한다.

ul {
  list-style: none;
  margin: 2em 1em;
  padding: 0;
}

li {
  margin: 0;
  padding: 1em 0.5em;
}

웹 브라우저에 아래처럼 표시된다.

li 태그의 display 속성 값을 inline으로 한다.

li {
  display: inline;
  margin: 0;
  padding: 1em 0.5em;
}


웹 브라우저에 아래처럼 표시된다.

li 태그에 border 속성을 준다.

li {
  border-bottom: 1px solid rgb(192, 192, 192);
  display: inline;
  margin: 0;
  padding: 1em 0.5em;
}


MS IE 6.0의 버그로 인해서 ul 태그 아래에 다른 태그가 없으면 선이 나타나지 않는다. 따라서 HTML을 아래처럼 수정한다.

<ul>
  <li><span>소프트웨어</span></li>
  <li><span>하드웨어</span></li>
  <li><span>네트워크</span></li>
</ul>

<div/>

 
모질라 파이어 폭스와 오페라에서는 아래 처럼 li 태그 사이에 간격이 생긴다.


이를 방지하기 위해서 아래처럼 li 태그 사이의 여백을 제거한다.
 
<ul>
  <li><span>소프트웨어</span></li
  ><li><span>하드웨어</span></li
  ><li><span>네트워크</span></li>
</ul>


선택한 탭 메뉴는 클래스 속성으로 표현한다.

<ul>
  <li><span>소프트웨어</span></li
  ><li class="selected"><span>하드웨어</span></li
  ><li><span>네트워크</span></li>
</ul>

아래와 같은 CSS 스타일을 추가한다.

li.selected {
  border: 1px solid rgb(192, 192, 192);
  border-bottom: none;
}

최종 결과는 아래와 같다.



탭 메뉴 2

배경 이미지를 이용해서 탭 메뉴를 수정한다.

앞의 예제에서 li 태그의 border 속성을 삭제하고 background 속성을 추가한다.

li {
  background: url("/resource/image/tab_off.gif") no-repeat;
  display: inline;
  margin: 0;
  padding: 1em 0.5em;
}

li.selected {
  background: url("/resource/image/tab_on.gif") no-repeat;
}


이미지를 이용했기 때문에 width 속성으로 넓이를 지정한다. 그리고 inline 요소는 넓이를 지정할 수 없기 때문에 display 속성을 삭제하고 float 속성의 값을 left로 지정한다. 그리고 좌우 padding 속성 값을 0으로 하고 텍스트를 가운데로 정렬한다.
 
li {
  background: url("/resource/image/tab_off.gif") no-repeat;
  float: left;
  margin: 0;
  padding: 1em 0;
  text-align: center;
  width: 78px;
}


2009-08-24

CSS 기본 설정

CSS 작성할 때 가장 먼저하는 것들이다.


웹 브라우저마다 다른 기본 값 초기화


block 유형 태그 기본 값이 웹 브라우저마다 다른 경우가 있다. 따라서 다음과 같이 border, margin, padding 등의 속성을 모두 0으로 설정한다.

html, body, form, ul, li, div, p, h1, h2, h3 {
  border: 0;
  margin: 0;
  padding: 0;
}

/*
html과 form에 대해서도 초기화를 해준다.
모든 태그를 초기화하지는 않고 실제로 사용할 태그들에 대해서만 초기화한다.
*/

예전에는 다음 방법을 사용한 적도 있는데 단점이 많았다.

* {
  border: 0;
  margin: 0;
  padding: 0;
}


이미지에 대한 외곽선 제거를 위한 설정

a img {
  border: 0;
}


테이블 작성

CSS와 HTML에 관심을 갖게된 계기가 table 목록이었다. 2003년 가을에 참여했던 프로젝트에서 웹 디자이너가 작성한 table 목록은 다음과 같았다.

  • table 태그 안에 table 태그를 넣고 첫번째 테이블 배경색으로 선을 표시한다.
  • 선과 글자 사이의 간격은 &nbsp;로 하는 것이었는데, 결과적으로 &nbsp;를 제대로 신경쓰지 않는 게으름이나 HTML 코드 상에서 태그와 글자 사이의 여백이나 줄 바꿈 등의 이유로 들죽날죽이었다.

그러다가 CSS padding 속성으로 이를 손쉽게 제어할 수 있다는 걸 알게 되면서 CSS와 HTML에 대한 관심을 가지게 되었다.



그 이후로 이런 저런 시행착오를 거쳐 지금 주로 사용하는 table 태그로 목록을 작성하는 간결한 방법은 다음과 같다. 우선 table 태그를 다음과 같이 작성한다.

<table>
  <thead>                       <!-- 목록 이름과 목록 내용을 thead와 tbody 태그로 명시적으로 구분 -->
    <tr>
      <th>Column 1</th>   <!-- 목록 이름에는 td 대신에 th 태그를 사용 -->
      <th>Column 2</th>
      <th>Column 3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Content 11</td>
      <td>Content 12</td>
      <td>Content 13</td>
    </tr>
    <tr>
     <td>Content 21</td>
     <td>Content 22</td>
     <td>Content 23</td>
   </tr>
  </tbody>
</table>


지금까지 작업 내용을 웹 브라우저로 보면 다음과 같다.
th와 td 태그에 선을 설정한다.

th, td {
  border: 1px solid silver;
}

이러면 선은 표시되지만 셀과 셀 사이에 여백이 보인다.
이 셀과 셀 사이 여백은 다음과 같이 제거한다.

table {
  border-collapse: collapse;
}

그리고 답답함을 없애기 위해서 셀에 여백을 준다.

th, td {
  border: 1px solid silver;
  padding: 4px 5px;
}

문자열이 긴 경우에는 셀 안에서 줄 바꿈이 일어나기 때문에 line-height 보다는 padding 속성으로 상하 여백을 설정한다.


그리고 목록 이름에 대해서 배경색을 준다.

thead {
  background: navy;
  color: white;             /* 배경색을 설정하면 글자 색도 함께 지정하는 것이 일반적이다. */
}


다음은 최종적인 모습이다.
table01.html

다운로드 편의를 위해서 HTML에 CSS를 포함시켰다.

2009-07-17

자바 2D 두선이 교차하는 지점

http://blog.persistent.info/2004/03/java-lineline-intersections.html


http://mathworld.wolfram.com/Line-LineIntersection.html

2009-07-15

웹 브라우저 버전 체크

클라이언트나 서버 프로그래밍에서 사용자 웹 브라우저 정보를 알아야 하는 경우가 있다. 이를 위한 방법이다.


자바 스크립트에서는 아래 함수를 사용한다.

function getBrowser() {
  var agent = navigator.userAgent;
  var result = "unknown";
  if (agent == null) {
  } else if (agent.indexOf("MSIE 6.0") > -1) {
    result = "ie6";
  } else if (agent.indexOf("MSIE 7.0") > -1) {
    result = "ie7";
  } else if (agent.indexOf("MSIE 8.0") > -1) {
    result = "ie8";
  } else if (agent.indexOf("Firefox") > -1) {
    result = "firefox";
  } else if (agent.indexOf("Opera") > -1) {
    result = "opera";
  } else if (agent.indexOf("Chrome") > -1) {
    result = "chrome";
  } else if (agent.indexOf("Safari") > -1) {
    result = "safari";
  }
  return result;
}


자바에서는 아래 메소드를 사용한다.

package com.dimdol.util;

import javax.servlet.http.HttpServletRequest;

public final class WebUtils {

  private WebUtils() {
  }

  public static String getBrowser(HttpServletRequest request) {
    String agent = request.getHeader("User-Agent");
    String result = "unknown";
    if (agent == null) {
    } else if (agent.indexOf("MSIE 6.0") > -1) {
      result = "ie6";
    } else if (agent.indexOf("MSIE 7.0") > -1) {
      result = "ie7";
    } else if (agent.indexOf("MSIE 8.0") > -1) {
      result = "ie8";
    } else if (agent.indexOf("Firefox") > -1) {
      result = "firefox";
    } else if (agent.indexOf("Opera") > -1) {
      result = "opera";
    } else if (agent.indexOf("Chrome") > -1) {
      result = "chrome";
    } else if (agent.indexOf("Safari") > -1) {
      result = "safari";
    }
    return result;
  }

}

Ruby on Rails에서는 아래 함수를 사용한다.

module ApplicationHelper

  def get_browser
    agent = request.env['HTTP_USER_AGENT']
    result = "unknown"
    if agent.nil?
    elsif agent.include?("MSIE 6.0")
      result = "ie6"
    elsif agent.include?("MSIE 7.0")
      result = "ie7"
    elsif agent.include?("MSIE 8.0")
      result = "ie8"
    elsif agent.include?("Firefox")
      result = "firefox"
    elsif agent.include?("Opera")
      result = "opera"
    elsif agent.include?("Chrome")
      result = "chrome"
    elsif agent.include?("Safari")
      result = "safari"
    end
    return result
  end

end


2009-07-02

MS SQL Server 2005 Express

SQL Server 2005 Express


SQL Server 2005 Management Studio Express


SQL Server JDBC Driver



[MSSQL] Microsoft SQL Server 2005 Express Edition 설치 및 사용후기

자바 스크립트 라이브러리

나중에 나온 jQueryPrototype보다는 조금은 우수한 API라는 평가가 있지만

익숙함으로 인해서 아직은 Prototype...

로그

Apache log4jApache Commons Logging을 사용하는 것이 고전적인 방법이기는 하지만

사용하는 라이브러리를 줄이기 위해서 Java Logging API를 사용하는 것을 권장한다.


* 톰켓을 사용하는 경우에는 톰켓 기본 설정 사용
* 동적으로 로깅 관련 설정을 변경할 수 있는가?

웹 디자인

PNG 사용을 검토한다. (IE 6.0에서는 사용할 수 없음)

Getting Creative With Transparency in Web Design

자바 플랫폼

Java SE 5.0 Update 19

Tomcat 6.0.20

Derby 10.5.1.1

2009-02-26

Ruby Gems 업데이트 오류

회사 홈페이지 개편 작업 때문에 노트북(윈도우즈 XP)에 루비를 설치했다.

ruby 1.8.6 (2007-09-24 patchlevel 111) [i386-mswin32]

그리고 Ruby on Rails 설치를 위해서 Ruby Gems를 최신으로 업데이트했다.

gem update --system


그런데 gem 명령어를 실행하면 정상적으로 동작하지 않았다. 다음 메시지가 출력되었다.

파일 이름, 디렉터리 이름 또는 볼륨 레이블 구문이 틀립니다.


그래서 RUBY_HOME/bin/gem.bat 파일의 마지막 줄을

@"ruby.exe"" "%~dpn0" %*

에서

@"ruby.exe" "%~dpn0" %*

으로 수정하였다.

2009-01-20

아파치 톰켓 6.0 한글 처리

아파치 톰켓 6.0에서 한글 처리 설정을 하는 방법은 다음과 같다. UTF-8을 사용하는 것으로 가정한다.


1. GET

${catalina.base}/conf/server.xml 파일에서 Connector 태그에 URIEncoding 속성을 설정한다.

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8"/>

${catalina.base}는 톰켓을 설치한 디렉토리를 의미한다.


2. POST

서블릿 필터를 이용해서 javax.servlet.ServletRequest 객체의 setCharacterEncoding 메소드를 호출한다.

request.setCharacterEncoding("UTF-8");

* ServletRequest 객체의 getParameter 메소드를 호출하기 전에 setCharacterEncoding 메소드를 호출해야 한다.


3. Content Type


javax.servlet.ServletResponse 객체의 setContentType 메소드로 content-type을 지정한다.

response.setContentType("text/html; charset=UTF-8");

JSP에서 page 선언부로 설정할 수도 있다.

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

JSP 컴파일과 관련된 pageEncoding 속성도 설정한다.


4. HTML

<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>

meta 태그는 title 태그 앞에 있어야 한다.


5. 참고 자료