Whiteship's Note


[IntelliJ + Atlassian] JIRA 연동 사용하기

Good Tools : 2009.12.04 12:57




스크린캐스팅으로 찍는 편이 더 보기 좋으시겠지만;; 귀찮아서;;; 다음 기회에;;
top

TAG IntelliJ, JIRA

조금 친절한 코드 생성기 1 - 구상

모하니?/Coding : 2009.12.04 12:23


어제는 컨트롤러 코드를 약간 불친절하게 생성해주는 코드 생성기를 만들었는데, 오늘은 DAO 코드를 조금 친절하게 생성해주는 기능을 추가해보겠습니다. CodeGeneration 인터페이스에 generateDao 메서드를 추가하고, 템플릿도 추가하면 될 것 같네요. 흠..

DAO 코드는 컨트롤러랑 다르게 CRUD 코드가 거~의 변할 일이 없고 추가될 일만 있죠. 게다가 필요로 하는 인터페이스도 하이버네이트 DAO의 경우에는 SessionFactory 정도가 다입니다. DAO에서 서비스를 참조할 일도 없으니 컨트롤러 보다는 조금 친절하게 코드 생성을 해줄 수 있겠군요.

자 그럼~ 후딱 해볼까요.
top


와이프는 요리사 - 한방 삼계탕




진짜로 요리사는 아니구요. 닭고기는 푹 삶아서 뼈에서 스스륵 분리가 되고, 국물은 온갖 한약 재료로 소금 간을 안해도 맛있게 마실 수 있더라구요. 캬~~ 와이프가 해주는 요리 중에 최고에요.

치킨 사 먹는 것보다 시간은 좀 오래 걸리지만, 싸고 맛있고 건강 음식이니까 좋은 것 같더라구요.

ps: 요즘 닭 한마리에 8천원 정도더군요.
top


불친절한 코드 생성기 5(일단 끝) - 프리마커 기반 코드 생성기 만들기

모하니?/Coding : 2009.12.03 18:02


서비스 인터페이스를 만듭니다.
public interface CodeGenerationService {

    void generateController(String module, Class domainClass) throws CodeGenerationException;

}


일단은 컨트롤러만 생성할테니, 컨틀로러 생성 메서드만 만듭니다. 이때 만들어질 컨트롤러가 속할 module의 이름과 어떤 도메인 클래스에 대한 컨트롤러인지 알려줍니다.

문제가 생기면 RuntimeException을 던집니다. 자, 이제 이 인터페이스를 프리마커 기반으로 구현할 시간이군요.

public class FreemarkerCodeGenerationService implements CodeGenerationService {

    private Configuration configuration;
    private Template controllerTemplate;
    private String destinationDir;
    private Stack<File> createdFilesWhileGenerateController;

    public FreemarkerCodeGenerationService(Configuration configuration, String controllerTemplateName, String destinationDir){
        this.destinationDir = destinationDir;
        this.configuration = configuration;
        try {
            this.controllerTemplate = configuration.getTemplate(controllerTemplateName);
        } catch (IOException e) {
            throw new CodeGenerationException("template file loading fail with [" + controllerTemplateName + "]", e);
        }
    }

    public void generateController(String module, Class domainClass) throws CodeGenerationException {
        createdFilesWhileGenerateController = new Stack<File>();
       
        Map<String, String> map = new HashMap<String,  String>();
        String className = domainClass.getSimpleName();
        map.put("module", module);
        map.put("domainClass", className);
        map.put("domainName", ClassUtils.getShortNameAsProperty(domainClass));

        File desticationFolder = new File(destinationDir);
        boolean created = desticationFolder.mkdir();
        if(created)
            createdFilesWhileGenerateController.push(desticationFolder);
       
        desticationFolder = new File(destinationDir + "/" + module);
        created = desticationFolder.mkdir();
        if(created)
            createdFilesWhileGenerateController.push(desticationFolder);

        File destinationFile = new File(destinationDir + "/" + module + "/" + className + "Controller.java");
        FileWriter writer = null;

        try {
            writer = new FileWriter(destinationFile);
            controllerTemplate.process(map, writer);
            writer.flush();
            writer.close();
            System.out.println(destinationFile.getAbsolutePath()  + " created");
             createdFilesWhileGenerateController.push(destinationFile);
        } catch (IOException e) {
            throw new CodeGenerationException("destincation file creation fail", e);
        } catch (TemplateException e) {
            throw new CodeGenerationException("template processing fail", e);
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
            }
        }
    }

