2010-01-25

A SAMPLE PROBLEM

Jon Bentley가 쓴 Programming Pearls 12장. SAMPLE PROBLEM을 정리한 글이다.

문제


여론 조사 기관에서는 N 명의 사람 중에서 M 명의 사람을 무작위로 추출하여 설문 조사를 한다.

M < N


N 명의 사람엔 0부터 시작하는 일련번호가 부여되어 있다. N을 5라고 가정하면 다음과 같다.

0 강백호
1 서태웅
2 채치수
3 정대만
4 송태섭

M이 2라면 이 중 동일한 확률로 2명을 추출해야 한다.

이는 0에서 N 사이에서 M 개의 숫자를 동일한 확률로 무작위로 추출하는 것에 다름아니다.

* 이 문제를 해결하는데 다음 2 함수를 사용할 수 있다.

bigrand : m 혹은 n 보다도 클 수 있는 정수를 무작위로 반환한다.
randint(i, j) : i에서 j 사이에 포함되어 있는 정수를 무작위로 반환한다.


솔루션 1

select = m
remaining = n
for i = [0, n)
  if (bigrand() % remaining) < select
    print i
    select--
  remaining--

* 추출된 숫자는 m 보다 많을 수도 m 보다 적을 수도 없다. 강조한 부분을 주의 깊게 보면 이를 파악할 수 있다.

일련번호가 0인 강백호가 선택될 확률은 2 / 5이지만 이 후에 있는 사람들이 선택될 확률은 앞 결과에 영향을 받는다. 예를 들어 강백호가 선택되면 일련번호가 1인 서태웅이 선택될 확률은 1 / 4이고, 강백호가 선택되지 않으면 서태웅이 선택될 확률은 2 / 4이다.

그러나 전체적으로는 서태웅이 추출될 확률도 2 / 5이다.

서태웅이 추출될 확률
= 강백호가 추출될 확률 (2 / 5) * 1 / 4 + 강백호가 추출되지 않을 확률 (3 / 5) * 2 / 4
= 2 / 20 + 6 / 20
= 2 / 5

이를 C++로 구현하면 다음과 같다.

void genknuth(int m, int n)
{
  for (int i = 0; i < n; i++)
    if ((bigrand() % (n - i) < m) {
      cout << i << "\n";
      m--:
    }
}


솔루션 2

set에 난수를 담는 방법도 있다.

* set에는 동일한 값이 들어 갈 수 없다.

initialize set S to empty
size = 0
while size < m do
  t = bigrand() % n
  if t is not in S
    insert t into S
    size++
print the elements of  S in sorted order

C++ STL을 이용하여 구현하면 다음과 같다.

void gensets(int m, int n)
{
  set<int> S;
  while (S.size() < m)
    S.insert(bigrand() % n);
  set<int>::iterator i;
  for (i < S.begin(); i != S.end(); ++i)
    cout << *i << "\n";
}


솔루션 3

n 개 크기 배열을 만든 후에 이들의 위치를 무작위로 변경(swap)하고 처음 m 개를 선택하는 방법도 있다. 화투나 포카에서 카드를 섞는 것(shuffle)과 유사하다.

for i = [0, n)
  swap(i, randint(i, n - 1))


C++를 구현하면 다음과 같다.

void genshuf(int m, int n)
{
  int i, j;
  int *x = new int[n];
  for (i = 0;; i < n; i++)
    x[i] = i;
  for (i = 0; i < m; i++) {
    j = randint(i, n - 1);
    int t = x[i]; x[i] = x[j]; x[j] = t;
  }
  sort(x, x + m)
  for (i = 0; i < m; i++)
    cout << x[i] << "\n";
}

개념 코드와는 다르게 m개 까지만 swap한다. (Ashley Shepherd와 Alex Woronow)



기타

만약 n이 100만이고 m이 n - 10이라면 조금은 다르게 접근해야 한다. 이 경우에는 추출되지 않을 10개를 계산하는 것이 적합하다.



프로그래밍 혹은 문제 해결은 다음과 같이 진행된다.

  1. Understand the Perceived Problem
  2. Specify an Abstract Problem
  3. Explore the Design Space
  4. Implement One Solution
  5. Retrospect

2010-01-13

Putty에서 캐릭터 셋 고정

Putty에서 캐릭터 셋을 UTF-8로 변경해도 다시 접속하면 값이 초기화되는 문제가 있었다.

이를 해결하는 방법이다.

http://k.daum.net/qna/view.html?qid=3yKL8



결론은 계정 정보 자체를 저장해야 한다는 것이다... 직관적이지 못하다...

2010-01-12

Color...

00FF66과 같은 HEX 문자열로 java.awt.Color 객체를 만들어야 했다.

Color 클래스 생성자는 RGB 값만을 파라미터로 받으니 귀찮은 데이터 전환을 해야 하나 고민하다가... 다음 메소드를 이용하면 된다는 걸 알았다.

Color color = Color.decode("00FF66");

그런데 예외가 발생했다. 앞에 #을 붙여 주어야 했다.

Color color = Color.decode("#00FF66");


그런데 스택트레이스 덕분에

Exception in thread "main" java.lang.NumberFormatException: For input string: "0ff66"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
    at java.lang.Integer.parseInt(Integer.java:458)
    at java.lang.Integer.valueOf(Integer.java:528)
    at java.lang.Integer.decode(Integer.java:958)
    at java.awt.Color.decode(Color.java:707)

실제로는 Integer 클래스의 decode 메소드를 사용하면 된다는 것도 알았다.


2010-01-07

이클립스에 static import 처리

이클립스에서 static import를 편리하게 사용하려면 다음 설정을 해준다.

1. Organize Import 처리

http://www.prever.co.kr/josh/?p=35

2. Code Assist 처리

http://blog.naver.com/kcufl/60094963610

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이었는데 여기서도 동일한 예외가 발생했다.