spring에는 cron 표현식을 사용하지만, 너무 똑같은 시간(0초)에 동작되지 않게 할 수 있다. 



spring 3.x에는 어노테이션에서 spel을 지원하지 않아 XML로 설정해야 한다. 



<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:task="http://www.springframework.org/schema/task"

       xsi:schemaLocation="

http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">


    <task:executor id="googleExecutor" pool-size="5-30" queue-capacity="10000"/>

    <task:annotation-driven scheduler="googleScheduler" executor="googleExecutor"/>


    <task:scheduler id="googleScheduler" pool-size="30"/>



    <task:scheduled-tasks scheduler="googleScheduler">

        <task:scheduled ref="googleFeedService" method="reloadCache" fixed-delay="#{new Double(T(java.lang.Math).random()*3000).intValue() + 600000}"/>

    </task:scheduled-tasks>


</beans>





반면, spring 4.3에서 어노테이션을 사용하면 다음과 같이 쉽게 사용할 수 있다.


@Scheduled(fixedRate = 600000, initialDelayString = #{ T(java.lang.Math).random() * 3000} )




Posted by '김용환'
,



logback에서 마음에 드는 것 중 하나는 log에 항상 나오는 패턴을 지정할 수 있다는 점(encoder)이다.




<appender name="birthday" class="ch.qos.logback.core.rolling.RollingFileAppender">

<file>${app.home}/logs/birthday.log</file>

<encoder>

<pattern>%d{yyyyMMdd}\t%d{HHmmssSSS}\t%m%n</pattern>

</encoder>

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

<fileNamePattern>${app.home}/logs/birthday.log.%d{yyyyMMdd}</fileNamePattern>

<maxHistory>21</maxHistory>

</rollingPolicy>

</appender>

<logger name="birthday_logger" level="INFO" additivity="false">

<appender-ref ref="birthday"/>

</logger>



birthday logger를 사용해서 로그를 저장할 때 탭 단위로 날짜, 시간,  메시지를 저장할 수 있다는 점이 매력적이다. 

Posted by '김용환'
,




cglib과 asm은 의존성 관계가 깊다. 버전을 잘 맞춰야 한다. 아래와 같은 에러가 발생해서 버전을 수정해야 했다. 


 .initialize Unable to obtain CGLib fast class and/or method implementation for class com.google.event.EventMessage, error msg is net/sf/cglib/core/DebuggingClassWriter

java.lang.VerifyError: net/sf/cglib/core/DebuggingClassWriter




아래 cglib를 기반으로 잘 맞는 버전을 찾으면 된다. 

https://github.com/cglib/cglib/releases


asm 4.x은 cglib 3.1로 맞추고, asm 3.1은 cglib 2.2로 맞춰야 한다. 




Posted by '김용환'
,


Json을 파싱할 수 있는 ObjectMapper 예시를 소개한다.  


많이 알려져 있지만, 자주 실수할 수 있는 내용과  좋은 팁을 소개한다. 





먼저, UnrecognizedPropertyException을 많이 발생시킬 수 있는 내용을 소개한다. 


아래는 무난하게 잘 동작하는 코드이다. 


import java.util.List;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.ObjectMapper;


public class JsonTest {

  @Test

  public void mapperObject() throws Exception {

  ObjectMapper mapper = new ObjectMapper();

  String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\"}";

  EventData eventData = mapper.readValue(jsonInString, EventData.class);

  System.out.println(eventData);

  }

}

 

class EventData {


  @JsonProperty("event_code")

  private int eventCode;


  @JsonProperty("name")

  private String name;


  @JsonProperty("messages")

  private List<String> messages;

}




이 코드가 정상적으로 잘 동작하려면, json이 EventData에 맞게 오지 않거나 최소한 해당 json 노드이외에는 전달되지 않는다는 보장이 있어야 한다. 


갑자기 json 문자열에 새로운 노드가 새로 추가되면, com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException이 발생한다.


String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\", \"emp\":\"samuel\"}";




예외 내용은 다음과 같다. 


com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "emp" (class com.kakao.story.util.EventData), not marked as ignorable (3 known properties: "event_code", "name", "messages"])

 at [Source: {"event_code":112233,"messages":["hello","java"],"name":"x112x1121", "emp":"samuel"}; line: 1, column: 77] (through reference chain: com.kakao.story.util.EventData["emp"])

