Javascript

[Javascript] jsTree 사용법 및 Tree 자료구조

jsTree는 jquery 기반으로, HTML 또는 JSON 데이터를 Tree 콘트롤로 웹에 출력해 주는 자바스트립트 라이브러리이다.
홈페이지는 이곳 ==> https://www.jstree.com/

사용한 예는 다음과 같다.
캡처-jsTree-실행예

먼저 jsTree 라이브러리를 불러온다.


	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.2/themes/default/style.min.css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.2/jstree.min.js"></script>

<!-- (...중략...) -->
<div id="layerTree"></div>

이후 HTML 또는 JSON 데이터를 생성해 jstree()를 실행시킨다.

// jsTree 생성
$('#layerTree').jstree({
	"plugins" : [ "wholerow", "checkbox", "changed" ],
	'core' : { 'data' : treeJson }
});

데이터 정의는 HTML과 JSON 두가지가 있는데, 위 코드는 JSON 방식이다.
HTML의 경우, ‘ul’과 ‘li’ 태그를 이용하여 계층을 표현한다.

예제를 따라서, 구현을 들어갔는데 HTML 출력이 원하는대로 되지 않아서, JSON 방식으로 구현했다.
오히려 더 깔끔하고, Tree 자료구조 그대로 삽입할 수 있어서 더 나은 선택이 되었다.

** JSON 자료구조
참고: https://www.jstree.com/docs/json/


// jsTree의 Node 자료구조
{
  id          : "string" // will be autogenerated if omitted
  text        : "string" // node text
  icon        : "string" // string for custom
  state       : {
    opened    : boolean  // is the node open
    disabled  : boolean  // is the node disabled
    selected  : boolean  // is the node selected
  },
  children    : []  // array of strings or objects
  li_attr     : {}  // attributes for the generated LI node
  a_attr      : {}  // attributes for the generated A node
}

// jsTree 사용예
$('#using_json').jstree({
  'core' : {
    'data' : [
       'Simple root node',           // 단독으로 text만 넣거나
       {                             // 자료구조를 지켜서 넣거나
         'text' : 'Root node 2',
         'state' : {
           'opened' : true,
           'selected' : true
         },
         'children' : [              // 자식노드들은 Array에 넣어야 한다
           { 'text' : 'Child 1' },
           'Child 2'
         ]
      }
    ]
} });

이를 위해 사용자가 정의한 데이터로부터 jsTree와 호환되는 Tree 자료구조를 구현해야 하는데
다음과 같이 코드를 작성하였다.


/**
 ** ****************************************************
 ** 레이어 리스트 Tree 생성
 ** ****************************************************
 */

var treeCtrl = treeCtrl || (function(){

	var Node = function( nodeName ){
		return {
			text: nodeName,
			state: { 'opened' : false, 'selected' : false },
			data: null,			// @type: ol.Layer
			children: []
		};
	};

	var _root = new Node('ROOT','group');

	var findChild = function( parent, nodeName ){

		for( var i=0; i<parent.children.length; i++ ){
			var child = parent.children[i];
			if( child.text == nodeName ){
				return child;
			}
		}

		return null;
	};

	// find parent which save item
	var getParent = function( parent, group, opt_make ){

		opt_make = ( typeof opt_make === 'undefined' ) ? true : false;

		for( var i=0; i<group.length; i++ ){
			var child = findChild( parent, group[i] );
			// 없으면 Node 생성
			if( !child ){
				if( !opt_make ) return null;

				child = new Node( group[i] );
				parent.children.push( child );
			}
			parent = child;
		}

		return parent;
	}

	// group에 layer 아이템 저장
	var _set = function( key, item ){
		var group = key.split('-');
		var parent = getParent( _root, group );

		var child = new Node( item.name, 'item' );
		child.data = item;

		parent.children.push( child );

		return child;
	};

	// item인 node들만 반환 (=layer)
	var _get = function( key ){
		var group = key.split('-');
		var parent = getParent( _root, group );

		return parent.children.filter(function( node ){
			return node.type == 'item'
		});
	};

	var _clear = function(){
		_root.children = [];
	};

	var _setState = function( key, state ){
		var group = key.split('-');
		var node = getParent( _root, group, false );
		if( !node ) return false;

		node['state'] = state;
		return true;
	};

	var _toTreeJson = function(){
		return _root.children;
	};

	return {
		'root'  	: _root,
		'set'   	: _set,
		'get'   	: _get,
		'setState'  : _setState,
		'toTreeJson': _toTreeJson,
		'clear' 	: _clear,
	};

})(treeCtrl);

// wfsData 읽어들이기
for( var key in wfsData ){
	var node = treeCtrl.set( wfsData[key].group, wfsData[key] );
}
// 법정경계 펼치고, 선택하기
treeCtrl.setState( '주제도-법정경계', { 'opened' : true, 'selected' : true } );
var treeJson = treeCtrl.toTreeJson();
//console.log( treeJson );

Node의 트리 위치는 set() 함수의 ‘key’로 정의되는데, ‘-‘ 분리자로 계층을 표현하였다.
예를 들어, ‘주제도-법정경계’라면 ‘주제도’ 아래에 ‘법정경계’ 노드를 가리킨다.

이에 대한 트리 생성과 이벤트 처리는 다음과 같이 한다.

** 생성 옵션: “wholerow”, “checkbox”, “changed”
** 이벤트 : ‘changed’에 대해 ‘selected’와 ‘deselected’를 처리


// jsTree 생성
$('#layerTree').jstree({
	"plugins" : [ "wholerow", "checkbox", "changed" ],
	'core' : { 'data' : treeJson }
});

// jsTree 클릭 이벤트 : 레이어 setVisible
$('#layerTree').on("changed.jstree", function (e, data) {
	// 선택된 레이어 보이기
	var i, j, r = [];
	for(i = 0, j = data.changed.selected.length; i < j; i++) {
		var node = data.instance.get_node( data.changed.selected[i] );
		if( node.data !== null && node.data.hasOwnProperty('layer') ){
			wfsLayers[ node.data.layer ].setVisible( true );
			r.push( node.data.layer );
		}
	}
	console.log( 'selected Layer: ' + r.join(', ') );

	// 선택해제된 레이어 감추기
	r = [];
	for(i = 0, j = data.changed.deselected.length; i < j; i++) {
		var node = data.instance.get_node( data.changed.deselected[i] );
		if( node.data !== null && node.data.hasOwnProperty('layer') ){
			wfsLayers[ node.data.layer ].setVisible( false );
			r.push( node.data.layer );
		}
	}
	console.log( 'deselected Layer: ' + r.join(', ') );
});

jsTree 선택에 의한 조작 대상은 OpenLayer3의 layer의 setVisible() 이다.

더 자세한 설정은 jsTree 홈페이지를 참조하시길..
구글링으로도 예제코드들이 잘 나온다. (마음에 드는게 안나와서 기록하는 거지만)

Javascript

[펌] ‘this’를 위한 문맥 지정에 call 사용

** 참고문서
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/call

익명 함수 호출에 call 사용

