java의 reflection은 플랫폼을 구성하는 데 효율적으로 쓰일 수 있는 좋은 api이다.

spring의 util에는 ReflectionUtils를 통해 java의 reflection api를 쉽게 사용할 수 있는 api를 제공한다.



예제를 시작한다.


앞으로 사용할 예시를 위해서 사용될 import 이다.

import com.google.common.collect.Maps;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Map;



먼저, MyActivity를 정의했다. 


import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

public class MyActivity {
public String id;
public String content;
private String writer;
private boolean liked;

public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}



MyActivity 클래스에 ReflectionUtils의 findField, setField, getField, makeAccessible 메소드를 사용하여 내부 변수를 마음대로 바꾼다. 하지만, private 필드를 함부로 수정할 수 없기 때문에 makeAccessible 메소드를 사용한다.

MyActivity myActivity = new MyActivity();

Field idField = ReflectionUtils.findField(MyActivity.class, "id");
System.out.println("field(id) : " + idField);

Field writerField = ReflectionUtils.findField(MyActivity.class, "writer");
System.out.println("field(writer) : " + writerField);

ReflectionUtils.setField(idField, myActivity, "200001");
Object value = ReflectionUtils.getField(idField, myActivity);
System.out.println("value : " + value);

// error - Could not access method: Class org.springframework.util.ReflectionUtils can not access a member of class MyActivity with modifiers "private"
// ReflectionUtils.setField(writerField, myActivity, "Kent");

ReflectionUtils.makeAccessible(writerField);
ReflectionUtils.setField(writerField, myActivity, "Kent");


실행하면, 다음 결과를 얻을 것이다.


field(id) : public java.lang.String MyActivity.id

field(writer) : private java.lang.String MyActivity.writer

value : 200001




참고로, ReflectionUtils.makeAccessible 메소드는 java의 reflection api의 다음 api를 조금 추상화한 것 밖에 없다.

field.setAccessible(true);





예제 2는 조금 길지만, Reflection.doWithFields에 대한 예시이다. ReflectionUtils.FieldCallback()을 이용한 것이라서, 무척 괜찮은 api이다. 간단하게 field를 출력하기 위해 RelfectionUtils.FieldCallback()의 doWith 메소드와 matches 메소드의 사용 방법을 작성했다. 


accessible에 대해서 조심스럽게 써야 할 예시도 추가했으며, matches 메소드 없이도 doWith 메소드로 모두 처리할 수 있다. 




MyActivity myActivity = new MyActivity();
Map<String, Object> properties = Maps.newHashMap();
properties.put("id", "1");
properties.put("content", "sns number one service");

// 먼저 field를 필터하기 위해 new ReflectionUtils.FieldFilter를 한 후
// FieldCallback를 사용하여 필드의 값을 저장한다
ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
field.set(myActivity, properties.get(field.getName()));
}
}, new ReflectionUtils.FieldFilter() {
@Override
public boolean matches(Field field) {
return properties.containsKey(field.getName()) && Modifier.isPublic(field.getModifiers());
}
});
System.out.println(myActivity);



ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
if (field.isAccessible()) {
field.set(myActivity, properties.get(field.getName()));
}
}
});

System.out.println(myActivity);


/*
access 못할 필드에 set하면, FieldCallback를 사용할 때 에러 발생한다.
ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
field.set(myActivity, properties.get(field.getName()));
}
});


<결과>
field : public java.lang.String MyActivity.id
field : public java.lang.String MyActivity.content
field : private java.lang.String MyActivity.writer

Not allowed to access field 'writer': java.lang.IllegalAccessException: Class ReflectionTest$3 can not access a member of class MyActivity with modifiers "private"
java.lang.IllegalStateException: Not allowed to access field 'writer': java.lang.IllegalAccessException: Class ReflectionTest$3 can not access a member of class MyActivity with modifiers "private"

==>


*/

properties.put("writer", "kent");
properties.put("liked", true);

//FieldCallback를 사용할 때 에러가 발생하니, field에 access하게 만들던지, modifier로 구분한다.
ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
ReflectionUtils.makeAccessible(field);
field.set(myActivity, properties.get(field.getName()));
}
});

System.out.println(myActivity);



ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
int modifier = field.getModifiers();
if (Modifier.isPrivate(modifier) || Modifier.isFinal(modifier) || Modifier.isStatic(modifier)) {
// do nothing;
} else {
String name = field.getName();
if (name.equals("id") || name.equals("content")) {
field.set(myActivity, properties.get(field.getName()));
}
}
}
});
System.out.println(myActivity);