at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51)

at 





예외를 발생시키지 않고 안전하게 해결하려면 @JsonIgnoreProperties(ignoreUnknown = true)을 클래스 정의에 둔다. 다른 방법도 있지만, 이 방식이 아주 많이 사용되는 방식이다.



import java.util.List;

import org.junit.Test;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.ObjectMapper;


public class JsonTest {

@Test

public void mapperObject() throws Exception {

ObjectMapper mapper = new ObjectMapper();

String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\", \"emp\":\"samuel\"}";

EventData eventData = mapper.readValue(jsonInString, EventData.class);

System.out.println(eventData);

}

}

@JsonIgnoreProperties(ignoreUnknown = true)

class EventData {


@JsonProperty("event_code")

private int eventCode;


@JsonProperty("name")

private String name;


@JsonProperty("messages")

private List<String> messages;

}




참고로, 코드는 문제가 없어보이지만, org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "event_code" (Class com.kakao.story.util.EventData), not marked as ignorable 예외가 발생할 수 있다.


ObjectMapper 클래스의 fully qualified name을 유심히 살펴본다. 


import org.codehaus.jackson.map.ObjectMapper;


import java.util.List;

import org.junit.Test;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.DeserializationConfig;


public class JsonTest {

@Test

public void mapperObject() throws Exception {

ObjectMapper mapper = new ObjectMapper();

String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\", \"emp\":\"samuel\"}";

EventData eventData = mapper.readValue(jsonInString, EventData.class);

System.out.println(eventData);

}

}

@JsonIgnoreProperties(ignoreUnknown = true)

class EventData {


@JsonProperty("event_code")

private int eventCode;


@JsonProperty("name")

private String name;


@JsonProperty("messages")

private List<String> messages;

}



import com.fasterxml.jackson.databind.ObjectMapper를 사용해야지 org.codehaus.jackson.map.ObjectMapper를 사용하면 원인모를 미궁에 빠질 수 있다. 






또 다른 팁은 serialization시 null에 대한 핸들링이다. 아래 코드를 살펴본다.



import java.util.List;


import org.apache.commons.lang.builder.ToStringBuilder;

import org.apache.commons.lang.builder.ToStringStyle;

import org.junit.Test;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.ObjectMapper;


public class JsonTest {

@Test

public void mapperObject() throws Exception {

ObjectMapper mapper = new ObjectMapper();

EventData eventData = new EventData(); 

eventData.eventCode = 123123;

eventData.name = "wang.lee";

String jsonString = mapper.writeValueAsString(eventData);

System.out.println(jsonString);

}

}

@JsonIgnoreProperties(ignoreUnknown = true)

class EventData {


@JsonProperty("event_code")

int eventCode;


@JsonProperty("name")

String name;


@JsonProperty("messages")

List<String> messages;

@Override

public String toString() {

return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);

}

}


실행하면, 저장 안한 키의 값을 null로 serialization한다. 


{"event_code":123123,"name":"wang.lee","messages":null}


최적화된 json 정보를 유지하기 위해 null 정보를 뺄 수 있다.




Pojo 클래스에 @JsonInclude(Include.NON_NULL)을 추가하면, null 정보를 뺄 수 있다.

import java.util.List;


import org.apache.commons.lang.builder.ToStringBuilder;

import org.apache.commons.lang.builder.ToStringStyle;

import org.junit.Test;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.annotation.JsonInclude;

import static com.fasterxml.jackson.annotation.JsonInclude.Include;


public class JsonTest {

@Test

public void mapperObject() throws Exception {

ObjectMapper mapper = new ObjectMapper();

EventData eventData = new EventData(); 

eventData.eventCode = 123123;

eventData.name = "wang.lee";

String jsonString = mapper.writeValueAsString(eventData);

System.out.println(jsonString);

}

}

@JsonIgnoreProperties(ignoreUnknown = true)

//@JsonInclude(Include.NON_NULL)
@JsonInclude(value=Include.NON_ABSENT, content=Include.NON_EMPTY)

class EventData {


@JsonProperty("event_code")

int eventCode;


@JsonProperty("name")

String name;


@JsonProperty("messages")

List<String> messages;

@Override

public String toString() {

return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);

}

}


결과는 다음과 같다. 


{"event_code":123123,"name":"wang.lee"}