순수하게 생성된 이 예에서, 익명 함수를 만들고 배열 내 모든 객체에서 이를 호출하기 위해 call을 사용합니다. 여기서 익명 함수의 주목적은 모든 객체에 print 함수를 추가하는 겁니다, 배열 내 객체의 정확한 인덱스를 출력할 수 있는 this 값으로 객체 전달이 반드시 필요하지는 않았지만 설명 목적으로 했습니다.

var animals = [
  { species: 'Lion', name: 'King' },
  { species: 'Whale', name: 'Fail' }
];

for (var i = 0; i < animals.length; i++) {
  (function(i) {
    this.print = function() {
      console.log('#' + i + ' ' + this.species + ': ' + this.name);
    }
    this.print();
  }).call(animals[i], i);
}
스프링4-마이바티스-예제08_케이스7
Spring MVC

스프링4 HaspMap으로 DB select 하기

몇달전 스프링을 접한 후로 DB 연결 설정과 JSON 출력 방법을 드디어 알아내었다.
책대로 따라도 해보고 검색으로 이것저것 공부했지만 이해하기 어려웠는데
간절하면 이루어진다는 말처럼 결국은 반복 학습이 해결을 해 주었다.
(스프링 책 앞부분만 5~6번은 반복해 따라해 본 듯 하다)

참고서적: 코드로 배우는 스프링 웹 프로젝트구멍가게 코딩단

스프링4에서 DB를 연결해 HashMap과 JSON으로 출력하기 위해서는
아래 순서의 작업 과정이 필요하다.

  1. 모듈 선언
    – pom.xml 작성
    – maven을 통해 jdbc와 mybatis, dbms 관련 모듈의 dependency 등록
  2. MyBatis 설정
    – src/main/webapp/WEB-INF/spring/root-context.xml 작성
    – db 접속정보와 MyBatis의 sqlSessionFactory, sqlSession 설정
  3. (optional) 추가 설정파일 생성
    – mybatis-config.xml: SQL 등록시 resultType의 namespace 참고용
    – log4jdbc.log4j2.properties 와 logback.xml: DB 로깅용 Log4j2 용 설정
  4. mapper SQL 작성
    – root-context의 ‘mapperLocations’에 정의한 위치에 *_SQL.xml 생성
    – DAO에서 호출할 namespace를 설정하고 select, insert/update/delete 등을 포함
    select 구문에 resultType=”java.util.HashMap” 사용
  5. DAO 작성 (interface & implement)
    return type으로 List< HashMap > 사용
    Java 기본객체를 사용할 경우 VO 클래스가 필요없어진다
    @Repository 선언하고 @Inject로 MyBatis의 SqlSession을 Inject한다
    – MyBatis의 SqlSession으로 mapper SQL의 namespace + ID를 호출한다
  6. Service 작성 (interface & implement)
    @Service 선언하고 @Inject로 dao를 Inject한다
  7. Controller 작성 (RestController)
    @RestController를 선언 (클래스 전체)
    – RequestMapping 개별로 REST 출력을 사용하려면 함수 출력부에 ‘@ResponseBody’를 선언

개발환경으로는

  • sts-3.8.0
  • java 1.7
  • springframework 4.1.7.RELEASE

스프링4-마이바티스-예제02

우선 DB 연결없이 JSON 출력을 위한 콘트롤러단의 샘플을 먼저 살펴보자.

CASE1. VO 데이터를 JSP의 View로 넘기는 일반적인 방법

// 외부에서 /C.do 을 호출하면 연결됨
@RequestMapping("C.do")
public String doC(Model model){
    logger.info( "doC called......................." );

    // model은 view 단으로 데이터를 넘기기 위한 Map형 자료구조이다
    String msg = "9999년99월99일";
    model.addAttribute( "serverTime", msg );

    ProductVO product = new ProductVO( "Sample Product C", 19999 );
    model.addAttribute( "product", product );

    // views 아래의 'home.jsp'를 호출
    return "home";
}

View: home.jsp

<%@ page contentType="text/html;charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<!doctype html>
<html lang="ko">
<head>
  <title>Home</title>
</head>
<body>
  <h1>Hello world!</h1>
  <P>The time on the server is <c:out value="${serverTime}"/>.</P>
  <P><c:out value="${product.name}"/> / <c:out value="${product.price}"/> </P>
</body>
</html>

스프링4-마이바티스-예제08_케이스1

CASE2. VO 데이터를 JSON으로 바로 출력하는 방법

// Annotation @ResponseBody 를 선언하면 클래스를 JSON으로 변환해 출력한다
@RequestMapping("D.do")
public @ResponseBody ProductVO doD(){
    logger.info( "doD called......................." );

    ProductVO product = new ProductVO( "Sample Product D", 59999 );

    return product;
}

스프링4-마이바티스-예제08_케이스2

CASE3. Map 데이터를 직접 작성해 JSON으로 출력하는 방법

@RequestMapping("E.do")
public @ResponseBody Map<String, Object> doE(){
    logger.info( "doE called......................." );

    // Map 데이터타입으로 출력 : 생성은 HashMap으로 하고
    Map<String, Object> product = new HashMap<String, Object>();
    product.put("name", new String("Map DATA2") );
    product.put("price", new Double(39999) );
    product.put("memo", new String("Rock, Pop") );

    return product;
}

스프링4-마이바티스-예제08_케이스3

CASE4. VO를 포함한 List 데이터를 직접 작성해 JSON으로 출력하는 방법

@RequestMapping("sendList")
public @ResponseBody List<SampleVO> sendList(){
    List<SampleVO> list = new ArrayList();
    for( int i=0; i<10; i++ ){
        SampleVO vo = new SampleVO();
        vo.setFirstName("길동"); vo.setLastName("홍"); vo.setMno( i );
        list.add( vo );
    }
    return list;
}

스프링4-마이바티스-예제08_케이스4

CASE5. VO를 포함한 Map 데이터를 직접 작성해 JSON으로 출력하는 방법

@RequestMapping("sendMap")
public @ResponseBody Map<Integer, SampleVO> sendMap(){

    Map<Integer, SampleVO> map = new HashMap<>();
    for( int i=0; i<10; i++ ){
        SampleVO vo = new SampleVO();
        vo.setFirstName("길동");	vo.setLastName("홍"); vo.setMno( i );
        map.put( i, vo );
    }
		
    return map;
}

스프링4-마이바티스-예제08_케이스5

CASE6. resultType으로 VO를 사용해 JSON으로 출력하는 일반적인 방법

@RequestMapping(value="/all/{bno}", method=RequestMethod.GET)
public ResponseEntity<List<ReplyVO>> list(@PathVariable("bno") Integer bno){
		
	ResponseEntity<List<ReplyVO>> entity = null;
	try{
		entity = new ResponseEntity<>(service.listReply(bno), HttpStatus.OK);
	} catch(Exception e){
		e.printStackTrace();
		entity = new ResponseEntity<>( HttpStatus.BAD_REQUEST );
	}
		
	return entity;
}

스프링4-마이바티스-예제08_케이스6

CASE7. resultType으로 HashMap을 사용해 JSON으로 출력하는 간편한 방법

@RequestMapping(value="/listAny/{bno}", method=RequestMethod.GET)
public ResponseEntity<List<HashMap<String, String>>> listAny(@PathVariable("bno") Integer bno){
		
	ResponseEntity<List<HashMap<String, String>>> entity = null;
	try{
		entity = new ResponseEntity<>(service.listAny(bno), HttpStatus.OK);
	} catch(Exception e){
		e.printStackTrace();
		entity = new ResponseEntity<>( HttpStatus.BAD_REQUEST );
	}
		
	return entity;
}