결과는 다음과 같다.


field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

MyActivity[id=1,content=sns number one service,writer=<null>,liked=false]

field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

field : private java.lang.String MyActivity.writer

field : private boolean MyActivity.liked

MyActivity[id=1,content=sns number one service,writer=<null>,liked=false]

field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

field : private java.lang.String MyActivity.writer

field : private boolean MyActivity.liked

MyActivity[id=1,content=sns number one service,writer=kent,liked=true]

field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

field : private java.lang.String MyActivity.writer

field : private boolean MyActivity.liked

MyActivity[id=1,content=sns number one service,writer=kent,liked=true]






또한 필드 뿐만 아니라, 필드에 붙은 Annotation도 처리할 수 있다. Comment 라는 모델과 CommentType이라는 Annotation 결합형에 대해서 ReflectionUtils 처리 코드를 살펴본다.



public class Comment {

public String id;

@CommentType("type1")
public String type;

public String content;


@CommentType("method")
public void createComment() {
System.out.println("create comment");
}
}


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CommentType {
String value();
}



Comment 객체를 생성하고, 필드에 CommentType에 annotation이 있다면, 처리하는 방식이다. AOP 나, 프레임 워크 구축 때 유용할 수 있는 포맷이다. 

Comment comment = new Comment();
comment.id="1";
comment.content="hahahah";
comment.type="comment";

ReflectionUtils.doWithFields(Comment.class, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

if (field.getAnnotation(CommentType.class) != null) {
System.out.println("field : " + field);
}
List<Annotation> annotationList = Arrays.asList(field.getAnnotations());
for (Annotation annotation : annotationList) {
System.out.println(annotation);
}
}
});


결과는 다음과 같다.


field : public java.lang.String Comment.type

@CommentType(value=type1)

BUILD SUCCESSFUL

Total time: 2.121 se





다음은 필드가 아닌 메소드 관점으로 살펴보는 메소드, ReflectionUtils.findMethod 메소드 예시이다. 모든 method를 출력하는 예시이다.


반면, 주의해야 할 내용도 있다. 실제 없는 메소드를 호출하면, NullPointerException(NPE)가 발생할 수 있다. 



Comment comment = new Comment();
comment.id="1";
comment.content="hahahah";
comment.type="comment";

Method method = ReflectionUtils.findMethod(Comment.class, "createComment");
ReflectionUtils.invokeMethod(method, comment);

// java.lang.NullPointerException
// Method noMethod = ReflectionUtils.findMethod(Comment.class, "noMethod");
// ReflectionUtils.invokeMethod(noMethod, comment);


ReflectionUtils.doWithMethods(Comment.class, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
System.out.println(method.getName());
}
});



결과는 다음과 같다.


create comment

createComment

finalize

wait

wait

wait

equals

toString

hashCode

getClass

clone

registerNatives

notify

notifyAll






ReflectionUtils.doWithMethods를 사용하여 메소드에 연관된 annotation을 발견할 수 있다. 사용방식은 ReflectionUtils.doWithFields 메소드와 동일하다.


Comment comment = new Comment();
comment.id = "1";
comment.content = "hahahah";
comment.type = "comment";


ReflectionUtils.doWithMethods(Comment.class, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {

Annotation annotation = AnnotationUtils.getAnnotation(method, CommentType.class);
if (annotation instanceof CommentType) {
System.out.println("find comment type annotation.");
System.out.println(method.getName());
}

}
});


결과는 다음과 같다.


find comment type annotation.

createComment

Posted by '김용환'
,

spring boot에서 아주 간단히 multi-datasource를 관리하고 jdbctemplate를 사용할 수 있는 예제를 소개한다.





먼저 maven또는 gradle 파일의 dependency에 db의 driver, spring-boot-starter-jdbc, commons-dbcp2를 추가한다.


build.gradle

compile("mysql:mysql-connector-java:5.1.36")
compile("org.springframework.boot:spring-boot-starter-jdbc")
compile("org.apache.commons:commons-dbcp2:2.1.1")


application.yml

