GXT RIA App 만들기2011. 3. 1. 23:08
오늘은 GWT에서 Service라는 개념에 대해 알아보고 실습해보자. GWT에서는 서버와 통신(RPC)하는 하나의 행위를 서비스라는 이름으로 불리운다. 지금까지 배웠던 부분들은 모두 Client단으로 최종 javascript로 변환되어 사용자에게 UI로 보여지는 것이다. 그러나 화면만으로는 프로그램이 데이터를 가질 수 없으므로 서버단과 통신하는 무엇인가가 필요한데 이것을 Service라 지칭한다.
서비스는 아래와 같이 2개의 인터페이스와 1개의 클래스가 한 쌍이된다.


1. com.gxt.riaapp.client.RiaAppPgmService 
   - 패키지를 보면 client아래 존재한다. 이 인터페이스에는 서비스에서 사용할 메소드를 명시하게 된다.
   - 이클립스에서는 최초 이 인터페이스를 생성하면 자동으로 나머지 인터페이스와 클래스를 생성해준다.
   - 우리가 메소드를 추가하고 싶다면 이 인터페이스에 메소드를 추가하게 되는데 추가할 경우 자동으로 xxxAsync, Impl 두개의 파일에 해당 메소드가 기본 구현되어지게 된다.

2. com.gxt.riaapp.client.RiaAppPgmServiceAsync
   - 이 인터페이스는 1번 인터페이스를 생성하면 자동으로 생성되게 된다. 이 인터페이스는 이클리스가 알아서 제어 해주므로 사실 개발자는 손댈 필요가 없다. 이 인터페이스는 서버와 통신을 주고 받는 역할을 한다고 보면되겠다.

3. com.gxt.riaapp.server.RiaAppPgmServiceImpl
  - 패키지에 주목하자. server아래 존재한다. 이 클래스는 Client와는 반대로 Server에서 실행 할 메소드들이 구현되어야 한다. 즉 위의 1,2번에 명시된 메소드가 최종 구현되는 곳이다. 이 클래스에서 SQL을 날리고 데이터를 불러와 리턴하면 Async인터페이스를 통해 xxxService가 클라이언트에게 전달한다고 생각하면 되겠다.

▶ 서비스를 생성해 보자. com.gxt.riaapp.client를 선택하자 여기서 마우스 우측클릭을 하고 아래와 같이 선택하자.


▶ RiaAppPgmService라는 이름으로 생성해보자.


▶ 위에서 설명했듣 client에 2개의 인터페이스 server에 한개의 클래스가 생성되었을 것이다. 여기서 한가지 주목할 부분이 web.xml파일이다. war/WEB-INF/web.xml파일을 열어보자. 아래와 같이 web.xml파일이 변경되었을 것이다. 이는 서버스가 Servlet기반이기 때문에 하나의 서비스가 추가되었을 경우 해당 서비스 이름으로 서블릿 설정이 web.xml파일에 추가되게 된다. 이 또한 자동으로 생성되게 되므로 손댈 필요는 없겠다


  
  
  
  
  
    RiaApp.html
  
  
  	RiaAppPgmService
  	com.gxt.riaapp.server.RiaAppPgmServiceImpl
  
  
  	RiaAppPgmService
  	/riaapp/RiaAppPgmService
  



▶ 이제 우리는 서비스에 메소드를 추가하고 그 메소드를 구현 해보 도록 하자. 구현이 끝나면 클라이언트에서 해당
 메소드를 호출하면 서버는 클라이언트에게 정보를 전달하는 지 확인해볼 수 있을 것이다.
▶ RiaAppPgmService.java를 열어 메소드를 선언해보자
package com.gxt.riaapp.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("RiaAppPgmService")
public interface RiaAppPgmService extends RemoteService {
	public int getSessionData() throws Throwable ; // 서버에서 세션을 전달받아 리턴하자.
	/**
	 * Utility class for simplifying access to the instance of async service.
	 */
	public static class Util {
		private static RiaAppPgmServiceAsync instance;
		public static RiaAppPgmServiceAsync getInstance(){
			if (instance == null) {
				instance = GWT.create(RiaAppPgmService.class);
			}
			return instance;
		}
	}
}