...

    public void deleteController() {
        while(!createdFilesWhileGenerateController.empty()){
            File file = createdFilesWhileGenerateController.pop();
            System.out.println(file.getAbsolutePath());
            boolean deleted = file.delete();
            if(deleted)
                System.out.println(file.getAbsolutePath() + " deleted");
            else
                System.out.println(file.getAbsolutePath() + " not deleted");
        }
    }
}

왠지 좀.. 부끄럽네요. 으흑;;

저 클래스에 필요한 속성(configuration 타입 객체, 템플릿 파일들이 들어있는 위치, 컨틀롤러 템플릿 파일)을 봄싹 프로젝트에 맞게 기본으로 가지고 있는 클래스를 하나 만듭니다.

public class SpringSproutCodeGenerationService extends FreemarkerCodeGenerationService {

    private static final String DESTINATION_DIR = "src/springsprout/modules";
    private static final String CONTROLLER_TEMPLATE_NAME = "controller.ftl";

    public SpringSproutCodeGenerationService(Configuration configuration) {
        super(configuration, CONTROLLER_TEMPLATE_NAME, DESTINATION_DIR);
    }

}

이제 테스트를 해봅니다.

public class SpringSproutCodeGenerationServiceTest {

    @Test
    public void generationTst() throws IOException {
        Configuration configuration = new Configuration();
        configuration.setObjectWrapper(new DefaultObjectWrapper());
        configuration.setDirectoryForTemplateLoading(new FileSystemResource("doc/template").getFile());

        assertNotNull(configuration);

        FreemarkerCodeGenerationService service = new SpringSproutCodeGenerationService(configuration);
        service.setDestinationDir("test/springsprout/modules");
        service.generateController("test", Study.class);

        assertTrue(new File("test/springsprout/modules/test/StudyController.java").exists());
        service.deleteController();
        assertFalse(new File("test/springsprout/modules/test/StudyController.java").exists());
    }
}

끝...




top


불친절한 코드 생성기 4 - 템플릿 만들기

모하니?/Coding : 2009.12.03 17:48


가장 단순한 컨트롤러 코드를 가져다가 군대 군대 코드를 끼워 넣을 지점에 프리마커 태그(?)로 표시를 합니다.

...
@Controller
@SessionAttributes("${domainName}")
public class ${domainClass}Controller {

    @Autowired ${domainClass}Service service;
    @Autowired ${domainClass}Validator validator;

    @RequestMapping(value="/${domainName}/list.do")
    public void list(Model model) throws ServletRequestBindingException {
        model.addAttribute("list",service.getAll());
    }

    @RequestMapping(value="/${domainName}/{id}.do")
    public String view(Model model, @PathVariable int id) {
        model.addAttribute(service.get(id));
        return "${domainName}/view";
    }

    @RequestMapping(value="/${domainName}/add.do", method=RequestMethod.GET)
    public void add(Model model) {
        model.addAttribute(new ${domainClass}());
    }
...


이런식입니다. 참 쉽죠?
- 자바 코드에서 최대한 제네릭하게 편집한 다음 프리마커 편집기로 가져오는 것이 좋겠습니다.
- 태그로 교체할 때는 replace 툴을 이용합시다.
- 친절한 코드 생성기를 만들 때는 템플릿을 어떻게 만드냐에 따라 코드 생성기와 모델의 복잡도가 달라질 겁니다.
- 저는 불친절한 코드 생성기를 만들고 있기 때문에 맘편히 쉽게 만들었습니다.(생성뒤 필요한 import는 알아서 하도록..ㅋ)


top


불친절한 코드 생성기 3 - Freemarker 학습 테스트

모하니?/Coding : 2009.12.03 15:19


참조: http://freemarker.org/docs/pgui_quickstart_all.html

프로젝트의 sandbox에 패키지를 하나 만들고, main 메서드로 학습 테스트를 작성할 클래스 하나와 프리마커 템플릿 하나를 만듭니다. 그리고 위 참조 링크에서 코드를 가져다가 살짝 바꿔서 테스트 해봅니다.

템플릿 파일은 매우 간단하게;;

${message}

main 메서드에 들어갈 코드는 위 링크에서 복사해서 가져온 다음에 File은 스프링의 ClassPathResource를 이용해서 바꾸고, 템플릿 파일 이름은 위에서 만든 템플릿 파일이름으로, Map에는 message에 들어가야 할 것만 넣어 봅니다.

public class SimpleExample {

