2014-12-16

CentOS에 JDK Alternative 멕이기 [삐-]나게 힘들구먼

RHEL이나 CentOS에서 Oracle JDK 설치 관련 구글신께 신탁을 청하면
그냥 "rpm 올리고 alternative 멕이세요." 라고만 되어있다. 진짜...

그래서 만들었습니다.
Oracle JDK Alternative 자동 등록 스크립트!
기존 구글신 신탁처럼 일부분만 등록해버리는게 아니라 전체를 빠르고 편하게 등록해드립니다.

이거 눌러서 받으시고...

0. 먼저 등록할 JDK부터 설치하고 봅니다. (대략 yum localinstall이 좋소)
1. 일단 적절한 곳에 던저놓으신 다음
2. chmod a+x centos-jdk-alternative-update.sh
3. ./centos-jdk-alternative-update.sh <JDK-DIR>

끗!
참 쉽죠?

물론 약간의 오류가 있을 수도 있지만, 그런거 내가 알 게 뭐야

일단 CentOS 7에 Oracle JDK 8 버전으로 테스트해봤습니다.
아마 이전 버전들 (JDK 6, 7)에서도 큰 문제없이 돌아가지 시포요.

p.s.
alternative 업데이트 후에도 버전이 바뀌지 않으면 다음 명령을 입력해줍니다.

최신버전으로 올릴 때:
update-alternatives --auto java
update-alternatives --auto javac

특정버전으로 설정할 때:
update-alternatives --set java <path>
update-alternatives --set javac <path>

각 버전별 경로는 다음 명령으로 알아낼 수 있습니다.
update-alternatives --display java
update-alternatives --display javac
Share:

2014-11-19

2014-10-06

JPA Entity와 Java 8

아직 하이버네이트에 대해서는 테스트해보지 않았지만, 이클립스링크에서 문제가 발생하므로,  비슷한 처리를 하는 하이버네이트에서도 똑같은 문제가 있을 것이다.

엔티티에 @OneToMany@ManyToMany로 컬렉션(List, Set, Map) 필드가 있을 때, 이 필드가 wrapper로 세팅되어있을 경우 (이클립스링크의 경우 IndirectList, IndirectSet, IndirectMap) 일부 Java 8 기능이 먹지를 않는다.

특히 가장 중요한 stream이 동작하지 않는다.
Aㅏ... 망했어요.
이보시오, 이보시오! JPA양반! 그게 무슨 소리요! Stream이 고자라니!

뭐 별 거 있나, wrap 된거를 꺼내오거나 그냥 새로운 컬렉션을 만들어서 값을 복사해서 쓰면 된다.
참 쉽죠? 너무 쉬워서 예제 따위는 없어요.



뱀발:
엔티티 내부에 Java 8 기능을 우겨넣으면 씐나게 로딩하다가 에러가 날 것이다.
아쉽게도 JPA는 Java 8 기능이 들어간 엔티티는 엔티티로 취급하지 않는다 (...)

물론 그런건 별도의 클래스로 분리해 놓고 써먹으면 그만이다.
Share:

2014-07-17

Spring Framework Error Handling: Accept 헤더와 무한루프

이거 때문에 그나마 없던 생활 리듬마저 개박살 나고 말았다아아아아아아~~~~


Spring Framework에는 당연히 에러 처리기가 있고, 적절한 Exception을 적절한 에러 코드로 변환해서 클라이언트에 던진다.
그리고 Spring Boot에는 기본 Error 처리 컨트롤러가 있어서, 적절하게 html 또는 json으로 에러 메세지를 뿌려준다.

그런데, 여기서 모든 문제가 시작된다!

Spring Boot를 사용하는 프로젝트를 하나 만들고, 적절한 Servlet Container Tomcat이라거나 Jetty라거나 를 사용해서 띄워본다.
그리고 (리눅스의 경우) 터미널을 열고 아래 명령을 때려봐라.

HTTP일 경우
curl -H "Accept: application/octet-stream" http://localhost:8080/<context-path>/error

HTTPS일 경우
curl -k -H "Accept: application/octet-stream" https://localhost:8443/<context-path>/error

CPU가 미친듯이 돌아가면서 StackOverflowException을 떨굴 것이다.
지옥에 입장한 것을 축하한다.

원인은 Exception에서 볼 수 있듯이 무한루프인데, Accept에 다른거 text/html이나 application/json 같은거 를 넘기면 정상 동작하는 것을 봐서는 에러 처리 로직에 문제가 있는 것이다.
그래서 디버그를 돌려본 결과...

일단 Spring Boot의 BasicErrorController는 정상적으로 탄다.
그리고 그 결과를 JSON으로 변환하는데, 문제는 Jackson은 application/octet-stream 따원 모른다네.
그래서 Jackson은 난 이런거 모른다네! 하고 에러를 던지고, Spring 에러 핸들러는 이걸 HTTP 응답 코드 406으로 변환한다.