스프링4-마이바티스-예제08_케이스7


MyBatis 설정과 resultType=”HashMap” 사용하기

스프링4-마이바티스-예제01

1. pom.xml 설정

	<!-- for MySQL -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.36</version>
	</dependency>

	<!-- for MyBatis -->
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis</artifactId>
		<version>3.2.8</version>
	</dependency>
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis-spring</artifactId>
		<version>1.2.2</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>${org.springframework-version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>${org.springframework-version}</version>
	</dependency>
		
	<!-- for JSON -->
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
		<version>2.5.4</version>
	</dependency>

	<!-- Test & logging -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.bgee.log4jdbc-log4j2</groupId>
		<artifactId>log4jdbc-log4j2-jdbc4</artifactId>
		<version>1.16</version>
	</dependency>

2. src/main/webapp/WEB-INF/spring/root-context.xml 설정

먼저 관련 모듈별 namespace 를 체크해 포함시킨다. 이후 source 탭에서 내용 수정

스프링4-마이바티스-예제09_rootContext

	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<!--
		/* log4jdbc 모듈 안쓸 때, 순수하게 JDBC 연결 설정만 하는 경우 */	
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://tonynedb.cnklczmjogww.ap-northeast-2.rds.amazonaws.com:3306/spring_exdb"></property> 
	-->
		<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
		<property name="url" value="jdbc:log4jdbc:mysql://tonynedb.cnklczmjogww.ap-northeast-2.rds.amazonaws.com:3306/spring_exdb"></property>
		<property name="username" value="tonyne"></property>
		<property name="password" value="tonyne0909"></property>
	</bean>
	
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
		<property name="configLocation" value="classpath:/config/mybatis-config.xml"></property>
		<property name="mapperLocations" value="classpath:/mapper/*_SQL.xml"></property>
	</bean>

	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
		<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
	</bean>
	
	<context:component-scan base-package="com.tonyne.ex01.dao"></context:component-scan>
	<context:component-scan base-package="com.tonyne.ex01.service"></context:component-scan>

3. (optional) 기타 설정파일 : 로깅, MyBatis의 참조용 namespace 설정

3-1. src/main/resources/log4jdbc.log4j2.properties
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
3-2. src/main/resources/logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/base.xml" />
	
	<!-- log4jdbc-log4j2 -->
	<logger name="jdbc.sqlonly"			level="DEBUG" />
	<logger name="jdbc.sqltiming"			level="INFO" />
	<logger name="jdbc.audit"			level="WARN" />
	<logger name="jdbc.resultset"			level="ERROR" />
	<logger name="jdbc.resultsettable"		level="ERROR" />
	<logger name="jdbc.connection"			level="INFO" />
</configuration>
3-2. src/main/resources/config/mybatis-config.xml
  • root-context.xml의 “configLocation” 항목으로 정의되어 있음
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    
    <configuration>
    	<typeAliases>
    		<!-- <typeAlias type="com.tonyne.ex01.domain.ReplyVO" alias="ReplyVO" /> -->
    		<package name="com.tonyne.ex01.domain" />
    	</typeAliases>
    </configuration>
    

4. mapper *_SQL.xml 작성

  • root-context.xml의 “mapperLocations” 항목으로 정의되어 있음
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.tonyne.ex01.mapper.ReplyMapper">
    	<!--
    	mybatis-config.xml : 긴 namespace를 typeAlias로 짧게 대체할 수 있음 
    	<select id="list" resultType="com.tonyne.ex01.domain.ReplyVO"> 
    	-->
    	<select id="listAny" resultType="java.util.HashMap">
    	select * from tbl_reply where bno = #{bno} order by rno desc
    	</select>
    
    	<select id="list" parameterType="Integer" resultType="ReplyVO">
    	select * from tbl_reply where bno = #{bno} order by rno desc
    	</select>
    </mapper>
    

5. DAO 작성

  • HashMap 사용때는 ReplyVO 클래스 기술이 필요없음
5-1. ReplyDAO.java
package com.tonyne.ex01.dao;

import ...;

public interface ReplyDAO {
	public List<HashMap<String, String>> listAny(Integer bno) throws Exception;
	public List<ReplyVO> list(Integer bno) throws Exception;
}

 

5-2. ReplyDAOImpl.java
package com.tonyne.ex01.dao;

import ...;

@Repository
public class ReplyDAOImpl implements ReplyDAO {
    @Inject
    private SqlSession session;

    private static String namespace = "com.tonyne.ex01.mapper.ReplyMapper";

    @Override
    public List<ReplyVO> list(Integer bno) throws Exception {
        return session.selectList(namespace+".list", bno);
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<HashMap<String, String>> listAny(Integer bno) throws Exception {
        return session.selectList(namespace+".listAny", bno);
    }
}

6. Service 작성

6-1. ReplyService.java
package com.tonyne.ex01.service;

import ...;

public interface ReplyService {
	public List<HashMap<String, String>> listAny(Integer bno) throws Exception;
	public List<ReplyVO> listReply(Integer bno) throws Exception;
}

6-2. ReplyServiceImpl.java

package com.tonyne.ex01.service;

import ...;

@Service
public class ReplyServiceImpl implements ReplyService {
    @Inject
    private ReplyDAO dao;

    @Override
    public List<ReplyVO> listReply(Integer bno) throws Exception {
        return dao.list(bno);
    }

    @Override
    public List<HashMap<String, String>> listAny(Integer bno) throws Exception {
        return dao.listAny(bno);
    }
}

7. Controller 작성

package com.tonyne.ex01.web;

import ...;

@RestController
@RequestMapping("replies")
public class ReplyController {

    @Inject
    private ReplyService service;

	@RequestMapping(value="/all/{bno}", method=RequestMethod.GET)
	public ResponseEntity<List<ReplyVO>> list(@PathVariable("bno") Integer bno){
		
		ResponseEntity<List<ReplyVO>> entity = null;
		try{
			entity = new ResponseEntity<>(service.listReply(bno), HttpStatus.OK);
		} catch(Exception e){
			e.printStackTrace();
			entity = new ResponseEntity<>( HttpStatus.BAD_REQUEST );
		}
		
		return entity;
	}

	@RequestMapping(value="/listAny/{bno}", method=RequestMethod.GET)
	public ResponseEntity<List<HashMap<String, String>>> listAny(@PathVariable("bno") Integer bno){
		
		ResponseEntity<List<HashMap<String, String>>> entity = null;
		try{
			entity = new ResponseEntity<>(service.listAny(bno), HttpStatus.OK);
		} catch(Exception e){
			e.printStackTrace();
			entity = new ResponseEntity<>( HttpStatus.BAD_REQUEST );
		}
		
		return entity;
	}
}

 

웹서버는 sts 개발도구의 기본 TC 웹서버를 이용했다. (Tomcat7 또는 Tomcat8 써도 OK)

Log4jdbc 모듈을 사용하게 되면 DB 관련 모든 로깅을 살펴볼 수 있다.
– MySQL에 접속해 ‘select now()’ 실행하는 테스트 코드의 로그

스프링4-마이바티스-예제05_DB로깅

개인 개발환경에 java를 여러버전 깔았다면, JRE 버전이 제대로 선택 안되었을 경우가 생긴다.
그러면 Java Library의 Build Path를 직접 확인할 것!

스프링4-마이바티스-예제06_자바빌드패스

이클립스(or STS) 개발시 탭보다는 공백이 좋다. (Window > Preference 의 Java 섹션 설정)

스프링4-마이바티스-예제07_탭공백설정

즐코딩~

오픈소스

[OpenLayers 2] 좌표변환 with Proj4js

GeoServer의 WMS 서비스를 이용해 지도 애플리케이션을 제작하는 경우
‘OpenLayers2’를 아직도 많이 사용한다.
* 참고 OpenLayers 2 – JavaScript Mapping Library

특히 관공서의 지도 애플리케이션들이 그러하고
좌표체계도 일반적인 WGS84(EPSG:4326)이 아닌 GRS80 한국형 좌표체계를 사용한다.
* 한국형이라고 나쁜게 아니다. 한반도 지형과 가장 잘맞는 투영도법을 쓴다는 뜻이다.

따라서 지도를 그리기 위해 사용할 OSM 소스의 EPSG:3857과 달라
좌표변환을 필수적으로 해야 한다.
** 과거 OSM 좌표로 사용했던 EPSG:900913는 없어졌다. EPSG:3857로 표기해야 함
==> 참조 Proj4js Problem in converting coordinates from google mercator

좌표변환을 위해 OpenLayers2에서 사용하는 라이브러리는 Proj4js 이다.
* OpenLayers3에서는 라이브러리가 통합되어 ol.proj 로 제공되고 있음

  Proj4js.defs["EPSG:4019"] = "+proj=longlat +ellps=GRS80 +no_defs";
  Proj4js.defs["EPSG:3857"] = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs";
  Proj4js.defs["EPSG:900913"] = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs";
  //UTM-K
  Proj4js.defs["EPSG:5179"] = "+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +units=m +no_defs";
  //중부원점(50만)
  Proj4js.defs["EPSG:5181"]="+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=500000 +ellps=GRS80 +units=m +no_defs";
  //서부원점
  Proj4js.defs["EPSG:5185"] = "+proj=tmerc +lat_0=38 +lon_0=125 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs";
  //중부원점-o
  Proj4js.defs["EPSG:5186"] = "+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs";
  //동부원점
  Proj4js.defs["EPSG:5187"] = "+proj=tmerc +lat_0=38 +lon_0=129 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs";
  //동해(울릉)원점
  Proj4js.defs["EPSG:5188"] = "+proj=tmerc +lat_0=38 +lon_0=131 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs";

  // projection for WGS84
  var projWGS84 = new OpenLayers.Projection("EPSG:4326");
  // projection for WMS
  var projWMS = new OpenLayers.Projection("EPSG:5186");
  // projection for OSM
  var projOSM = new OpenLayers.Projection("EPSG:3857");

  //초기 페이지 지도 중심점 (EPSG:5186 -> EPSG:3857) 
  // ex) 제주도청 [EPSG:4326] 126.498197, 33.489004 
  //	<==> [EPSG:5186] 99597.5637890962, 153365.05908124548
  //	<==> [EPSG:3857] 3960392.400920103, 14081714.876307208
  try{
    var centerWGS84 = new OpenLayers.LonLat( 126.498197, 33.489004 );
    var center4WMS = centerWGS84.transform( projWGS84, projWMS );
    console.log( 'center4WMS : '+center4WMS.toShortString() );
    //=> center4WMS : 153365.05908124548, 99597.5637890962

    var centerWGS84 = new OpenLayers.LonLat( 126.498197, 33.489004 );
    var center4OSM = centerWGS84.transform( projWGS84, projOSM );
    console.log( 'center4OSM : '+center4OSM.toShortString() );
    //=> center4OSM : 14081714.876307208, 3960392.400920103

    var center4WMS = new OpenLayers.LonLat( 153365.05908124548, 99597.5637890962 );
    var mapCenter = center4WMS.transform( projWMS, projOSM );
    console.log( 'mapCenter : '+mapCenter.toShortString() );
    //=> mapCenter : 14081714.876307214, 3960392.400919703
  }catch(e){}

** 특이사항
왜 그런지는 모르겠지만..
projWGS84 에서 projOSM 변환시 원점인 centerWGS84 변수를 다시 선언하지 않고
앞에 썼던거 그대로 다시 사용하면 transform() 함수가 비정상 작동한다.
centerWGS84 변수의 재선언 라인을 주석처리하면 다음과 같이 출력된다.
“center4OSM : 17032445265.718983, NaN”

변수를 참조해서 값을 직접 조작하는 탓인듯 한데, 정리하자면
OpenLayers.LonLat의 transform() 함수 사용시 좌표변수를 반드시 재선언해야 한다!

Proj4js에도 포인트와 좌표변환 함수가 있다. (같은 결과)
* 좌표 new Proj4js.Proj(“EPSG:4326”);
* 포인트 new Proj4js.Point( 85286.417057415703, 4347668.9422677439 );
* 변환 Proj4js.transform(source, dest, point);

개발일반오픈소스

Java8 메모리 설정과 GeoServer

지도 렌더링 서비스를 제공하는 GeoServer를 위한 메모리 설정이라고 했지만,
Java8을 사용하는 모든 애플리케이션에 해당한다.

Java8부터는 메모리 설정시 ‘-XX:PermSize= -XX:MaxPermSize=’를 사용하지 않는다.
대신, ‘-XX:MetaspaceSize= -XX:MaxMetaspaceSize=’를 사용한다.

Perm 영역에 대한 관리 문제(Out of Memory와 GC 성능)로 Perm+Native 영역을 통합해
Metaspace 영역으로 제공한다.

이로인한 장점은,
PermGen 영역이 삭제되어 heap 영역에서 사용할 수 있는 메모리가 늘어났다.
PermGen 영역을 삭제하기 위해 존재했던 여러 복잡한 코드들이 삭제 되고 PermGen영역을 스캔 하기 위해 소모되었던 시간이 감소되어 GC 성능이 향상 되었다.
** 출처: JDK8에선 PermGen이 완전히 사라지고 Metaspace가 이를 대신 함

정리하면
Java7 메모리 구성: new/ survive/ old/ perm/ native
Java8 에서는: new/ survive/ old/ metaspace 로 변경되었음
** 참고: JAVA8 Permanent 영역은 어디로 갔는가

이를 GeoServer에 적용해 보면,

설치후 기본 메타정보를 조회 요청 한번 하면, 메모리 문제로 뻗어 버린다.

http://localhost:8080/geoserver/wms?
service=wms&
version=1.1.1&
request=GetCapabilities

서버 로그에는 다음과 같이 남아 있다.

Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000f925f000, 39886848, 0) failed; error='메모리를 없습니다' (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 39886848 bytes for committing reserved memory.
# An error report file with more information is saved as:

JVM 메모리 문제이므로, Java의 실행옵션을 변경해 주면 된다.
* 수정대상: “/bin/startup.sh”

# (생략)....................

# if not told otherwise pump up the permgen
if [ -z '$JAVA_OPTS' ]; then
  #JVM Option for JRE 7 and lower version
  #export JAVA_OPTS='-XX:MaxPermSize=128m'

  #JVM Option for JRE 8 &lt;== 요기다!!
  export JAVA_OPTS='-XX:MaxMetaspaceSize=512m -XX:MetaspaceSize=256m'
fi 

cd '$GEOSERVER_HOME'

echo 'GEOSERVER DATA DIR is $GEOSERVER_DATA_DIR'
#added headless to true by default, if this messes anyone up let the list
#know and we can change it back, but it seems like it won't hurt -ch
exec '$_RUNJAVA' $JAVA_OPTS -DGEOSERVER_DATA_DIR='$GEOSERVER_DATA_DIR' -Djava.awt.headless=true -DSTOP.PORT=8079 -DSTOP.KE

메모리를 늘린만큼 서버가 굼떠지겠지만, 암튼 죽지않고 작동된다.

캡처-ReactJS-샘플01
React.js

React.js 시작하기 (on AWS EC2)

“프로 리액트”라는 책을 사서 React.js 배우기를 시작했습니다.

우선 셋팅을 해야 하는데, 웹서버로 webpack을 사용하고 있기 때문에
아무것도 없어도 작동이 가능합니다. (설치과정에서 자동 다운로드)

윈도우 환경에서 잘 작동되고, AWS EC2에서도 잘 작동됩니다.
* EC2의 Security Groups에서 inbound Rule에 사용할 포트를 열어야 함

캡처-ReactJS-샘플01

샘플을 실행하면 무조건 ‘localhost:8080’에서만 작동되는데
public access가 가능하게 하려면, webpack의 host를 ‘0.0.0.0’로 설정해야 합니다.
* 참고문서
http://stackoverflow.com/questions/33272967/how-to-make-the-webpack-dev-server-run-on-port-80-and-on-0-0-0-0-to-make-it-publ/33273459#33273459

[ 설정파일들 ]

  • package.json
    {
      "name": "react-app-boilerplate",
      "version": "0.1.4",
      "description": "React application boilerplate",
      "author": "Cássio Zen",
      "license": "ISC",
      "scripts": {
        "start": "webpack-dev-server --progress --host 0.0.0.0 --port 8088",
        "build": "NODE_ENV=production node_modules/.bin/webpack -p --progress --colors"
      },
      "devDependencies": {
        "babel-core": "~6.7.*",
        "babel-loader": "~6.2.*",
        "babel-preset-es2015": "~6.6.*",
        "babel-preset-react": "~6.5.*",
        "webpack": "~1.12.*",
        "webpack-dev-server": "~1.14.*"
      },
      "dependencies": {
        "react": "^15.0.0",
        "react-dom": "^15.0.0"
      }
    }
    
  • webpack.config.js
    var webpack = require('webpack');
    
    /*
     * Default webpack configuration for development
     */
    var config = {
      devtool: 'eval-source-map',
      entry:  [
         'webpack-dev-server/client?http://0.0.0.0:8088',
          __dirname + "/app/App.js"
        ],
      output: {
        path: __dirname + "/public",
        filename: "bundle.js"
      },
      module: {
        loaders: [{
          test: /\.jsx?$/,
          exclude: /node_modules/,
          loader: 'babel',
          query: {
            presets: ['es2015','react']
          }
        }]
      },
      devServer: {
        contentBase: "./public",
        colors: true,
        historyApiFallback: true,
        inline: true,
        port: 8088
      },
    }
    
    /*
     * If bundling for production, optimize output
     */
    if (process.env.NODE_ENV === 'production') {
      config.devtool = false;
      config.plugins = [
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin({comments: false}),
        new webpack.DefinePlugin({
          'process.env': {NODE_ENV: JSON.stringify('production')}
        })
      ];
    };
    
    module.exports = config;
    
    

[ 소스파일들 ]

  • App.js
    import React, { Component } from 'react';
    import {render} from 'react-dom';
    
    import GroceryList from './GroceryList.js';
    
    
    class App extends Component {
    
    	render(){
    		var place = "World!!";
    		return (
    			<h1>Hello {place}</h1>
    		);
    	}
    }
    
    render(<App />, document.getElementById('root'));
    
  • GroceryList.js
    import React, { Component } from 'react';
    import {render} from 'react-dom';
    
    class GroceryList extends Component {
    
    	render(){
    		return (
    			<ul>
    				<ListItem quantity="1" name="Bread" />
    				<ListItem quantity="5" name="Eggs" />
    				<ListItem quantity="6" name="Cheases" />
    				<ListItem quantity="2" name="Milk" />
    			</ul>
    		);
    	}
    }
    
    class ListItem extends Component {
    
    	render(){
    		return(
    			<li>
    				{this.props.quantity} x {this.props.name}
    			</li>
    		);
    	}
    }
    
    render(<GroceryList />, document.getElementById('GroceryList'));
    
    
  • index.html
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title></title>
        <link rel="stylesheet" href="styles.css">
      </head>
      <body>
        <div id='root'></div>
        <div id='GroceryList'></div>
    
        <script src="bundle.js"></script>
      </body>
    </html>
    
    

 

작동해 보니 js 파일을 변경할 때마다, 브라우져단도 알아서 휙휙 변경이 되더군요.
확실히 (모던 스타일의 프론트 개발을 위해서) 사용해볼한 호기심을 주네요.
좀더 공부해 보겠습니다.

 

캡처-로그인_전체(편집)
오픈소스

다양한 소셜 로그인 가능한 OAuth 라이브러리(Java)

ScribeJava, the simple OAuth Java lib

다양한 소셜 로그인이 가능한 ScribeJava를 소개합니다.

웹서비스를 제작할 때 다양한 SNS 서비스의 OAuth를 이용하는 경우가 많은데
이것들을 하나로 처리할 수 있는 통합 라이브러리입니다.

[기능 및 특징]

  • 심플하다
  • Thread-safe
  • Async
  • facebook, google, Sina, Tweeter, Yahoo, Pinterest 등 36개 SNS 로그인 지원
  • 작고 모듈화 됨 (약 1k LOC)
  • 안드로이드앱 지원
  • 안전성 & 오류처리
  • Maven 지원 (pom.xml)

[사용법]

  1. pom.xml 에 dependency 등록
  2. OAuthService 오브젝트 생성
  3. request 토큰 가져오기
  4. OAuth call로 서비스할 앱에 대한 권한 허용을 사용자가 하면
  5. 로그인 처리를 수행할 URL을 던져준다
  6. 발급된 AccessToken을 가져와서
  7. 로그인처리 URL을 AccessToken과 함께 요청한다
  8. 이후는 자신의 서비스 로직대로 진행

[사용예]

  • 프론트 (HTML/JS)
  •     <ul class="snsLogin">
            <li class="facebook"><a href="/snsGateway.do?mode=fb&returnUrl="><span></span><strong>페이스북</strong>으로
                            로그인</a></li>
            <li class="kakao"><a href="/snsGateway.do?mode=kakao&returnUrl="><span></span><strong>카카오톡</strong>으로
                            로그인</a></li>
            <li class="instagram"><a href="/snsGateway.do?mode=instagram&returnUrl="><span></span><strong>인스타그램</strong>으로
                            로그인</a></li>
        </ul>
    
  • 웹 콘트롤러 (Java)
  • import com.github.scribejava.core.model.*;
    import com.github.scribejava.core.oauth.OAuth10aService;
    import com.github.scribejava.core.oauth.OAuth20Service;
    
    /* ... */
    
        @RequestMapping("/snsGateway.do")
        public ModelAndView snsLogin(@RequestParam String mode,
                                     @RequestParam(required = false, value = "returnUrl") String returnUrl) {
            SnsServiceFactory factory = new SnsServiceFactory(request);
    
            ModelAndView mav = new ModelAndView();
    
            if (returnUrl != null) {
                request.getSession().setAttribute("returnUrl", returnUrl);
            }
    
            if ("fb".equals(mode)) {
                mav.setViewName("redirect:" + factory.facebook().getAuthorizationUrl());
            } else if ("instagram".equals(mode)) {
                mav.setViewName("redirect:" + factory.instagram().getAuthorizationUrl());
            } else if ("kakao".equals(mode)) {
                mav.setViewName("redirect:" + factory.kakao().getAuthorizationUrl());
            } else if ("line".equals(mode)) {
                mav.setViewName("redirect:" + factory.line().getAuthorizationUrl());
            } else if ("twitter".equals(mode)) {
                OAuth10aService service = factory.twitter();
                OAuth1RequestToken requestToken = service.getRequestToken();
                String u = service.getAuthorizationUrl(requestToken);
    
                request.getSession().setAttribute("tw_requestToken", requestToken);
    
                mav.setViewName("redirect:" + u);
            } else if ("google".equals(mode)) {
                mav.setViewName("redirect:" + factory.google().getAuthorizationUrl());
            } else if ("weibo".equals(mode)) {
                mav.setViewName("redirect:" + factory.weibo().getAuthorizationUrl());
            } else if ("wechat".equals(mode)) {
                mav.setViewName("redirect:" + factory.wechat().getAuthorizationUrl());
            }
    
            return mav;
        }
    
  • 서비스 팩토리 (Java)
  • import com.github.scribejava.apis.FacebookApi;
    import com.github.scribejava.apis.GoogleApi20;
    import com.github.scribejava.apis.TwitterApi;
    import com.github.scribejava.core.builder.ServiceBuilder;
    import com.github.scribejava.core.oauth.OAuth10aService;
    import com.github.scribejava.core.oauth.OAuth20Service;
    
    public class SnsConfigParameter {
        private String clientId;
        private String client_secret;
        private String redirect_uri;
    
        // ...
    };
    
    public class SnsServiceFactory {
        private HttpServletRequest request;
    
        public SnsServiceFactory(HttpServletRequest request) {
            this.request = request;
        }
    
        public OAuth10aService twitter() {
            SnsConfigParameter t = SnsConfig.getInstance(request).twitter();
    
            return new ServiceBuilder()
                    .apiKey(t.getClientId())
                    .apiSecret(t.getClient_secret())
                    .callback(t.getRedirect_uri())
                    .build(TwitterApi.instance());
        }
    
        public SnsService kakao() {
            SnsConfigParameter t = SnsConfig.getInstance(request).kakao();
    
            return new ServiceBuilder()
                    .apiKey(t.getClientId())
                    .grantType("authorization_code")
                    .callback(t.getRedirect_uri())
                    .build(KakaoApi.instance());
        }
    
        public SnsService weibo() {
            SnsConfigParameter t = SnsConfig.getInstance(request).weibo();
    
            return new ServiceBuilder()
                    .apiKey(t.getClientId())
                    .apiSecret(t.getClient_secret())
                    .grantType("authorization_code")
                    .callback(t.getRedirect_uri())
                    .build(WeiboApi.instance());
        }
    
        public SnsService instagram() {
            SnsConfigParameter t = SnsConfig.getInstance(request).instagram();
    
            return new ServiceBuilder()
                    .apiKey(t.getClientId())
                    .apiSecret(t.getClient_secret())
                    .grantType("authorization_code")
                    .callback(t.getRedirect_uri())
                    .build(InstagramApi.instance());
        }
    
        public OAuth20Service facebook() {
            SnsConfigParameter t = SnsConfig.getInstance(request).facebook();
    
            return new ServiceBuilder()
                    .apiKey(t.getClientId())
                    .apiSecret(t.getClient_secret())
                    .scope("email")
                    .callback(t.getRedirect_uri())
                    .build(FacebookApi.instance());
        }
    
        public SnsService line() {
            SnsConfigParameter t = SnsConfig.getInstance(request).line();
    
            return new ServiceBuilder()
                    .apiKey(t.getClientId())
                    .apiSecret(t.getClient_secret())
                    .grantType("authorization_code")
                    .callback(t.getRedirect_uri())
                    .build(LineApi.instance());
        }
    
        public OAuth20Service google() {
            SnsConfigParameter t = SnsConfig.getInstance(request).google();
    
            return new ServiceBuilder()
                    .apiKey(t.getClientId())
                    .apiSecret(t.getClient_secret())
                    .scope("profile")
                    .callback(t.getRedirect_uri())
                    .build(GoogleApi20.instance());
        }
    
        public SnsService wechat() {
            SnsConfigParameter t = SnsConfig.getInstance(request).wechat();
    
            return new ServiceBuilder()
                    .apiKey(t.getClientId())
                    .apiSecret(t.getClient_secret())
                    .callback(t.getRedirect_uri())
                    .scope("snsapi_login")
                    .grantType("authorization_code")
                    .build(WechatApi.instance());
        }
    }
    
  • API 설정 파라미터 (Java)
  • public class SnsConfig {
        private HttpServletRequest request;
    
        public String getHostName() {
            String scheme = request.getScheme();
            String serverName = request.getServerName();
    
            return scheme + "://" + serverName;
        }
    
        public String getRedirectUrl(String snsType) {
            return getHostName() + "/oAuthCallback.do?m=" + snsType;
        }
    
        public SnsConfigParameter line() {
            return new SnsConfigParameter( /* ... */ );
        }
    
        public SnsConfigParameter kakao() {
            return new SnsConfigParameter( /* ... */ );
        }
    
        public SnsConfigParameter facebook() {
            return new SnsConfigParameter( /* ... */ );
        }
    
        public SnsConfigParameter instagram() {
            return new SnsConfigParameter( /* ... */ );
        }
    
        public SnsConfigParameter twitter() {
            return new SnsConfigParameter( /* ... */ );
        }
    
        public SnsConfigParameter google() {
            return new SnsConfigParameter( /* ... */ );
        }
    
        public SnsConfigParameter weibo() {
            return new SnsConfigParameter( /* ... */ );
        }
    
        public SnsConfigParameter wechat() {
            return new SnsConfigParameter( /* ... */ );
        }
    
        private static SnsConfig instance = new SnsConfig();
    
        public void setRequest(HttpServletRequest request) {
            this.request = request;
        }
    
        public static SnsConfig getInstance(HttpServletRequest request) {
            instance.setRequest(request);
    
            return instance;
        }
    
        public final static SnsConfigParameter FACEBOOK = new SnsConfigParameter( /* ... */ );
    
        public final static SnsConfigParameter INSTAGRAM = new SnsConfigParameter( /* ... */ );
    
        public final static SnsConfigParameter KAKAO = new SnsConfigParameter( /* ... */ );
    
        public final static SnsConfigParameter TWITTER = new SnsConfigParameter( /* ... */ );
    
        public final static SnsConfigParameter GOOGLE = new SnsConfigParameter( /* ... */ );
    
        public final static SnsConfigParameter WEIBO = new SnsConfigParameter( /* ... */ );
    }
    
  • 웹콘트롤러 (java)
  •     public Map getTokenMap() {
            return request.getSession().getAttribute(TOKEN_KEY) == null ?
                    new HashMap() : (Map) request.getSession().getAttribute(TOKEN_KEY);
        }
    
        @RequestMapping("/oAuthCallback.do")
        public String oauthCallback(@RequestParam String m, HttpServletResponse response) {
            SnsServiceFactory factory = new SnsServiceFactory(request);
    
            String code = request.getParameter("code");
    
            JSONObject result;
            String profile_image = null;
            String id = null;
            String name = null;
    
            try {
                if ("fb".equals(m)) {
                    OAuth20Service service = factory.facebook();
                    OAuth2AccessToken accessToken = service.getAccessToken(code);
    
                    getTokenMap().put(m, accessToken);
    
                    OAuthRequest request = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v2.5/me", service);
                    service.signRequest(accessToken, request);
                    result = new JSONObject(request.send().getBody());
    
                    OAuthRequest request2 = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v2.5/me?fields=picture.type(large)", service);
                    service.signRequest(accessToken, request2);
                    JSONObject result2 = new JSONObject(request2.send().getBody());
                    result = JsonUtils.merge(result, result2);
    
                    id = result.getString("id");
                    name = result.getString("name");
                    profile_image = result.getJSONObject("picture").getJSONObject("data").getString("url");
                } 
                else if ("google".equals(m)) {
                    OAuth20Service service = factory.google();
                    OAuth2AccessToken accessToken = service.getAccessToken(code);
                    getTokenMap().put(m, accessToken);
    
                    OAuthRequest request = new OAuthRequest(Verb.GET, "https://www.googleapis.com/plus/v1/people/me", service);
                    service.signRequest(accessToken, request);
                    result = new JSONObject(request.send().getBody());
    
                    id = String.valueOf(result.get("id"));
                    name = result.getString("displayName");
                    profile_image = result.getJSONObject("image").getString("url");
                } 
                else if ("twitter".equals(m)) {
                    OAuth1RequestToken requestToken = (OAuth1RequestToken) request.getSession().getAttribute("tw_requestToken");
    
                    OAuth10aService service = factory.twitter();
                    String oauthVerifier = request.getParameter("oauth_verifier");
                    OAuth1AccessToken accessToken = service.getAccessToken(requestToken, oauthVerifier);
                    getTokenMap().put(m, accessToken);
    
                    OAuthRequest request = new OAuthRequest(Verb.GET, "https://api.twitter.com/1.1/account/verify_credentials.json", service);
                    service.signRequest(accessToken, request);
                    result = new JSONObject(request.send().getBody());
    
                    id = String.valueOf(result.get("id"));
                    name = result.getString("name");
                    profile_image = result.getString("profile_image_url_https");
                } 
                else if ("instagram".equals(m)) {
                    SnsService service = factory.instagram();
                    result = requestSns(code, "https://api.instagram.com/v1/users/self", service);
    
                    id = result.getJSONObject("data").getString("id");
                    name = result.getJSONObject("data").getString("username");
                    profile_image = result.getJSONObject("data").getString("profile_picture");
                } 
                else if ("kakao".equals(m)) {
                    SnsService service = factory.kakao();
                    result = requestSns(code, "https://kapi.kakao.com/v1/user/me", service);
    
                    id = String.valueOf(result.get("id"));
                    name = result.getJSONObject("properties").getString("nickname");
                    profile_image = result.getJSONObject("properties").getString("profile_image");
                } else if ("weibo".equals(m)) {
                    SnsService service = factory.weibo();
                    String url = "https://api.weibo.com/2/users/show.json";
    
                    OAuth2AccessToken accessToken = service.getAccessToken(code);
                    SnsRequest request = new SnsRequest(Verb.GET, url, service);
                    request.addParameter("access_token", accessToken.getAccessToken());
                    request.addParameter("uid", String.valueOf(service.getResponseJson().get("uid")));
                    result = new JSONObject(request.send().getBody());
    
                    id = String.valueOf(result.get("id"));
                    name = result.getString("screen_name");
                    profile_image = result.getString("avatar_large");
                } 
                else if ("line".equals(m)) {
                    SnsService service = factory.line();
                    result = requestSns(code, "https://api.line.me/v1/profile", service);
    
                    id = String.valueOf(result.get("mid"));
                    name = result.getString("displayName");
                    profile_image = result.getString("pictureUrl");
                } 
                else if ("wechat".equals(m)) {
                    SnsService service = factory.wechat();
    
                    Map<String, String> data = new HashMap<String, String>();
                    data.put("appid", service.getConfig().getApiKey());
                    data.put("secret", service.getConfig().getApiSecret());
    
                    OAuth2AccessToken accessToken = service.getAccessToken(code, data);
                    String url = "https://api.weixin.qq.com/sns/userinfo";
    
                    SnsRequest request = new SnsRequest(Verb.GET, url, service);
                    request.addParameter("access_token", accessToken.getAccessToken());
                    request.addParameter("openid", String.valueOf(service.getResponseJson().get("openid")));
                    result = new JSONObject(request.send().getBody());
    
                    id = String.valueOf(result.get("openid"));
                    name = result.getString("nickname");
                    profile_image = result.getString("headimgurl");
                }
    
                User user = new User();
                user.setId(id);
                user.setName(name);
                user.setPicture(profile_image);
                user.setSnsType(m);
    
                MbrManageVO mbrUser = mbrManageService.getUserInfoMbrId(user.getUniqueID());
    
                if (mberUser == null) {
                    // 임의 사용자 처리
                } 
                request.getSession().setAttribute("mbrUser", mbrUser);
    
                // 토큰 발급 
    
                Authentication authentication = this.authenticationManager.authenticate(authToken);
                this.persistRealNameAuthentication(authentication, request.getSession());
                this.rememberMeServices.loginSuccess(request, response, realAuthToken);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            String returnUrl = (String) request.getSession().getAttribute("returnUrl");
            request.getSession().removeAttribute("returnUrl");
            if (!StringUtils.isEmpty(returnUrl)) {
                return "redirect:" + returnUrl;
            }
    
            if ("mobile".equals(request.getSession().getAttribute("loginMode"))) {
                return "redirect:/mobile/main.do";
            } else {
                return "redirect:/index.do";
            }
        }
    
        protected JSONObject requestSns(String code, String url, SnsService service) throws Exception {
            OAuth2AccessToken accessToken = service.getAccessToken(code);
            OAuthRequest request = new OAuthRequest(Verb.GET, url, service);
    
            request.addHeader("Authorization", accessToken.getAccessToken());
            service.signRequest(accessToken, request);
    
            return new JSONObject(request.send().getBody());
        }
    
  • SNS별 API (Java)
    ** 없는건 형식에 맞춰 만들어 사용
  • import com.github.scribejava.core.builder.api.BaseApi;
    import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
    import com.github.scribejava.core.extractors.TokenExtractor;
    import com.github.scribejava.core.model.OAuth2AccessToken;
    import com.github.scribejava.core.model.Verb;
    import com.github.scribejava.core.builder.api.DefaultApi20;
    import com.github.scribejava.core.model.OAuthConfig;
    import com.github.scribejava.core.model.Verb;
    import com.github.scribejava.core.utils.OAuthEncoder;
    import com.github.scribejava.core.utils.Preconditions;
    
    public abstract class SnsApi implements BaseApi<SnsService> {
        public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
            return OAuth2AccessTokenJsonExtractor.instance();
        }
    
        public Verb getAccessTokenVerb() {
            return Verb.POST;
        }
    
        public abstract String getAccessTokenEndpoint();
    
        public abstract String getAuthorizationUrl(OAuthConfig var1);
    
        public String getAuthorizationUrl(OAuthConfig config, Map<String, String> additionalParams) {
            String authUrl = this.getAuthorizationUrl(config);
    
            if (additionalParams != null && !additionalParams.isEmpty()) {
                StringBuilder authUrlWithParams = (new StringBuilder(authUrl)).append(authUrl.indexOf(63) == -1 ? '?' : '&');
    
                for (Object o : additionalParams.entrySet()) {
                    Map.Entry param = (Map.Entry) o;
                    authUrlWithParams.append(OAuthEncoder.encode((String) param.getKey())).append('=').append(OAuthEncoder.encode((String) param.getValue())).append('&');
                }
    
                authUrl = authUrlWithParams.substring(0, authUrlWithParams.length() - 1);
            }
    
            return authUrl;
        }
    
        public SnsService createService(OAuthConfig config) {
            return new SnsService(this, config);
        }
    }
    
    public class WechatApi extends SnsApi {
        public static WechatApi instance() {
            return WechatApi.InstanceHolder.INSTANCE;
        }
    
        public Verb getAccessTokenVerb() {
            return Verb.POST;
        }
    
        public String getAccessTokenEndpoint() {
            return "https://api.weixin.qq.com/sns/oauth2/access_token";
        }
    
        public String getAuthorizationUrl(OAuthConfig config) {
            StringBuilder sb = new StringBuilder(String.format("https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code", new Object[]{config.getApiKey(), OAuthEncoder.encode(config.getCallback())}));
    
            if (config.hasScope()) {
                sb.append('&').append("scope").append('=').append(OAuthEncoder.encode(config.getScope()));
            }
    
            String state = config.getState();
            if (state != null) {
                sb.append('&').append("state").append('=').append(OAuthEncoder.encode(state));
            }
    
            return sb.toString();
        }
    
        private static class InstanceHolder {
            private static final WechatApi INSTANCE = new WechatApi();
        }
    }
    
    ////////////////////////////////////////////////////////
    import com.github.scribejava.core.model.OAuthConfig;
    import com.github.scribejava.core.model.Verb;
    import com.github.scribejava.core.utils.OAuthEncoder;
    import com.github.scribejava.core.utils.Preconditions;
    
    public class LineApi extends SnsApi {
        public static LineApi instance() {
            return LineApi.InstanceHolder.INSTANCE;
        }
    
        public Verb getAccessTokenVerb() {
            return Verb.POST;
        }
    
        public String getAccessTokenEndpoint() {
            return "https://api.line.me/v1/oauth/accessToken";
        }
    
        public String getRefreshTokenEndpoint() {
            throw new UnsupportedOperationException("Line doesn\'t support refershing tokens");
        }
    
        public String getAuthorizationUrl(OAuthConfig config) {
            Preconditions.checkValidUrl(config.getCallback(), "Must provide a valid url as callback. Kakao does not support OOB");
    
            StringBuilder sb = new StringBuilder(String.format("https://access.line.me/dialog/oauth/weblogin?client_id=%s&redirect_uri=%s&response_type=code", new Object[]{config.getApiKey(), OAuthEncoder.encode(config.getCallback())}));
    
            if (config.hasScope()) {
                sb.append('&').append("scope").append('=').append(OAuthEncoder.encode(config.getScope()));
            }
    
            String state = config.getState();
            if (state != null) {
                sb.append('&').append("state").append('=').append(OAuthEncoder.encode(state));
            }
    
            return sb.toString();
        }
    
        private static class InstanceHolder {
            private static final LineApi INSTANCE = new LineApi();
    
            private InstanceHolder() {
            }
        }
    }
    
    ////////////////////////////////////////////////////////
    import com.github.scribejava.core.model.OAuthConfig;
    import com.github.scribejava.core.model.Verb;
    import com.github.scribejava.core.utils.OAuthEncoder;
    
    public class KakaoApi extends SnsApi {
        public static KakaoApi instance() {
            return KakaoApi.InstanceHolder.INSTANCE;
        }
    
        public Verb getAccessTokenVerb() {
            return Verb.POST;
        }
    
        public String getAccessTokenEndpoint() {
            return "https://kauth.kakao.com/oauth/token";
        }
    
        public String getAuthorizationUrl(OAuthConfig config) {
            StringBuilder sb = new StringBuilder(String.format("https://kauth.kakao.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code", new Object[]{config.getApiKey(), OAuthEncoder.encode(config.getCallback())}));
    
            if (config.hasScope()) {
                sb.append('&').append("scope").append('=').append(OAuthEncoder.encode(config.getScope()));
            }
    
            String state = config.getState();
            if (state != null) {
                sb.append('&').append("state").append('=').append(OAuthEncoder.encode(state));
            }
    
            return sb.toString();
        }
    
        private static class InstanceHolder {
            private static final KakaoApi INSTANCE = new KakaoApi();
        }
    }
    
    ////////////////////////////////////////////////////////
    import com.github.scribejava.core.model.OAuthConfig;
    import com.github.scribejava.core.model.Verb;
    import com.github.scribejava.core.utils.OAuthEncoder;
    
    public class WeiboApi extends SnsApi {
        protected WeiboApi() {
        }
    
        public static WeiboApi instance() {
            return WeiboApi.InstanceHolder.INSTANCE;
        }
    
        public Verb getAccessTokenVerb() {
            return Verb.POST;
        }
    
        public String getAccessTokenEndpoint() {
            return "https://api.weibo.com/oauth2/access_token";
        }
    
        public String getAuthorizationUrl(OAuthConfig config) {
            StringBuilder sb = new StringBuilder(String.format("https://api.weibo.com/oauth2/authorize?client_id=%s&redirect_uri=%s&response_type=code", new Object[]{config.getApiKey(), OAuthEncoder.encode(config.getCallback())}));
    
            if (config.hasScope()) {
                sb.append('&').append("scope").append('=').append(OAuthEncoder.encode(config.getScope()));
            }
    
            String state = config.getState();
            if (state != null) {
                sb.append('&').append("state").append('=').append(OAuthEncoder.encode(state));
            }
    
            return sb.toString();
        }
    
        private static class InstanceHolder {
            private static final WeiboApi INSTANCE = new WeiboApi();
        }
    }