Java8과 guava에서도 쓸 수 있게 @JsonInclude(value=Include.NON_ABSENT, content=Include.NON_EMPTY)를 사용했다. java7이하에서는 @JsonInclude(Include.NON_NULL)를 사용할 수 있을 것이다. 





참고로 @JsonInclude에 대해 설명하면 다음과 같다.


Include.NON_ABSENT는 값이 null이나 java8, guava의 optional의 내부값이 null일 때 serialization하지 않는다.

Include.ALWAYS는 값과 상관없이 serialization한다.

Include.NON_DEFAULT는 값이 기본 설정과 다른 경우 속성이 serialization한다.

Include.NON_EMPTY는 값이 null이 아니거나 empty하지 않으면 serialization한다.

Include.NON_NULL는 null이 아닐 때만 serialzation한다.






Posted by '김용환'
,


google의 guava의 hash 함수가 존재한다.


패키지명은 com.google.common.hash.Hashing이다.


아주 친절하게도 http://goo.gl/jS7HH에 잘 저장되어 있어서 어느 것을 선택할 지 알려주고 있다!!





adler32, crc32,sha256 등등의 경우는 추천하지 않고 있다. Not stable것도 있으니 노트를 잘 보고 용도에 맞게 써야 할 것 같다. 


예를 들어, Hashing.murmur3_32는 다음 메소드를 가지고 있다. 


  /**

   * Returns a hash function implementing the

   * <a href="http://smhasher.googlecode.com/svn/trunk/MurmurHash3.cpp">

   * 32-bit murmur3 algorithm, x86 variant</a> (little-endian variant),

   * using the given seed value.

   *

   * <p>The exact C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A).

   */

  public static HashFunction murmur3_32(int seed) {

    return new Murmur3_32HashFunction(seed);

  }


   /**

   * Returns a hash function implementing the

   * <a href="http://smhasher.googlecode.com/svn/trunk/MurmurHash3.cpp">

   * 32-bit murmur3 algorithm, x86 variant</a> (little-endian variant),

   * using a seed value of zero.

   *

   * <p>The exact C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A).

   */

  public static HashFunction murmur3_32() {

    return Murmur3_32Holder.MURMUR3_32;

  }



다음과 같이 사용할 수 있다. 


 com.google.common.hash.HashFunction algorithm = com.google.common.hash.Hashing.murmur3_32(0);

 long b = algorithm.hashBytes("google:plus:201612123:na".getBytes()).padToLong()






참고로 jedis에도 hashing 코드가 존재한다.


https://github.com/xetorthio/jedis/blob/master/src/main/java/redis/clients/util/Hashing.java


다음과 같이 사용할 수 있다. 


redis.clients.util.Hashing algorithm = redis.clients.util.Hashing.MURMUR_HASH;

long b = algorithm.hash(redis.clients.util.SafeEncoder.encode("google:plus:20161223:na"));


Posted by '김용환'
,



jenkins에 job을 생성하고 jenkins의 환경 변수를 스크립트에서 쓸 수 있는 환경 변수를 얻으려면 다음 주소를 살펴본다.



https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project


build number, build url, job name, jenkins url, build tag 등 다양한 환경 변수를 사용할 수 있다. 



주의사항으로.. 장비 이름과 실제 호출되는 도메인 이름이 다를 때는 jenkins url이 의도한 것과 달리 나타날 수 있다.


jenkis url은 node 기준으로 나타나기 때문에 호출시 불리우는 dns name이 아닐 수 있다.  장비 관점의 도메인이 아니라, dns 서버 관점의 도메인이 url로 볼 수 없다. 이는 jenkins 로그인 설정이 활성화되어 있는 경우, 스크립트 내부에서 jenkins url을 사용할 때, 로그인을 다시 해야 하는 상황이 생길 수 있다. 