spring:
profiles : dev
activity_slave_db_0:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://db0.google.com:3306/test
username: develop
password : devteam
max-wait : 10000
max-active : 5
test-on-borrow: true
validationQuery: select 1
activity_slave_db_1:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://db1.google.com:3306/test
username: develop
password : devteam
max-wait : 10000
max-active : 5
test-on-borrow: true
validationQuery: select 1



Datasource 설정 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class DatabaseConfig {

@Primary @Bean(name = "dsSlave0")
@ConfigurationProperties(prefix="spring.activity_slave_db_0")
public DataSource dsSlave0() {
return DataSourceBuilder.create().build();
}

@Bean(name = "dsSlave1")
@ConfigurationProperties(prefix="spring.activity_slave_db_1")
public DataSource dsSlave1() {
return DataSourceBuilder.create().build();
}

@Bean(name = "jdbcSlave0")
@Autowired
public JdbcTemplate slave0JdbcTemplate(@Qualifier("dsSlave0") DataSource dsSlave) {
return new JdbcTemplate(dsSlave);
}

@Bean(name = "jdbcSlave1")
@Autowired
public JdbcTemplate slave1JdbcTemplate(@Qualifier("dsSlave1") DataSource dsSlave) {
return new JdbcTemplate(dsSlave);
}

}




실제 DAO에서 사용하는 jdbctemplate 코드.