여기까지는 좋았지.
Request에 Accept가 그대로 남아있는고로...

Request의 Accept는 끝이 없고
같은 무한루프를 반복한다.



그래서 이걸 어떻게 해결하느냐고?
안알랴줌.






이면 이 글을 안썼겠지.

Filter를 하나 추가해서 406 응답코드를 인식하면 Request를 Wrapper로 감싼다.
Wrapper는 Accept 헤더의 값을 요청할 경우 null을 떨구면 된다.

참 쉽죠?


p.s
Spring Framework로 생각하고 있었는데 spring-boot의 문제였고, 1.1.5에서 수정되었다.
(https://github.com/spring-projects/spring-boot/issues/1257)
Share:

2014-04-16

Gradle + Eclipse Expert

일단 이 글은 Gradle을 가지고 제대로 삽질 한 번쯤 해봤으면 쉽게 이해할 수 있다.
Gradle 1.11 기준이다. 난 최신을 좋아하지 (물론 삽질도...)

시작하기 전에...

당연히 Eclipse Plugin을 사용해야 된다.
일반 프로젝트라면
apply plugin: 'eclipse'
웹 프로젝트라면
apply plugin: 'eclipse-wtp'

http://kwonnam.pe.kr/wiki/gradle/eclipse도 참고하면 매우 좋다.

Package Explorer와 Project Explorer 정리

Gradle로 그냥 이클립스 프로젝트를 만들면 Dependencies JAR 파일들이 Package Explorer와 Project Explorer를 점령하게 된다. 이제 그것들을 몰아낼 시간이다.

build.gradle에 다음을 추가한다.
eclipse {
    classpath {
        file {
            whenMerged { classpath ->
                def entries = classpath.entries
                entries.findAll { it.kind == 'lib' }*.exported = false
            }
        }
    }
}
그리고 Gradle -> Refresh All을 하면 Maven 쓸 때처럼 깔끔하게 정리된 모습을 볼 수 있다.

WTP Facet 설정

Gradle은 WTP Facet을 알아서 무지 구식으로(!!!) 설정해준다. 진짜로. Servlet 2.3이라니 잘도 이런 구석기 시대 세팅을!
따라서 이런 사태를 방지하기 위해 facet 설정을 따로 해줘야 한다.

아래 설정은 Java 1.7, Servlet 3.0을 사용하는 facet 설정이다.
eclipse {
    wtp {
        facet {
            facet name: 'jst.web', version: '3.0'
            facet name: 'java', version: '1.7'
            facet name: 'wst.jsdt.web', version: '1.0'
        }
    }
}

Project Dependency Correction with Hierarchical Project Name

Spring 프로젝트에서 제공하는 이클립스 Gradle IDE 플러그인이 꽤 괜찮은 기능을 제공하는데, 그 중 하나가 Hierarchical Project Name 지원으로, 이클립스상의 프로젝트명을 root.child 같이 만들어준다. Gradle 프로젝트 임포트시 Use hierarchical project names를 체크하면 이 기능을 사용할 수 있다. 이를 이용해서 여러 프로젝트에 같은 이름의 서로 다른 하위 프로젝트를 가지고 있더라도 아무 문제없이 사용할 수 있다.
다른 프로젝트를 참조하기 전 까지는...

Hierarchical Project Name을 사용한 프로젝트를 dependencies에 추가할 경우 플러그인이 해당 프로젝트를 찾지 못해서 정신줄을 놓게 된다!

뭐 별 거 있나... 직접 수정하면 될 것을...

build.gradle에 다음을 추가한다.

For WTP Project

eclipse {
    wtp {
        component {
            file {
                withXml { provider ->
                    def module = provider.asNode().'wb-module'
                    module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/resource') }.each { entry ->
                        def name = entry.@'handle'.substring('module:/resource/'.length(), entry.@'handle'.lastIndexOf('/'))
                        entry.@'handle' = "module:/resource/${rootProject.name}.${name}/${rootProject.name}.${name}"
                        entry.@'archiveName' = "${name}.jar"
                    }
                }
            }
        }
    }
}

For Normal Project

eclipse {
    classpath {
        file {
            whenMerged { classpath ->
                def entries = classpath.entries
                entries.findAll { it.kind == 'src' && it.path.startsWith('/') }.each {
                    def name = it.path.substring(1)
                    it.path = "/${rootProject.name}.${name}"
                }
            }
        }
    }
}

그리고 Gradle -> Refresh All을 하면 정상적으로 프로젝트 참조를 인식한다.

[2016-08-26] 어... 플러그인이 buildship으로 바뀌면서 이건 더이상 못쓰는데, 사실 더 쉽고 간단한 방법이 있었다. (...)
http://divinespear.blogspot.com/2016/08/gradle-eclipse-expert-2016-edition.html 를 보시라.

Web App Libraries 라이브러리 그룹 비우기