Environment VariableDescription
BUILD_NUMBERThe current build number, such as "153"
BUILD_IDThe current build id, such as "2005-08-22_23-59-59" (YYYY-MM-DD_hh-mm-ss, defunct since version 1.597)
BUILD_URL 
The URL where the results of this build can be found (e.g. http://buildserver/jenkins/job/MyJobName/666/)
NODE_NAMEThe name of the node the current build is running on. Equals 'master' for master node.
JOB_NAMEName of the project of this build. This is the name you gave your job when you first set it up. It's the third column of the Jenkins Dashboard main page.
BUILD_TAGString of jenkins-${JOB_NAME}-${BUILD_NUMBER}. Convenient to put into a resource file, a jar file, etc for easier identification.
JENKINS_URLSet to the URL of the Jenkins master that's running the build. This value is used by Jenkins CLI for example
EXECUTOR_NUMBERThe unique number that identifies the current executor (among executors of the same machine) that's carrying out this build. This is the number you see in the "build executor status", except that the number starts from 0, not 1.
JAVA_HOMEIf your job is configured to use a specific JDK, this variable is set to the JAVA_HOME of the specified JDK. When this variable is set, PATH is also updated to have $JAVA_HOME/bin.
WORKSPACEThe absolute path of the workspace.
SVN_REVISIONFor Subversion-based projects, this variable contains the revision number of the module. If you have more than one module specified, this won't be set. 
CVS_BRANCHFor CVS-based projects, this variable contains the branch of the module. If CVS is configured to check out the trunk, this environment variable will not be set.
GIT_COMMIT 
For Git-based projects, this variable contains the Git hash of the commit checked out for the build (like ce9a3c1404e8c91be604088670e93434c4253f03) (all the GIT_* variables require git plugin)     
GIT_URLFor Git-based projects, this variable contains the Git url (like git@github.com:user/repo.git or [https://github.com/user/repo.git])
GIT_BRANCH 
For Git-based projects, this variable contains the Git branch that was checked out for the build (normally origin/master) 


Posted by '김용환'
,



Jenkins에서 아래와 같이 job이 실행이 안되고, 계속 기다려야 하는 상황이 있을 수 있다.


(대기열—Waiting for next available executor on worker) 





아마도 화면에서는 이렇게 보일 것이다.





화면에서 추측할 수 있듯이 프로세스 executor 개수 설정에 따라 executor 상태 정보를 볼 수 있다.






jenkins 노드 설정에 # of executors 값이 1 로 설정되어 있다.(화면의 항목은 실행할 수 있는 job executor를 표현한 것이다) 이 값을 큰 값으로 정의한다.


큰 값(예, 20)으로 정의하면, 위 화면에서 3 번째 노드처럼 20개의 executor를 볼 수 있다.









Posted by '김용환'
,





jenkins(1.583)에서 Job 구성시 Build 섹션에서 MultiJob Phase 에서 여러 job을 추가할 수 있다. 

만약 Multijob 실행 중 하나에서 문제가 발생하면, job에서는 다음과 같은 에러가 발생하고, 더 이상 job 실행이 되지 않는다.



<문제 발생한 job 로그>


Build step 'Execute shell' marked build as failure

Finished: FAILURE




<문제 job 때문에 같이 에러가 발생하는 job 로그>


Aborted by anonymous 

jenkins Finished: ABORTED




이 문제를 해결하는 과정을 공유한다.


- Job의 Kill the Phase on 옵션에 Never 를 선택한다.

- Abort all other job이 실행되지 않도록 한다.






이렇게 설정해도  문제가 발생할 수 있다. 




그럴 때는 장비에 접근해서 자바 데몬(slave)이 잘 떠 있는지 확인한다.

보니, slave 데몬이 동시에 두 개가 떠 있다면, 문제 해결의 실마리가 있는 지 확인할 수 있다.


$ ps -ef | grep java

www   11282 11227  0 19:10 pts/0    00:00:00 grep --color=auto java

www   24960 24711  0 16:47 ?        00:00:00 bash -c cd "/var/lib/jenkins" && java  -jar slave.jar

www   24961 24710  0 16:47 ?        00:00:00 bash -c cd "/var/lib/jenkins" && java  -jar slave.jar

www   25002 24960  0 16:47 ?        00:00:31 java -jar slave.jar

www   25003 24961  0 16:47 ?        00:00:36 java -jar slave.jar





아마도 장비 설정을 잘못한 경우이다. 


- jenkins 노드 설정시, 한번 slave agent를 실행한 상태라면, 기존 장비로 연결되어 있다. 따라서, '연결끊기'를 한후, 다시 '연결' 버튼을 누르고, launch slave agent 버튼을 클릭하여 jenkins slave 데몬이 정상적으로 실행될 수 있다. 



Multi job을 실행하는 장비에서 slave데몬이 아래처럼 필요한 데몬만 뜨도록 설정되어 있는지 ps 명령어로 확인한다.



$ ps -ef | grep java

deploy   12347 12225  0 19:22 ?        00:00:00 bash -c cd "/var/lib/jenkins" && java  -jar slave.jar

deploy   12368 12347  8 19:22 ?        00:00:05 java -jar slave.jar

deploy   12985  4801  0 19:23 pts/0    00:00:00 grep --color=auto java


    

이제 더 이상 aborted 에러 메시지를 발생하지 않는다.



Posted by '김용환'
,


play1 framework에서 test 스코프 실행할 수 있다. 

(좀 이상하긴 하지만..)



conf/dependencies.yml를 수정한다. 


    - org.springframework -> spring-test 3.2.11.RELEASE:

            id: test





test 스코드 관련 의존성 파일 읽어온다.


play dependencies --%test --sync

play test



play run 할 때 다음 로그가 출력된다.


~ framework ID is test




다시 원래대로 상용 환경으로 실행하려면, 다음과 같이 실행한다.


play dependencies --sync

play run



* 참고 문서


https://github.com/playframework/play1/blob/master/documentation/manual/ids.textile


By the way, play test is then equivalent to:

play run --%test




* 참고 소스



https://github.com/playframework/play1/blob/master/framework/test-src/play/deps/dependencies_test2.yml


require:

    - play

    - play -> pdf [0.9,)

    - play -> deadbolt 1.5.4

    - play -> cobertura 2.4:

        id: test

    - commons-lang -> commons-lang 2.5

    - commons-codec 1.7

    - net.sf.json-lib -> json-lib 2.4 jdk15

    - crud



https://github.com/playframework/play1/blob/master/samples-and-tests/just-test-cases/conf/application.conf



%test.application.mode=dev

%test.db.url=jdbc:h2:mem:play;MODE=MYSQL;LOCK_MODE=0;MV_STORE=false;

%test.jpa.ddl=create


# Second database

%test.db_other.url=jdbc:h2:mem:other;MODE=MYSQL;LOCK_MODE=0

%test.db_other.jpa.ddl=create




https://github.com/playframework/play1/blob/master/framework/test-src/play/LoggerTest.java


  @Before

    public void setUp() throws Exception {

        Play.configuration = new Properties();

        Play.applicationPath = new File(".");

        Play.id="test";   

    }



Posted by '김용환'
,

apache common에 NumberUtils 메소드 중에, to타입이라는 타입 변환 메소드가 있다.

또한 숫자인지 구분하는 것과 간단히 max, min을 구하는 메소드가 있어서 소개한다.



org.junit.Assert.assertEquals(1111L, org.apache.commons.lang.math.NumberUtils.toLong("1111"));

org.junit.Assert.assertEquals(0, org.apache.commons.lang.math.NumberUtils.toLong("111a"));


org.junit.Assert.assertEquals(1.1, org.apache.commons.lang.math.NumberUtils.toDouble("1.1"), 0);

org.junit.Assert.assertEquals(3000, org.apache.commons.lang.math.NumberUtils.toInt("3000"));

org.junit.Assert.assertEquals(0, org.apache.commons.lang.math.NumberUtils.toInt("abc"));


org.junit.Assert.assertEquals(false, org.apache.commons.lang.math.NumberUtils.isDigits("a1"));

org.junit.Assert.assertEquals(true, org.apache.commons.lang.math.NumberUtils.isNumber("111"));


org.junit.Assert.assertEquals(3, org.apache.commons.lang.math.NumberUtils.max(1, 2, 3));

org.junit.Assert.assertEquals(1, org.apache.commons.lang.math.NumberUtils.min(new int[]{1,2,3}));


//모두 통과






그리고, NumberUtils에는 큰 숫자를 만들 수 있는 'create타입'이 있는데, 그 중 createBigDecimal()과 createBigInteger() 메소드가 있어서 종종 유용하다.



System.out.println(org.apache.commons.lang.math.NumberUtils.createBigDecimal("11111111111111111111111111111111111111"));

System.out.println(org.apache.commons.lang.math.NumberUtils.createBigInteger("11111111111111111111111111111111111111"));


<결과>



11111111111111111111111111111111111111

11111111111111111111111111111111111111


Posted by '김용환'
,