@Component
public class ActivitySlaveDao {
@Autowired
@Qualifier("jdbcSlave0")
private JdbcTemplate jdbcTemplate0;
@Autowired
@Qualifier("jdbcSlave1")
private JdbcTemplate jdbcTemplate1;
private List<JdbcTemplate> getJdbcTemplates() {
List list = Lists.newArrayList();
list.add(jdbcTemplate0);
list.add(jdbcTemplate1); }

// .. jdbcTemplate 사용. }


Posted by '김용환'
,


up이라는 테이블이 존재하고 그에 맞는 간단한 모델 객체가 있다.

up이라는 테이블은 간단하게 id와 date가 존재하고, date 타입으로 update_at 이라는 필드가 있다고 가정하자.


public class Up {

public long id;

public long updateAt;

}



RowMapper를 정의한다. 유의할 점은 mysql date 타입을 java 객체에서 표현할 방법이 중요하다. Types.TIMESTAMP인지 메타데이터를 확인한다. Object로 먼저 읽은 후, TimeStamp 객체로 변경한 다음 getTime()을 호출하여 long 값으로 얻어내야 한다.

(String이나 그냥 Long 타입으로 받아들인후 Date 타입으로 쉽게 변환되지 않는다)

public class UpRowMapper implements RowMapper {

public Object mapRow(ResultSet rs, int rowNum) throws SQLException {

Up up = new Up();

up.id = rs.getLong("id");

ResultSetMetaData metadata = rs.getMetaData();

if (metadata.getColumnType(2) == Types.TIMESTAMP) {

Object updateAt = rs.getObject(2);


up. updateAt = ((Timestamp) updateAt).getTime();

}

return up;

}

}



실제 쿼리는 다음과 같이 사용한다.

List<Up> upList = jdbcTemplate.query("select id, updated_at from up", new UpRowMapper(), id);


jdbcTemplate에 배치성 데이터를 생성한다.  이 때 setTimestamp() 메소드를 사용한다.

jdbcTemplate.batchUpdate("INSERT IGNORE INTO up (id, updated_at) values (?, ?)  ", new BatchPreparedStatementSetter(){

@Override

public void setValues(PreparedStatement ps, int i) throws SQLException {

Up up = upList.get(i);

ps.setLong(1, up.id);

ps.setTimestamp(4, new Timestamp(up. updateAt));

}


@Override

public int getBatchSize() {

return upList.size();

}

});

Posted by '김용환'
,



spring boot에 레디스 클러스트와 연동하는 코드를 작성했는데, 


http://docs.spring.io/spring-data/redis/docs/current/reference/html/#cluster 예제를 사용하면 잘 동작한다.



build.gradle 추가

compile("org.springframework.data:spring-data-redis:1.7.2.RELEASE")
compile("redis.clients:jedis:2.8.1")



applicationproperties

spring.redis.cluster.nodes=...
spring.redis.cluster.timeout=5
spring.redis.cluster.max-redirects=3



코드


import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class ClusterConfigurationProperties {

List<String> nodes;

public List<String> getNodes() {
return nodes;
}

public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
}




import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Set;

@Service
public class RedisClusterService {

@Autowired
private RedisTemplate<String, String> redisTemplate;

@Resource(name="redisTemplate")
private ListOperations<String, String> listOps;

public void addKeyValue(String key, String value) {
listOps.leftPush(key, value);
}

public String getKey(String key) {
return listOps.rightPop(key);
} }


set, zset, list,  일반 key-value에 대한 command는 아래 커맨드를 사용하여 의미있는 커맨드를 카테고리 별로 확인/실행할 수 있다. 

redisTemplate.opsForSet() redisTemplate.opsForZSet() redisTemplate.opsForValue() redisTemplate.opsForList()
...




Posted by '김용환'
,


play1 framework 유틸리티는 문서로 잘 설명되어 있지 않다.

이럴 때를 위해 play help 명령어를 실행한다.


$ play help

~        _            _

~  _ __ | | __ _ _  _| |

~ | '_ \| |/ _' | || |_|

~ |  __/|_|\____|\__ (_)

~ |_|            |__/

~

~ play! 1.4.2, https://www.playframework.com

~

~ For all commands, if the application is not specified, the current directory is used

~ Use 'play help cmd' to get more help on a specific command

~

~ Core commands:

~ ~~~~~~~~~~~~~~

~ antify          Create a build.xml file for this project

~ autotest        Automatically run all application tests

~ build-module    Build and package a module

~ check           Check for a release newer than the current one

~ classpath       Display the computed classpath

~ clean           Delete temporary files (including the bytecode cache)

~ dependencies    Resolve and retrieve project dependencies

~ eclipsify       Create all Eclipse configuration files

~ evolutions      Run the evolution check

~ evolutions:applyAutomatically apply pending evolutions

~ evolutions:markAppliedMark pending evolutions as manually applied

~ evolutions:resolveResolve partially applied evolution

~ help            Display help on a specific command

~ id              Define the framework ID

~ idealize        Create all IntelliJ Idea configuration files

~ install         Install a module

~ javadoc         Generate your application Javadoc

~ list-modules    List modules available from the central modules repository

~ modules         Display the computed modules list

~ netbeansify     Create all NetBeans configuration files

~ new             Create a new application

~ new-module      Create a module

~ out             Follow logs/system.out file

~ pid             Show the PID of the running application

~ precompile      Precompile all Java sources and templates to speed up application start-up

~ restart         Restart the running application

~ run             Run the application in the current shell

~ secret          Generate a new secret key

~ start           Start the application in the background

~ status          Display the running application's status

~ stop            Stop the running application

~ test            Run the application in test mode in the current shell

~ version         Print the framework version

~ war             Export the application as a standalone WAR archive

~

~ Also refer to documentation at https://www.playframework.com/documentation



status가 궁금하다면, 다음 명령어를 사용한다.


$ play help status

~        _            _

~  _ __ | | __ _ _  _| |

~ | '_ \| |/ _' | || |_|

~ |  __/|_|\____|\__ (_)

~ |_|            |__/

~

~ play! 1.4.2, https://www.playframework.com

~

~ Name:

~ ~~~~~

~ status -- Display the running application's status

~

~ Alias:

~ ~~~~~

~ st

~

~ Synopsis:

~ ~~~~~~~~~

~ play status [app_path] [--url=http://...] [--secret=...]

~

~ Description:

~ ~~~~~~~~~~~~

~ This script tries to connect to the running application's /@status URL to request the application status.

~ The application status contains useful informations about the running application.

~

~ The status command is aimed at monitoring applications running on production servers.

~

~ Options:

~ ~~~~~~~~

~ --url:

~ The script try to connect to the application at the localhost domain. If you want to monitor an application running on

~ a remote server, specify the application URL using this option (eg. play status --url=http://myapp.com)

~

~ --secret:

~ The script uses the secret key to generate an authorization token. It assumes that the secret key available from the

~ app_path/conf/application.conf is valid. If not you can provide your own secret key using this option

~ (eg. play status --secret=bghjT7ZG7ZGCO8)

~

~ If you provide both options, you can run this command without a local application directory (app_path is not required).

~


프로젝트 디렉토리에서 다음 커맨드를 실행한다.


$ play status --url=http://127.0.0.1:9000

~        _            _

~  _ __ | | __ _ _  _| |

~ | '_ \| |/ _' | || |_|

~ |  __/|_|\____|\__ (_)

~ |_|            |__/

~

~ play! 1.4.2, https://www.playframework.com

~

~ Status from http://127.0.0.1:19000/@status,

~

Java:

~~~~~

Version: 1.8.0_40

Home: /usr/java/jdk1.8.0_40/jre

Max memory: 438304768

Free memory: 36274240

Total memory: 233832448

Available processors: 2

Play framework:

~~~~~~~~~~~~~~~

Version: 1.4.2

Path: /usr/local/play-1.4.2

ID: beta

Mode: PROD

Tmp dir: xxxxxxx


Application:

~~~~~~~~~~~~

Path: xxxxx

Name: play-server

Started at: 06/13/2016 22:23


Loaded modules:

~~~~~~~~~~~~~~

spring at xxxxx


Loaded plugins:

~~~~~~~~~~~~~~

0:play.CorePlugin [enabled]

1:play.ConfigurationChangeWatcherPlugin [disabled]

10:play.modules.router.ext.YetAnotherRouterAnnotationsPlugin [enabled]

100:play.data.parsing.TempFilePlugin [enabled]

200:play.data.validation.ValidationPlugin [enabled]

300:play.db.DBPlugin [enabled]

400:play.db.jpa.JPAPlugin [enabled]

450:play.db.Evolutions [enabled]

500:play.i18n.MessagesPlugin [enabled]

600:play.libs.WS [enabled]

700:play.jobs.JobsPlugin [enabled]

1000:play.modules.spring.SpringPlugin [enabled]

100000:play.plugins.ConfigurablePluginDisablingPlugin [enabled]

Threads:
~~~~~~~~
Thread[Reference Handler,10,system] WAITING
Thread[Finalizer,8,system] WAITING
Thread[Signal Dispatcher,9,system] RUNNABLE
Thread[RMI TCP Accept-0,5,system] RUNNABLE
Thread[RMI TCP Accept-19100,5,system] RUNNABLE
Thread[RMI TCP Accept-0,5,system] RUNNABLE
......
Thread[nifty-client-worker-2,5,main] RUNNABLE
Thread[nifty-client-worker-3,5,main] RUNNABLE

Requests execution pool:
~~~~~~~~~~~~~~~~~~~~~~~~
Pool size: 256
Active count: 0
Scheduled task count: 1936
Queue size: 0

Monitors:

~~~~~~~~

AController.list()                             ->      121 hits;    226.6 avg;    116.0 min;    707.0 max;

........


Jobs execution pool:

~~~~~~~~~~~~~~~~~~~

Pool size: 0

Active count: 0

Scheduled task count: 0

Queue size: 0




만약 play 커맨드를 알고 싶다면, 다음 소스를 살펴본다.


https://github.com/playframework/play1/tree/master/framework/pym/play/commands


만약 status에 대해서 깊이 알고 싶다면,

CorePlugin.java와  https://github.com/playframework/play1/blob/master/framework/pym/play/commands/status.py를 살펴본다.

실제 url이  url = 'http://localhost:%s/@status' % http_port 인 것을 볼 수 있다.



Posted by '김용환'
,



apache commons io의 FilenameUtils 클래스 사용 예제


import static org.junit.Assert.assertTrue;

import org.apache.commons.io.FilenameUtils;

import org.apache.commons.lang.StringUtils;

import org.junit.Test;




@Test

public void test()

String url="http://host.google.com/abc/def/ghi/img.jpg?height=500&width=500";

String path = StringUtils.substringBefore(url, "?");

assertTrue("jpg".matches(FilenameUtils.getExtension(path)));

assertTrue("img".matches(FilenameUtils.getBaseName(path)));


assertTrue("http://host.google.com/abc/def/ghi/".matches(FilenameUtils.getFullPath(path)));

assertTrue("http://host.google.com/abc/def/ghi".matches(FilenameUtils.getFullPathNoEndSeparator(path)));

assertTrue("http://host.google.com/abc/def/ghi/".matches(FilenameUtils.getPath(path)));

}


Posted by '김용환'
,

1.3.0에서 1.3.4 또는 1.4.2로 버전업할 때 유의사항을 소개한다.



play1 framework가 잠깐 중지되었다가 다시 진행되어 개발되고 있다.

특별히 API는 거의 바뀌지는 않고, 의존 library 버전 업 또는 elastic한 코드 유지보수 정도로 진행되고 있다.




play1 framework를 1.3.0에서 1.3.4 또는 1.4.2로 버전 업할 때, 기존에 쓰던 netty 버전이 이슈가 될 수 있다.

play1 1.3.4 또는 1.4.2는 netty 3.9.8 final 버전을 사용하고 있다.


특히 PlayHandler.java에서 netty 3.9.7부터 소개된 org.jboss.netty.handler.codec.http.cookie.ServerCookieEncoder 때문이라도 3.9.7 이전 버전을 쓸 수 없다. 


https://github.com/playframework/play1/blob/master/framework/src/play/server/PlayHandler.java#L412


nettyResponse.headers().add(SET_COOKIE, ServerCookieEncoder.STRICT.encode(c));





만약 그 이하의 버전의 netty를 사용할 때는 500 에러와 함께 internal error (check logs)만 보게 될 것이다.


(문제를 해결하기 위해 play 소스의 PlayHandler.java를 수정하면서 진행하니. 해당 코드의 Unexpected Error만 출력되는 것을 확인할 수 있었다. 그래서 더 깊이 보니. ClassNotFoundException같은 류로 여겨진다.)



-> 따라서 기존의 웹 애플리케이션 서버의 netty 버전을 3.9.8 final로 쓰면 간단히 문제는 해결된다.






*play1의 단점

Play1 framework의 PlayHandler.java 코드를 살펴보면, Exception 처리가 잘 안되어 있다. 따라서 문제가 생겼을 때 아무것도 못할 수 있는데, 이때는 framework/src 디렉토리 밑의 소스를 수정하고 ant 빌드하면서 play 코드를 실행할 수 있다. (play1 framework는 Exception 기반의 framework라....)





* dependency lib 이슈

play 애플리케이션이 play/framework/lib/asm-all-5.1.jar 대신 asm-all-4.1.0.jar를 써야 동작되는 이슈가 있었다.

관련해서 lib를 교체하고 사설 jar를 만들어 배포해서 실행토록 진행했다.

Posted by '김용환'
,


간단한 집합 명령에 대한 코드이다. 



https://en.wikipedia.org/wiki/Symmetric_difference









https://en.wikipedia.org/wiki/Complement_(set_theory)


The relative complement of A (left circle) in B (right circle): B \cap A^c = B \setminus A






The absolute complement of A in UAc = U \ A




Set의 difference, removeall, symmetricDifference과 Guava의 difference 메소드 테스트 예시이다.

Set<String> s1 = Sets.newHashSet("1", "2", "3");

Set<String> s2 = Sets.newHashSet("3", "4");


System.out.println(Sets.difference(s1, s2)); // 1,2


Set<String> set1 = ((Set<String>) ((HashSet<String>) s1).clone());

set1.removeAll(s2);

System.out.println(set1); // 1, 2


System.out.println(com.google.common.collect.Sets.difference(s1, s2)); // 1,2


System.out.println(Sets.symmetricDifference(s1, s2)); // 1, 2, 4





Posted by '김용환'
,


jenkins에서 간단한 인증을 하도록 설정하는 가이드이다. 


Jenkins관리 > Configure Global Security >Enable Security 


1) Jenkins’ own user database를 체크, 사용자의 가입 허용 여부는 확인(!)

2)  Matrix-based security에서 

User/group to add: 를 이용하여 사용자 계정 추가.

   그 다음 화면에서 패스워드를 등록하는 화면이 존재한다. 


Posted by '김용환'
,


인증이 필요한 jenkins job에서 job을 모니터링할 때, 쓸만한 python 예시 코드이다.


헤더에 인증 관련 base64 인코딩이 필요하고, python urllib2을 잘 사용하면 테스트할 수 있는 코드이다. 


아래 코드는 재시작하는 jenkins job이 동작중이면, 모니터링을 하지 않으려는 코드이다. 재시작할 때는 모니터링하지 않으려 했다. 



#!/usr/bin/python


import base64

import urllib2

import json


def auth_headers(username, password):

   return base64.encodestring('%s:%s' % (username, password))[:-1]


auth_header = auth_headers('develop', 'devteam')

headers={'Content-Type': 'text/xml; charset=UTF-8','Authorization': 'Basic %s' % auth_header}

jenkins_url = 'http://jenkins.google.com8080/job/elasticsearch-batch/lastBuild/api/json'

req = urllib2.Request(url=jenkins_url, headers=headers)

isJobWorking=json.load(urllib2.urlopen(req))['building']

print isJobWorking





Posted by '김용환'
,