    public static void main(String[] args) throws Exception {
        /* ------------------------------------------------------------------- */
        /* You should do this ONLY ONCE in the whole application life-cycle:   */

        /* Create and adjust the configuration */
        Configuration cfg = new Configuration();
        cfg.setObjectWrapper(new DefaultObjectWrapper());
        cfg.setDirectoryForTemplateLoading(new ClassPathResource("/sandbox/freemarker").getFile());

        /* ------------------------------------------------------------------- */
        /* You usually do these for many times in the application life-cycle:  */

        /* Get or create a template */
        Template temp = cfg.getTemplate("testTemplate.ftl");

        /* Create a data-model */
        Map root = new HashMap();
        root.put("message", "Hello Freemarker");

        /* Merge data-model with template */
        Writer out = new OutputStreamWriter(System.out);
        temp.process(root, out);
        out.flush();
    }
}

결과는 그냥 눈으로 확인합니다.

Hello Freemarker
Process finished with exit code 0

끝!

이제 Configuration, Template, Map의 관계를 알았으니 본격적으로 코드 생성기를 작성해 보겠습니다.

top


불친절한 코드 생성기 2 - 구상2

모하니?/Coding : 2009.12.03 14:38


아랫 글에 댓글이 달렸지만, 이 기능에 대해 성윤군과 논의를 하다가 IDE가 제공하는 코드 템플릿 기능에 대해 들었습니다. 아차.. 싶더군요. 그래서 생각을 해봤습니다.

IDE 템플릿 기능을 이용할 경우

장점
- 매우 간편하게 코드 생성을 할 수 있습니다. 단축키를 입력하면 코드가 좌르륵.. 생겨나겠죠.

단점
- IDE 별로 템플릿을 작성해 줘야 합니다. 현재 봄싹 개발자 중 20%는 인텔리J IDE를 사용하고 있습니다. 따라서 이클립스용과 인텔리J용 템플릿을 만들어둬야 합니다.
- 개발자가 사용하는 IDE 마다 템플릿을 등록해줘야 합니다. 템플릿 파일도 버전 관리에 포함시켜서 들고다니면 배포하는 방법은 간편하지만 등록은 수동으로 해줘야 합니다. 그건 이클립스 자체를 패키징
- 자동 생성할 파일이 여러개면 매번 파일 만들고 그 파일 돌아다니면서 코드 템플릿 생성 해야함

템플릿 생성 프레임워크를 이용하여 구현할 경우

장점
- 배포 방법 고려할 필요 없음. 소스 코드에 들어있으니 그냥 실행.
- 나중에 여러 파일을 한 방에 생성하는 것도 가능

단점
- 코딩 쬐끔 해야 함.
- 라이브러리 추가 해야 함.

결국은 그냥 코딩 쬐끔 하는 편으로 기울었습니다.
top


불친절한 코드 생성기 1 - 구상

모하니?/Coding : 2009.12.03 12:44




사실 좀 고민입니다. OSAF처럼 GenericController를 만들까. 그냥 저 코드를 찍어내주는 코드 생성기를 만들까. 어떤게 더 사용하기 편하고 확장하기 편할까? GenericControlle를 확장하기 좋게 잘 만들면 되겠지만, 클래스 만들 때 프레임워크 코드를 상속해야 한다는게 귀찮기도 하고, 그래서 어차피 그 부분을 찍어내는 코드 생성기가 필요해지는 상황입니다. 게다가 GenericController를 만들려면 GenericService 인터페이스까지도 있어야 할테니, 일은 더 커지겠죠; 이러다 배보다 배꼽이 커지겠다 싶어서...

일단은 코드 생성기부터 만들기로 마음 먹었습니다. 간단하게;; 위와 비슷한 코드를 그냥 찍어내 주고, 컴파일 에러가 나던 말던 알아서 고쳐서 쓰도록!!!

이름하야.. 불친절한 코드 생성기!


top


이번주 토요일 IBM dW "웹 개발 다반사"

모하니?/Planning : 2009.12.03 10:16


참조: http://www.ibm.com//developerworks/kr/event/seminar/dwlive_1205/index.html

지난 번 KSUG 모임에서는 봄싹 소스 코드를 보면서 이런 저런 스프링 기능을 보여드리려고 했으나, 불발했습니다. 개인적으로 많이 아쉽고 속상했지만, 아직은 그런 모임 운영이 전문적이지 못한 신생 운영진이었는데다가, 이전 발표 하신 분들의 내용이 워낙 참신하고 재미있었기에 제 발표 시간이 없어진 것에 대해 머리로는 이해할 수 있었습니다. 그래도 아쉬운 속내는 어쩔 수가 없었죠.

그런데 이번에는 조금 다른 형식으로 봄싹을 소개할 자리가 마련됐습니다. 당일에도 스터디가 있어서 시작 시간에 맞춰서 갈 수 있을지는 의문이지만.. 발표 시간에는 늦지 않게 참석할 생각입니다.

최종 선정된 발표 주제는 다음과 같습니다.
* 괜찮은 오픈 API 제공하기 + VLAAH API 소개 - 홍민희
* 봄싹 싸이트(http://springsprout.org) 개발 협업 방법 및 사용 기술 - 백기선
* 코드 품질 포탈 SONAR 적용기 - 고경철
* 흑백무성영화한편! (HTTP) - 이동욱
* 자바스크립트 삽질(실수?) 베스트 10 - 장동수
* (Startup기업 CEO의 관점에서 본) 기술의 경제학 - 정지웅
* Realtime Web 간보기 - 김석준
* Spring Framework with JavaFX - 이승철
* 추상 계층의 딜레마 - 황대산
* timelog 업무 적용 실험기 - 송승렬

흠.. 개인적으로는 자바스크립트 삽질 베스트, Realtime Web, 추상 계층의 딜레마 JavaFX with Spring(설마 JavaFX를 이용해서 스프링 프로그래밍을 한 건 한 건 아니겠죠? 제목이 좀...)이 기대됩니다. 다른건 저에게 생소한 것들(VLAAH, timelog, SONAR)이라;; 뭔지 잘 몰라서..;

'모하니? > Planning' 카테고리의 다른 글

블로깅 계획  (0) 2010.05.06
백기선's 2010 Weekly Report - 2/23  (2) 2010.02.23
목표 실천은 진행 중  (12) 2010.02.17
백기선 2010 목표  (12) 2010.02.15
봄싹 Career Path  (6) 2009.12.17
이번주 토요일 IBM dW "웹 개발 다반사"  (2) 2009.12.03
봄싹을 알리러 갑니다.  (6) 2009.11.24
[Atlassian] 이직 계획  (2) 2009.11.02
[ToDo] 20091016  (0) 2009.10.16
[ToDo] 오늘 할 일 - 할일(예상 소요 시간)(실제 소요 시간)  (4) 2009.10.15
2009년 마무리로 할 일  (4) 2009.10.08
top


[아틀라시안] Crowd - 싱글 사인 온

Good Tools : 2009.12.01 14:52


아.. 내가 이녀석 어드민 로그인하느라 삽질한것만 생각하면... ㅠ.ㅠ

왜 이 ㅆㅂㄻㄺㅇㅇㄱㄸㄱㅇㅁㄷㅇㅅㅈㄹㅇㅇㅁㄴㅇㅇㄹㄷㅆㅈㅇㅈ

분명히 설치는 됐는데, 설치후 최초 설정 과정에서 만든 관리자 계정으로 로그인이 안되는 겁니다. 아주 돌아버릴 것 같았죠. DB를 잘못 설치했나, 인코딩이 잘못됐나, 경로가 틀렸나, 톰캣 로그를 보고, 구글링하고 아주.. 짜증이 나서 근 2주 동안 포기했다가 오랜만에 다시 도전했습니다.

http://forums.atlassian.com/thread.jspa?messageID=257323467

이 글 맨 마지막 댓글에 실마리를 찾을 수 있었습니다. /etc/hosts에 crows.properties에 설정되어 있는 도메인을 맵핑해줘야되더군요. 아유 써글.. 이런건 당연히 설치 매뉴얼에 써줘야 하는거 아니니???

자.. 이제 어디 한 곳에만 로그인 해도 여러 사이트에 다 자동으로 로그인이 되는지 설정해봐야겠습니다.
top


[웹 사이트 속도 향상 베스트 프랙티스 9] DNS 룩업 줄이기

모하니?/Coding : 2009.12.01 13:50


http://developer.yahoo.com/performance/rules.html#dns_lookups

Reduce DNS Lookups

tag: content

도메인 네임 시스템(DNS)는 호스트네임을 IP 주소로 맵핑한다. 마치 전화부에서 사람의 이름을 그 사람의 전화번호로 맵핑하는 것처럼. www.yahoo.com을 브라우저에 입력하면, 브라우저에 연결된 DNS 리졸버(resolver) 해당 서버의 IP 주소를 룩업한다. DNS는 비용이 따른다. DNS가 해당 호스트네임으로 IP 주소를 찾는데 보통 20~120 밀리초가 소요된다. 브라우저는 DNS가 해당하는 것을 찾을 때까지 아무것도 다운로드 할 수가 없다.

DNS 룩업을 캐시해서 성능을 높인다. 이 캐싱은 사용자의 ISP 또는 LAN(로컬 지역 네트워크)에서 관리하는 별도의 캐싱 서버에서 할 수 있지만 사용자의 개인 컴퓨터에서도 캐싱할 수 있다. DNS 정보는 운영체제(OS)의 DNS 캐시에 남게된다. (마이크로소프트 윈도우는 "DNS Client service가 있다.) 대부분의 브라우저는 OS와는 별도로 그들만의 캐시를 가지고 있다. 브라우저가 DNS 기록은 자신의 캐시에 유지하는 이상, 기록을 뒤지기 위해 OS를 귀찮게 하지는 않는다.

인터넷 익스플로러(IE)는 DNS 룩업을 기본적으로 DnsCacaheTimeout 레지스트리 설정에 명시한데로, 30분간 캐시한다. 파이어폭스는 network.dnsCacheExpiration 설정에 의해 1분간 DNS 룩업을 캐시한다.(Fasterfox는 이것을 1시간으로 변경한다.)

클라이언트의 DNS 캐시가 (브라우저와 OS에서 모두) 비게되면, DNS 룩업 수가 웹 페이지에 있는 유일한 호스트네임의(unique hostname) 갯수와 동일하게 된다. 페이지의 URL, 이미지, 스크립트 파일, 스타일시트, 플래스 객체 등등에 포함되어 있는 모든걸 말한다. 따라서 호스트 네임 수를 줄이면 DNS 룩업의 수도 줄어든다.

호스트네임 수를 줄이면 잠재적으로 한 페이지에 있는 구성요소를 동시에 다운로드 할 수 있는 양이 줄어들게 된다. DNS 룩업 수는 줄어들지만, 동시 다운로드가 줄어들어 응답 시간이 증가할 수 있다. 개인적인 견해로는 2개 이상으로 컴포넌트를 나누되, 4개 이상의 호스트네임을 사용하지 말자는 것이다. 그런식으로 DNS 룩업 줄이기와 병렬 다운로드 사이의 균형을 맞출 수 있다.
top


난 월세가 좋은데;

모하니?/Thinking : 2009.12.01 12:41


그렇다. 난 돈이 없다. 그렇다고 끼니를 거르거나 길에서 자야할 정도는 아니지만 람보르기니를 산다거나, 마당에 수영장과 잔디밭과 호수가 있는 저택을 살 돈은 없다. 한마디로 그냥 서민이다. 우리 부모님은 자꾸 전세를 어떻게든 얻어라, 집을 빨리 사야 안정적이다 하시는데, 난 잘 모르겠다.

왜 집을 사야 하는걸까?? 아니면 왜 그 힘들게 모은 목돈으로 전세를 얻어 들어가야 하는걸까? 둘 다 어차피 월세 내는 만큼의 유지비(세금, 아파트 관리비 등)는 발생하기 마련 아닌가? 그렇다면 굳이.. '아~~ 내가 샀다!!' 이런 만족감을 얻기 위해서 그렇게까지 해야 할 필요가 있을까? 부동산 투자? 집 사서 나중에 오를테니까 그때 판다고? 과연 그렇게 집값이 오를만한 집을 나같이 평범한 시민이 살 수나 있을까? 부르마블처럼 언제든 팔려면 팔 수 있는 집을 살 수 있다면 나도 사고 싶기는 한데... 한국의 천민자본주의에서 그게 어디 가능키나 한 발상이냔 말이다.

지하철이나 TV 광고중에 가끔 20년 장기로 자기집을 마련하라는 광고를 접할때면 난 그런 생각이 든다. 왜 집을 담보로 반 평생을 국가의 노예로 살아야 하는거지? 저런 집을 사람들은 정말로 산단 말이야?? 헐...  사람이 살다 보면 일을 못하게 될 일이 생길 수도 있고, 그냥 일이 하기 싫어서 쉬고 싶을 수도 있는데, 그런 이유로 빚을 못갚아서 집을 국가가 다시 가져가서 경매로 내다 팔면? 난 거지가 되는거 아냐?? 그럼 아파도 참고 미친듯이 일을 해야 한다느건데.. 그렇게 일하는게.. 사람인가? 노예지.. 전국민을 노예로 만들려는 심산인가... 하긴 MB를 뽑을 정도의 개념이라면 자신이 돈의 노예가 되었다는 사실을 알지도 못한채 살아갈 것 같다.

그런 사람들이 많아지니까 천민자본주의가 되는거고, 실력보단 허세와 룸싸롱 로비가 먹히는거고, 순수한 열정을 가진 사람들은 질력이 나서 떠나거나 죽거나 물들어가며 사는거고, 그런걸 사회화라고, 성장하는거라며 자위나 하는 구더기로 넘쳐나는 쓰레기더미가 되가는건 아닐까?

그렇다. 나는 다소 과대망상하는 경향이 있다. 진정하고 공부나 하자. 그게 살 길이다.

top