▶ 이제 Async 인터페이스와 Impl클래스를 열어보라 위에서 선언한 메소드가 선언 또는 구현되어 있을 것이다.
package com.gxt.riaapp.client;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface RiaAppPgmServiceAsync {
	public void getSessionData(AsyncCallback callback) ; // 서버에서 세션을 전달받아 리턴하자.
}
package com.gxt.riaapp.server;

import javax.servlet.http.HttpSession;

import com.gxt.riaapp.client.RiaAppPgmService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class RiaAppPgmServiceImpl extends RemoteServiceServlet implements RiaAppPgmService {
	private HttpSession getSession() {
		return this.getThreadLocalRequest().getSession();
	}
	@Override
	public int getSessionData() throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("여기는 서버예요...");
		return 0;
	}
}



▶ 이제 client에서 getSessionData메소드를 호출해보자 Impl클래스의 메소드가 실행되어 System.out.println("여기는 서버예요..");가 실행되는 지 보도록 하자. 또한 실행됨과 동시에 리턴되는 값이 무엇인지도 확인해보자. 이러한 형태로 서버와 통신해서 최종적으로는 데이터베이스의 데이터등을 리턴하는 것이다.
▶ RiaApp.java를 수정해서 위의 getSessionData메소드를 호출해보자.
	public void onModuleLoad() {
		model = new RiaAppModel();
	    Registry.register(MODEL, model);  // 최초 실행시 Registry에 RiaAppModel을 등록한다.
		dispatcher = Dispatcher.get();
	    dispatcher.addController(new AppController());
	    dispatcher.addController(new NavigationController());  // 좌측
	    dispatcher.addController(new ContentController());
	    dispatcher.dispatch(AppEvents.Init);
	    
	    if(model.getEntries().size()>0) // 등록된 첫번째 프로그램을 보여준다.
			showPage(model.getEntries().get(0));

	    onSubmit(); // 서버쪽 메소드를 호출해서 세션이 유효한지 확인한다.
	   
	}

	public int onSubmit() {
		service.getSessionData( new AsyncCallback() {
			@Override
			public void onSuccess(Integer result) {
				String time = new Date().toString();
				//if(result == -1){
					System.out.println("서버에서 리턴해준 결과는 "+result);
				//}			}
			@Override
			public void onFailure(Throwable caught) {
				MessageBox.alert("로그인", "세션이 종료되었습니다. \n 다시 로그인 해주세요", null);
			}
		});
		return 0;
	}


▶ 이제 실행해 보자.  아래 그림처럼 server 쪽 메소드에서 "여기는 서버예요"를 출력하고 그 다음 클라이언트쪽 메소드에서 "서버에서 리턴해준..."라고 출력된다. 여기서 주목할 부분은 위의 onSubmit()메소드에서 보여주는 것처럼 서버쪽 메소드를 호출하고 결과를 리턴받는데 성공할 경우 onSuccess()메소드가 실행되고 그렇지 않고 문제가 발생할 경우 onFailure()메소드가 호출되게 된다.
이 두개의 메소드는 두고두고 나올테니 기억하도록 하자.

▶ 이제 getSessionData메소드를 역할에 맞게 수정하도록 하자. 이 메소드는 세션이 유효한지 체크하는 것이다.
일단 메소드의 리턴 type을 변경하자 단지 세션이 유효한지 여부를 판단 할 것이라면 유효하지 않은 경우에는 onFailure메소드가 실행되도록 하고 onFailure메소드에서는 유효하지 않다는 메시지와 함께 로그인 페이지로 이동하도록 하면 될것이므로 리턴타입은 void로 변경하는것이 좋을 듯 하다.

// RiaAppPgmService.java
	public void getSessionData() throws Throwable ; // 서버에서 세션을 전달받아 리턴하자.

// RiaAppPgmServiceAsync.java
	public void getSessionData(AsyncCallback callback) ; // 서버에서 세션을 전달받아 리턴하자.

// RiaAppPgmServiceImpl.java
	@Override
	public void getSessionData() throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("여기는 서버예요...");
	}

// RiaApp.java 
	public void onSubmit() {
		service.getSessionData( new AsyncCallback() {
			@Override
			public void onFailure(Throwable caught) {
				MessageBox.alert("로그인", "세션이 종료되었습니다. \n 다시 로그인 해주세요", null);
			}
			@Override
			public void onSuccess(Void result) {
				// TODO Auto-generated method stub
				
			}
		});
	}
이제 기존 int형이었던 메소드를 void로 변경하였으니 Impl클래스의 메소드를 구현해보도록 하자. 이 메소드는 세션을 구해와 유효한지 확인하는 메소드이므로 일단 세션정보를 얻어오도록 코딩하자. RiaAppPgmServiceImpl 클래스에 아래의 메소드를 추가하고 세션을 구해와 유효한지 확인해보자.
package com.gxt.riaapp.server;

import javax.servlet.http.HttpSession;

import com.gxt.riaapp.client.RiaAppPgmService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class RiaAppPgmServiceImpl extends RemoteServiceServlet implements RiaAppPgmService {
	private HttpSession getSession() {
		return this.getThreadLocalRequest().getSession();
	}
	@Override
	public void getSessionData() throws Throwable {
		HttpSession session = getSession();
		if( null == session.getAttribute("loginid")) // index.html페이지에서 전달해주고 loginChk.jsp에서 생성한 세션정보
			throw new Throwable("세션이 종료되었습니다.");
		else
			System.out.println("로그인 아이디는 : "+(String)session.getAttribute("loginid"));
	}
}


이제 실행해 보자. RiaApp.html파일을 실행할 경우와 index.html파일을 통해 로그인을 했을 경우와 비교를 해보자. 일단 RiaApp.html을 실행할 경우 아래 그림처럼 세션이 종료되었다는 경고창이 보일 것이다. 이는 로그인을 하지 않았으므로 세션이 존재하지 않아 경고창을 보여주는 것이다.

이제 index.htlm파일을 실행하여 정상적으로 로그인해보자. 아래 그림처럼 서버쪽 메소드에서 세션정보를 확인할 수 있다.

▶ 자 이제 생각을 해보자. RiaApp.html은 실제 프로그램이 돌아가는 영역이고 index.html은 로그인 페이지다. RiaApp.html을 실행해서 세션이 존재하지 않을 경우 즉 로그인이 되어 있지 않거나 일정시간 사용하지 않아 세션이 끊겼을 경우 "세션이 종료되었습니다.."라는 메시지를 출력해주었다. 그럼 이제 무엇을 해야할 까? 경고창이 나오고 ok버튼을 클릭했을 경우 로그인 페이지로 유도하는 것이 필요할 듯 하다. 로그인 페이지로 유도하고 로그인을 하고 다시 RiaApp.html을 호출하여 프로그램을 보여줄수 있도록 하는것이다.
▶ 그럼 무엇이 필요할까 생각해보자. 우선 위의 경고창에서 보듣 "ok"버튼을 클릭했을 경우 이 클릭한 이벤트를 이용해서 로그인 페이지로 이동하는 코드를 만들어 보자. 우선 "ok"버튼을 감시할 리스너를 선언하자.
// RiaApp.java를 수정하자.
	/**
	 * Native Javascript Call
	 */
	private native void goIndex() /*-{ 
		$wnd.location.href = "/index.html"; 
	}-*/;

	/**
	 * 세션이 유효하지 않을 경우 뛰워주는 경고창 내의 OK버튼을 감시할 리스너
	 */
	final Listener btnListener = new Listener() {  
		public void handleEvent(MessageBoxEvent ce) {  
			goIndex();
		}  
	};  
	public void onSubmit() {
		service.getSessionData( new AsyncCallback() {
			@Override
			public void onFailure(Throwable caught) {
				// 버튼 리스트를 설정
				MessageBox.alert("로그인", "세션이 종료되었습니다. \n 다시 로그인 해주세요", btnListener);
			}
			@Override
			public void onSuccess(Void result) {
				// TODO Auto-generated method stub
				
			}
		});
	}

이제 실행해 보자. RiaApp.html을 실행해 보면 로그인을 하지 않았으므로 "세션이 종료되었습니다. 다시 로그인해주세요"라는 메시지를 출력하고 "OK"버튼을 클릭하면 로그인 페이지로 이동하게 될것이다. 이렇게 하면 기본적으로 로그인에 대한 처리가 완료되었다.
Posted by 베니94
GXT RIA App 만들기2011. 1. 31. 17:53
오늘 강좌에서는 로그인 페이지와 세션에 대해 애기해보자. 우리는 지금까지 어플리케이션의 뼈대를 구성하고 추가적으로 개발할 프로그램이 자리 잡을 수 있도록 프로그램영역과 좌측 트리메뉴 등을 구성했다. 이제 관리자페이지에 접근할 수 있도록 로그인 페이지를 만들고 아이디와 패스워드를 통해 인증처리를 추가해 보도록 하자.


 

로그인 페이지를 만들어 보자 . 우선 본인은 디자이너가 아니므로 내 손으로 html을 만들면 흉해질 것이므로 tistory의 로그인페이지를 살짝 긁어와 맘대로 사용해보자. 뭐 상업적으로 이용하는건 아니니깐 .. 암튼 본인이 바꿀 수 있으면 바꾸시고 ~

▷ 티스토리에서 훔쳐온 로그인페이지를 그대로 이용하자

▷ 소스는 아래와 같다. war폴더에 index.html이라는 이름으로 파일을 생성하자.






	
▷ 이제 실행시켜 보자. 근데 실행하기 전에 Run Configuration에 대해 간략히 언급을 하고 가자. 아래와 같이 Run Configuration을 실행해보자.

▶ Run Configuration 의 실행 모습이다. 기본적으로 해당 프로젝트의 Root에 마우스우측 클릭하고 실행하면 그 프로젝트의 이름으로 된 html파일이 실행되도록 설정되어 있다 . 나의 경우 Name필드에 .html을 떼어버리고 RiaApp로 변경하고 그 밑의 Server 탭에서 서버구동시 포트를 변경할 수 있도록 세팅가능하도 여러개의 GWT어플리케이션을 구동할 때 포트를 달리하여 사용할 수 있다..

▶ Server탭의 포트 설정 부분

▶ 위의 설정이 끝나면 Apply를 클릭하고 다음번에 GWT어플리케이션을 실행할 때는 아래 그림처럼 하면 간편하죠!


▶ 자 이제 본론으로 들어가서 브라우저에 우리가 만든 index.html을 실행 해보도 록 하자.

이제 아이디와 패스워드를 아무렇게나 넣고 로그인 버튼을 클릭해보자. 아마 아무 반응이 없을 것이다. 콘솔 창을 통해 무슨일이 일어났는지 보자. 아래 로그를 보니 /common/loginChk.jsp가 없다고 나온다. 우리는 index.html에 계정을 넣고 로그인 버튼을 클릭했을 경우 /common/loginChk.jsp로 보내도록 코딩했다. 이 파일에서는 index.html에서 받아온 계정을 확인하고 세션을 생성하고 실제 Gxt 어플리케이션을 실행하는 역할을 하도록 해야한다.

▶ /common/loginChk.jsp를 생성하고 아래와 같이 코딩하자.
<%
// 1. 로그인 계정의 유효성을 확인하는 로직
// 2. 세션 생성
// Gwt 어플리케이션에서 이 세션을 어떻게 이용할 수 있는지
// 확인할 수 있다.
session.setAttribute("loginid", request.getParameter("loginid"));
%>
< script language='javascript'>
parent.location.href="/RiaApp.html?gwt.codesvr=127.0.0.1:9997#0001";
< /script>
▶ 이제 다시 실행해 보자. index.html을 실행하고 계정을 입력하고 로그인 버튼을 클릭하면 Gxt어플리케이션이 실행될 것이다.

오늘은 여기까지... 다음에는 RiaApp.html을 조금 정비하고 GWT에서 서비스를 이용하는 방법과 세션의 사용법을 배워보도록 하자.
Posted by 베니94
GXT RIA App 만들기2011. 1. 28. 09:14

오늘은 상단부분을 완성해보도록 하자. 최초 생각했던 것은 상단에는 단순 html을 삽입 로그인 정보정도만 보여주려고 했으나 계획을 변경해서 일반어플리케이션에서 볼수 있는 풀다운 메뉴와 툴바를 추가해보도록 하자. 풀다운 메뉴는 좌측 메뉴로는 도저히 정리되지 않을 경우나 좌측메뉴와는 별개로 성격이 아주 다른 프로그램들을 모아두거나 하면 될테고 툴바는 미리 보여줄 필요가 있는 자주 쓰는 프로그램을 모아놓는 용도로 사용하자.

☞ 오늘 만들게 될 화면이다. 최상단에 메뉴가 들어가고 그 아래에 툴바를 배치하자.

최초 계획과는 달리 상단부분에 html이 아닌 Menu와 툴바를 넣기로 했으므로 기존 AppView클래스의 northPanel을 HtmlContainer가 아닌 ContentPanel로 변경하여 Menu와 툴바를 삽입하도록 하자.

☞ northPanel과 createNorth()메소드를 변경하자.
//	private HtmlContainer 	northPanel;	// 상단영역
	private ContentPanel	northPanel;

	private void createNorth() {
		BorderLayoutData data = new BorderLayoutData(LayoutRegion.NORTH, 45);
		data.setMargins(new Margins(0, 5, 5, 5));
		northPanel = new ContentPanel();
		northPanel.setHeaderVisible(false);
		ToolBar tb = new ToolBar();
		Button topBtn = new Button("로그아웃");
		topBtn.setIcon(RiaApp.ICONS.go_out());
		tb.add(topBtn);
		topBtn = new Button("사용자정보수정");
		topBtn.setIcon(RiaApp.ICONS.accordion());
		tb.add(topBtn);
		topBtn = new Button("접속로그");
		topBtn.setIcon(RiaApp.ICONS.text());
		tb.add(topBtn);
		tb.setAlignment(HorizontalAlignment.RIGHT);
		northPanel.setBottomComponent(tb);
		Menu menu = new Menu();   
		  
	    MenuItem item1 = new MenuItem("Edit");   
	    menu.add(item1);   
	  
	    MenuItem item2 = new MenuItem("Open File");   
	    menu.add(item2);   
	  
	    Menu sub = new Menu();   
	    sub.add(new MenuItem("readme.txt"));   
	    sub.add(new MenuItem("helloworld.txt"));   
	    item2.setSubMenu(sub);   
	    	
	    MenuBar bar = new MenuBar();   
	    bar.setBorders(true);   
	    bar.setStyleAttribute("borderTop", "none");   
	    bar.add(new MenuBarItem("New", menu));   
	  
	    Menu sub2 = new Menu();   
	    sub2.add(new MenuItem("Cut"));   
	    sub2.add(new MenuItem("Copy"));   
	  
	    MenuBarItem edit = new MenuBarItem("Edit", sub2);   
	    bar.add(edit);   
	  
	    sub = new Menu();   
	    sub.add(new MenuItem("Search"));   
	    sub.add(new MenuItem("File"));   
	    sub.add(new MenuItem("Java"));   
	  
	    MenuBarItem item3 = new MenuBarItem("Search", sub);   
	    bar.add(item3);   
	       
	    menu = new Menu();   
	  
	    CheckMenuItem menuItem = new CheckMenuItem("I Like Cats");   
	    menuItem.setChecked(true);   
	    menu.add(menuItem);   
	  
	    menuItem = new CheckMenuItem("I Like Dogs");   
	    menu.add(menuItem);   
	  
	  
	    menu.add(new SeparatorMenuItem());   
	  
	    MenuItem radios = new MenuItem("Radio Options");   
	    menu.add(radios);   
	  
	    Menu radioMenu = new Menu();   
	    CheckMenuItem r = new CheckMenuItem("Blue Theme");   
	    r.setGroup("radios");   
	    r.setChecked(true);   
	    radioMenu.add(r);   
	    r = new CheckMenuItem("Gray Theme");   
	    r.setGroup("radios");   
	    radioMenu.add(r);   
	    radios.setSubMenu(radioMenu);   
	  
	    MenuItem date = new MenuItem("Choose a Date");   
	    menu.add(date);   
	  
	    date.setSubMenu(new DateMenu());   
	       
	    MenuBarItem item4 = new MenuBarItem("Foo", menu);   
	    bar.add(item4);   
	    northPanel.add(bar);
	    
	    northPanel.setAutoHeight(true);
		viewport.add(northPanel, data);
	}
☞ 위와 같이 코딩하면 에러가 날것이다. RiaApp.ICONS...이 부분이다. 이부분은 툴바에 설정하는 아이콘 이미지로 com.gxt.riaapp.client.resource라는 이름으로 패키지를 생성하고 아래 클래스를 추가해 보자. 아래의 ResourceIcons클래스는 보여질 이미지를 추가하고 해당 이미지를 툴바나 기타 아이콘으로 사용하기 위함이다.
package com.gxt.riaapp.client.resource.icon;

import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.ImageBundle;
@SuppressWarnings("deprecation")
public interface ResourceIcons extends ImageBundle {

  @Resource("table.png")
  AbstractImagePrototype table();

  @Resource("add16.gif")
  AbstractImagePrototype add16();

  @Resource("add24.gif")
  AbstractImagePrototype add24();

  @Resource("add32.gif")
  AbstractImagePrototype add32();

  @Resource("application_side_list.png")
  AbstractImagePrototype side_list();

  @Resource("application_form.png")
  AbstractImagePrototype form();

  @Resource("connect.png")
  AbstractImagePrototype connect();

  @Resource("user_add.png")
  AbstractImagePrototype user_add();

  @Resource("user_delete.png")
  AbstractImagePrototype user_delete();

  @Resource("accordion.gif")
  AbstractImagePrototype accordion();

  @Resource("add.gif")
  AbstractImagePrototype add();

  @Resource("delete.gif")
  AbstractImagePrototype delete();

  @Resource("calendar.gif")
  AbstractImagePrototype calendar();

  @Resource("menu-show.gif")
  AbstractImagePrototype menu_show();

  @Resource("list-items.gif")
  AbstractImagePrototype list_items();

  @Resource("album.gif")
  AbstractImagePrototype album();

  @Resource("text.png")
  AbstractImagePrototype text();

  @Resource("plugin.png")
  AbstractImagePrototype plugin();
  
  @Resource("music.png")
  AbstractImagePrototype music();
  
  @Resource("table_refresh.png")
  AbstractImagePrototype refresh();
  
  @Resource("folder_go.png")
  AbstractImagePrototype go_out();
  
  @Resource("excel_download16.png")
  AbstractImagePrototype excel16();
  
  @Resource("excel_download32.png")
  AbstractImagePrototype excel32();
}
☞ 같은 패키지에 이미지도 추가하자. gxt쪽 샘플에서 사용하는 이미지로 gxt 루트에서 찾아보면 될것이다.
 
마지막으로 테마를 변경하자. 이 강좌 맨위의 이미지를 보면 이전에 푸른색 계열의 색상에서 세련된 회색계열로 변경되었다. 개인적으로 푸른색은 좀 촌스러워서 바꿔봤다. war폴더내의 RiaApp.html파일에 한줄 추가하자.
    
     
    
Posted by 베니94