Web App Libraries 라이브러리 그룹은 Maven으로 프로젝트 만들 시절에는 구경도 못해본 거였는데, 이게 가끔 말썽을 부린다.
그래서 이클립스에서 이를 무시하도록 설정을 좀 봐줘야 한다.

build.gradle에 다음을 추가한다.
eclipse {
    classpath {
        containers.removeAll(EclipseWtpPlugin.WEB_LIBS_CONTAINER)
    }
    wtp {
        component {
            file {
                withXml { provider ->
                    def module = provider.asNode().'wb-module'
                    module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/classpath') }*.replaceNode {}
                }
            }
        }
    }
}
그리고 Gradle -> Refresh All을 하면 Web App Libraries에 프로젝트 참조를 제외한 모든 JAR 참조가 날아가 있는 것을 볼 수 있다.

WAR Overlay

Gradle의 WAR Overlay 자체는 참 간단하긴 한데... 이클립스 플러그인이 이를 인식하지 못하니 참으로 통탄할 일이로다.

그래서 역시 설정을 직접 고치면 된다. 안되는게 어디있나... 안되면 되게하라.

build.gradle에 다음을 추가한다.

Eclipse 4.4 이전

eclipse {
    wtp {
        component {
            file {
                withXml { provider ->
                    def module = provider.asNode().'wb-module'
                    module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/overlay') }.each { entry ->
                        entry.'dependency-type'.each {
                            it.value = 'consumes'
                        }
                    }
                }
                whenMerged { component ->
                    // 현재 프로젝트: 무조건 제일 위에 와야함
                    component.wbModuleEntries += new org.gradle.plugins.ide.eclipse.model.WbDependentModule('/', 'module:/overlay/slf/?includes=**/**&excludes=META-INF/MANIFEST.MF')
                    // overlay할 WAR 프로젝트들을 밑에 나열한다.
                    component.wbModuleEntries += new org.gradle.plugins.ide.eclipse.model.WbDependentModule('/', 'module:/overlay/prj/OverlayProject?includes=**/**&excludes=META-INF/MANIFEST.MF')
                    ...
                }
            }
        }
    }
}

Eclipse 4.4 이후

eclipse {
    wtp {
        component {
            file {
                withXml { provider ->
                    def module = provider.asNode().'wb-module'
                    module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/overlay') }.each { entry ->
                        entry.@'handle' = entry.@'handle'.replaceAll('module:/overlay', 'module:/resource')
                        entry.'dependency-type'.each {
                            it.value = 'consumes'
                        }
                    }
                }
                whenMerged { component ->
                    // overlay할 WAR 프로젝트들만 밑에 나열한다. 존나좋군?
                    component.wbModuleEntries += new org.gradle.plugins.ide.eclipse.model.WbDependentModule('/', 'module:/overlay/OverlayProject/OverlayProject?includes=**/**&excludes=META-INF/MANIFEST.MF')
                    ...
                }
            }
        }
    }
}
OverlayProject는 overlay할 WAR 프로젝트명을 써준다. 여러 개의 프로젝트를 overlay할 경우 overlay 순서에 유의해서 추가해주면 된다.

그리고 Gradle -> Refresh All을 하면 정상적으로 WAR Overlay를 인식하고, WTP Server Deploy도 잘 작동한다.

잡담: 이거 삽질이 가장 힘들었다.
Share:

2014-03-10

JPA - Hibernate Specific WTF Rules

JPA 구현 중 가장 널리 쓰이는 것들이 EclipseLinkHibernate이다.
JPA 2.0 이후로는 EclipseLink가 레퍼런스 구현임에도 일단 한국에서는 EclipseLink보다는 Hibernate를 더 많이 쓰는데, Hibernate가 아무래도 역사도 오래됐고 속도는 느리지만 그만큼 편의기능이 많아서 그렇지 않나 싶다.
한국에서 EclipseLink 미는 사람은 저밖에 없는듯?

여기서는 Hibernate 특유의 정신나간 특징들을 내 맘대로 (...) 찝어보겠다.
  1. SEQUENCE를 지원하지 않는 DBMS(예를 들면 MSSQL이나 MySQL)를 타겟으로 @GeneratedValue(strategy = GenerationType.SEQUENCE)를 사용하면 좆된다.
  2. Spring에서 엔티티를 @SessionAttribute로 물릴 경우 @ElementCollectionfetch 속성을 EAGER로 설정해야 한다.
    안그러면 나중에 엔티티 값을 수정할 때 초기화 되지 않았다고 뭐라 그런다.
  3. @ElementCollectionfetch 속성을 EAGER로 설정했으면 Hibernate에서 제공하는 @Fetch로 긁어올 방식을 설정해 주어야 한다.
    안붙여주면 처음 실행할 때부터 테이블 Alias가 없네 하면서 정신줄 놓는다.
일단은 여기까지.
더 밝혀낸 것이 있으면 계속 업데이트될 지도 모른다.
Share: