앱엔진 짱이네요-_-
속도만 빨랐다면 호스팅을 여기로 옮겨도 뭐 문제가 없을 듯. 시스템적으로는 구글이 다 관리해주고, 개발환경도 매우 편하니...(하지만, DB 접근이 방식이 틀려서 힘든면도 있지만ㅠ)

pc랑 android랑 뭔가 싱크맺는 뭐 그런거 만드려다보니 여기까지 와버렸네요-_- 이걸 통해서 하면 좋을 것 같아서^^

암튼, Channel API를 제공하는데, 원하는 채널을 생성해서 그곳 페이지로 접속한 사용자들끼리 메세지를 주고 받을 수 있게 됩니다. 

앱엔진 채널부분 문서입니다.
http://code.google.com/intl/ko-KR/appengine/docs/java/channel/

보면 JavaAPI랑 JavascriptAPI 두개 있는데, 클래스도 몇 개 없습니다.
Java에서는 ChannelService를 가져와서 createChannel로 채널을 만든 뒤에, sendMessage함수로 메세지만 보내면 됩니다.
Javascript에서는 goog.appengine.Channel이라는 클래스가 존재하는데, 이걸로 오픈하고 메세지 받으면 됩니다.

일단..... 나중에 쓰려고 만든거다보니 Spring3.0과 jQuery가 들어갔네요-_-
http://mudchobo.tistory.com/470 이거 참조해서 환경 구축을 하면 됩니다~^^

ChannelController.java

package com.mudchobo.apps.exchangepp.controller;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.google.appengine.api.channel.ChannelMessage;
import com.google.appengine.api.channel.ChannelService;
import com.google.appengine.api.channel.ChannelServiceFactory;

@Controller
@RequestMapping("/channeltest")
public class ChannelController {
	
	private String channelName = "test";
	
	@RequestMapping(value="/test", method=RequestMethod.GET)
	public String test(Model model){
		ChannelService channelService = ChannelServiceFactory.getChannelService();
		String token = channelService.createChannel(channelName);
		model.addAttribute("token", token);
		return "test";
	}
	
	@RequestMapping(value="/open", method=RequestMethod.POST)
	@ResponseBody
	public String openChat(){
		ChannelService channelService = ChannelServiceFactory.getChannelService();
		channelService.sendMessage(new ChannelMessage(channelName, "open"));
		return "";
	}
	
	@RequestMapping(value="/send", method=RequestMethod.POST)
	@ResponseBody
	public String sendChat(@RequestParam("msg") String msg){
		System.out.println("message = " + msg);
		try {
			ChannelService channelService = ChannelServiceFactory.getChannelService();
			channelService.sendMessage(new ChannelMessage(channelName, URLEncoder.encode(msg, "UTF-8")));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return "";
	}
}
최초 접속페이지는 /test 입니다.
여기서 채널을 생성해줍니다. 이 생성해서 나온 토큰을 client로 전달합니다. 그러면 그 client에서 goog.appengine.Channel("토큰값");을 통해 Channel을 생성합니다. 그러면 커넥션을 맺고 있게 되는겁니다.

클라이언트부분
test.jsp

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page isELIgnored="false" %>
<!doctype html>
<html>
	<head>
		<script src="/_ah/channel/jsapi"></script>
		<script src="/js/lib/jquery-1.4.4.min.js" type="text/javascript"></script>
		<script type="text/javascript">
			var sendMessage = function(path, msg){
				var xhr = new XMLHttpRequest();
				xhr.open("POST", "/apps/channeltest/" + path + "?msg=" + encodeURIComponent(msg), true);
				xhr.send();
			}
			
			var onOpen = function(){
				sendMessage("open");
			};
			var onMessage = function(m){
				var msg = decodeURIComponent(m.data.split("+").join(" "));
				$("#message_box ul").append("<li>" + msg + "</li>");
				$("#message_box").scrollTop(999999999);
			};
			var onError = function(){
				alert("error");
			};
			var onClose = function(){
				alert("close");
			};
			
			$(document).ready(function(){
				var channel = new goog.appengine.Channel("${token}");
				var socket = channel.open();
				socket.onopen = onOpen;
				socket.onmessage = onMessage;
				socket.onerror = onError;
				socket.onclose = onClose;
				
				$("#send_btn").click(function(){
					sendMessage("send", $("#message").val());
					$("#message").val("");
				});
				$("#message").keyup(function(e){
					if (e.keyCode == 13){
						sendMessage("send", $("#message").val());
						$("#message").val("");
					}
				});
			});
		</script>
	</head>
	<body>
		<div id="message_box" style="width:500px;height:300px;overflow:scroll;font-size:12px">
			<ul></ul>
		</div>
		<input type="text" id="message" name="message" />
		<input type="button" value="보내기" id="send_btn" name="send_btn" /><br />
	</body>
</html>
일단 Channel클래스를 쓰려면 <script src="/_ah/channel/jsapi"></script>를 include해야해요. 그리고, new goog.appengine.Channel생성부분을 유심히 보면 함수로 onopen, onmessage, onerror, onclose를 등록할 수 있어요. 메세지를 받으면 onmessage가 호출이 되니 여기서 처리하면 됨 ㅇㅇ 보내는 것은 아까 java에서 만들어 놓은 url을 호출하면 됨. 끗~

예제주소는 여기
http://mudchobo.appspot.com/apps/channeltest/test

소스파일...귀찮아서 그냥....통째로.....-_-



ps. 한글이 깨지길래 보낼 때 인코딩하고, 다시 받을 때 url인코딩해서 보내고 받은 걸 푸는 방식으로 했더니 되네요. 

 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon 레몬에이드 2011.01.31 14:05 신고  댓글주소  수정/삭제  댓글쓰기

    음... 역쉬 만능! 못하시능게 없어! +ㅁ+

  2. JIN32 2011.03.09 15:47  댓글주소  수정/삭제  댓글쓰기

    죄송하지만 한가지 여쭤볼께요.
    로컬에서 테스트할때

    <script src="/_ah/channel/jsapi"></script>

    이 부분에요.

    com.google.appengine.api.channel.dev.LocalChannelFailureException: Channel for application key null not found.

    이런 오류가 발생되는데...
    혹시 해결방법 알고 계시는지 궁금해서 댓글 남겨봅니다.

    암튼 좋은글 잘 보고 갑니다.
    감사합니다. ^^

  3. Jin 2011.09.06 00:57  댓글주소  수정/삭제  댓글쓰기

    우와 전부터 앱엔진으로 채팅기능 구현하고싶었는데 channel api가 있는지 처음알았네요 좋은정보 감사합니다^^

  4. gslee 2011.09.22 08:41  댓글주소  수정/삭제  댓글쓰기

    안녕하세요~
    구글앱엔진 java 를 공부하고있는데요안드로이드랑연결시켜서 간단하게 방명록처럼만드려고 하는데 한글이 전부깨지내요 ㅠ
    해결방법을 아시면 저에게 도움좀 부탁드립니다^^
    skyyy80@gmail.com

  5. gslee 2011.09.22 08:41  댓글주소  수정/삭제  댓글쓰기

    안녕하세요~
    구글앱엔진 java 를 공부하고있는데요안드로이드랑연결시켜서 간단하게 방명록처럼만드려고 하는데 한글이 전부깨지내요 ㅠ
    해결방법을 아시면 저에게 도움좀 부탁드립니다^^
    skyyy80@gmail.com

 
여기저기 메이븐이 많이 쓰이는 것 같아 이참에 삽질을 해봤습니다.
Maven은 소프트웨어 프로젝트 관리툴인데, 의존적인 라이브러리를 서버와 연동해서 쉽게 업데이트를 해주며, 컴파일 및 배포 과정을 최소화하며 자동으로 테스트를 할 수 있게 도와주는 뭐 그런 툴인 듯 합니다.

한마디로 나름 그냥 편하려고 만든거라는거-_-

라이브러리를 수동으로 복사해서 lib폴더에 쳐넣는 행위를 막고 자동으로 라이브러리를 업데이트할 수 있게 해주는 것만해도 큰 장점인 것 같습니다.

1. Maven 다운로드 및 설치
http://maven.apache.org/download.html 여기서 최신버전인 3.0을 받아서 압축해제
환경변수 Path에 maven디렉토리/bin폴더를 걸어두셔야 어디서든 mvn을 때릴 수 있기에 추가!
환경변수 MAVEN_HOME을 maven디렉토리홈으로 해서 추가!

2. m2eclipse설치
이건 이클립스에서 maven템플릿파일을 쉽게 생성할 수 있는 플러그인입니다.
eclipse에서 Help -> Install New Software -> Add해서 url을 http://m2eclipse.sonatype.org/sites/m2e/ 로 한다음에 Maven Integration for eclipse를 체크하고 설치하면 됩니다 ㄷㄷ

3. Mavan프로젝트 생성
이제 Maven프로젝트 생성을 합니다.
New -> Project 하면 Maven Project가 새로 생겼음.
선택하고, location에서 디폴트로하고 Next하고, 우린 webapp을 만들꺼니까 groupid가 org.apache.maven.archetypes이고, Artifact Id가 maven-archetype-webapp을 선택하고 Next!
Group Id는 패키지명으로 대충 com.mudchobo.springtest라고 하고,
Artifact Id는 프로젝트 이름이니까 대충 SpringTest로....-_-

그리고 이제 cmd쳐서 콘솔로가서 SpringTest폴더로 이동. 아래 커맨드발동!
[code]mvn -Dwtpversion=2.0 eclipse:eclipse[/code]
그러면 wtp용 프로젝트로 변환이 되어있을겁니다.
그리고 이상하게 Java Compiler가 1.4로 맞춰져있는데, 1.6으로 맞춥니다.
프로젝트Properties에서 Java Compiler에서ㅓ 1.6으로 맞추고, 아래 Use default compliance setting체크해주시면 됨 ㅇㅇ
그리고 Project Facets에서 Java를 6.0으로 바꿔주시면 됨 ㅇㅇ

4. 의존성 라이브러리 추가
pom.xml
[code]<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mudchobo.springtest</groupId>
  <artifactId>SpringTest</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>SpringTest Maven Webapp</name>
  <properties>
      <spring.version>3.0.5.RELEASE</spring.version>
  </properties>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>${spring.version}</version>
    </dependency>
    <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
          <version>${spring.version}</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>SpringTest</finalName>
  </build>
</project>
[/code]
스프링 라이브러리 2개를 추가했습니다. 그러면 뭔가 웹에서 다운받으면서 라이브러리를 저장소에 저장해둡니다 ㄷㄷ 뭔가 알아서 처리하는 느낌!

5. 이제 스프링라이브러리를 쓰도록 web.xml수정 및 spring-servlet.xml추가!
web.xml
[code]<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>Court Reservation System</display-name>

    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
[/code]
spring-servlet.xml
[code]<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
</beans>[/code]

6. Maven Dependencies 라이브러리 추가.

그냥 실행하게 되면 메이븐에서 읽어드린 라이브러리가 추가되지 않아 Eclipse에 있는 Server에 SpringTest프로젝트를 추가하면 ClassNotFoundException에러가 납니다.
[code]심각: Error loading WebappClassLoader
  context: /SpringTest
  delegate: false
  repositories:
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@1172e08
 org.springframework.web.servlet.DispatcherServlet
java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet[/code]
그래서 저도 뭔가 라이브러리를 추가해야된다는 생각에 구글링을 해보니 방법이 있군요!

해당 프로젝트폴더에 .setting폴더에 있는 org.eclipse.wst.common.component파일
org.eclipse.wst.common.component
[code]<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java" />
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/resources" />[/code]위에 두줄 지우고,
[code]<dependent-module deploy-path="/WEB-INF/lib">
    <dependency-type>uses</dependency-type>
</dependent-module>[/code]3줄추가하고~

그리고 .classpath파일에 아래 클래스패스를 추가합니다.
.classpath
[code]<classpathentry exported="true" kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER">
    <attributes>
        <attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
    </attributes>
</classpathentry>[/code]
그러면 프로젝트properties에서 Java Build Path에서 Library를 보면 Maven Dependencies가 추가되어있을겁니다~ 여기에는 pom.xml에서 라이브러리 추가한 것이 들어있어요~

이제 Server에 추가한다메 시작하면 톰캣 에러없이 동작할겁니다.

7. war파일 만들기~
프로젝트에 Export해도 되고, mvn으로 명령어를 때려도 됩니다.
[code]mvn package[/code] 때리면 packaging이 war로 되어있어서 war파일이 생성되더군요.
물론 이과정에서 ftp에 배포라던지 그런게 가능한 것 같습니다. 좀 더 연구해보고!

PS. 갑자기 느끼는 생각인데, 이것보다 더 간편한 방법이 있지 않을까 싶기도하고....-_- 후.....

참고자료
http://blog.v-s-f.co.uk/2010/09/jsf-2-1-project-using-eclipse-and-maven-2/
http://maven.apache.org/plugins/maven-eclipse-plugin/

 
Posted by 머드초보

댓글을 달아 주세요

 
https를 아직 이해를 못해서.....-_-
일단 기록용으로-_-













[code]
public class Test extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        StringBuilder content = new StringBuilder();
       
        try
        {
            String data = "userid=???&password=???";
            URL url = new URL("https://url~~");
           
            HttpURLConnection http = null;
           
            if (url.getProtocol().toLowerCase().equals("https")) {
                trustAllHosts();
                HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
                https.setHostnameVerifier(DO_NOT_VERIFY);
                http = https;
            } else {
                http = (HttpURLConnection) url.openConnection();
            }
            http.setDoOutput(true);
            OutputStreamWriter wr = new OutputStreamWriter(http.getOutputStream());
            wr.write(data);
            wr.flush();
           
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(http.getInputStream()));

            String line;

            while ((line = bufferedReader.readLine()) != null)
            {
              content.append(line + "\n");
            }
            Log.i("content", content.toString());
            wr.close();
            bufferedReader.close();
        }
        catch(Exception e)
        {
        }
    }
   
    private static void trustAllHosts() {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return new java.security.cert.X509Certificate[] {};
                }

                @Override
                public void checkClientTrusted(
                        java.security.cert.X509Certificate[] chain,
                        String authType)
                        throws java.security.cert.CertificateException {
                    // TODO Auto-generated method stub
                   
                }

                @Override
                public void checkServerTrusted(
                        java.security.cert.X509Certificate[] chain,
                        String authType)
                        throws java.security.cert.CertificateException {
                    // TODO Auto-generated method stub
                   
                }
        } };

        // Install the all-trusting trust manager
        try {
                SSLContext sc = SSLContext.getInstance("TLS");
                sc.init(null, trustAllCerts, new java.security.SecureRandom());
                HttpsURLConnection
                                .setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
                e.printStackTrace();
        }
    }
   
    final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };
}
[/code]
 
Posted by 머드초보

댓글을 달아 주세요

  1. 승군 2010.11.05 16:51  댓글주소  수정/삭제  댓글쓰기

    머드초보님 코드 덕분에 제가 살았습니다.
    정말 감사합니다 ㅠㅠ

    • 머드초보 2010.11.05 21:21  댓글주소  수정/삭제

      아~ 도움이 되셔서 다행이네요
      저도 이거 https요청에 대해서 구글링한거 그냥 적어놓은거라는^^
      방문해주셔서 감사해요~

  2. BlogIcon 도플광어 2010.11.30 17:16  댓글주소  수정/삭제  댓글쓰기

    감사합니다. 저도 살앗네요^^;

  3. 초보개발자 2011.07.04 21:25  댓글주소  수정/삭제  댓글쓰기

    오호~ 저도 잘몰라서 이것저것 했는데 실패했는데
    이걸로 성공했네요ㅠㅠ
    좋은 소스 감사합니다^^

 
HttpClient 4.x버전으로 올라오면서 조쿰 바뀐 것 같습니다.
기록용으로 기록합니다-_-
아래 예제는.....티월드사이트의 무료사용량 조회 예제입니다-_-










[code]
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

public class Main {

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception{
        HttpClient httpclient = new DefaultHttpClient();
       
        String id = "t월드 아이디";
        String pw = "비밀번호";
       
        List<NameValuePair> qparams = new ArrayList<NameValuePair>();
        qparams.add(new BasicNameValuePair("URL", "http://www.tworld.co.kr/loginservlet.do?returnURL=http%3A%2F%2Fwww.tworld.co.kr&kind=&popup=&cmd=&reload=&ID=" + id));
        qparams.add(new BasicNameValuePair("ID", id));
        qparams.add(new BasicNameValuePair("PASSWORD", pw));
        qparams.add(new BasicNameValuePair("SERVERIP", "203.236.20.129"));
        qparams.add(new BasicNameValuePair("X", "0"));
        qparams.add(new BasicNameValuePair("Y", "0"));
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(qparams, "UTF-8");
        HttpPost httpPost = new HttpPost("http://nicasams.sktelecom.com:2040/icas/fc/LogOnSV");
        httpPost.setEntity(entity);
       
        ResponseHandler<String> responseHandler = new BasicResponseHandler();
        String responseBody = "";
        HttpResponse response = httpclient.execute(httpPost);
        Header[] headers  = response.getAllHeaders();
        httpclient = new DefaultHttpClient();
        HttpGet httpGet = new HttpGet();
        if (headers.length > 1){
            String url = headers[1].getValue();
            System.out.println("url = " + url);
            httpGet.setURI(new URI(url));
            responseBody = httpclient.execute(httpGet, responseHandler);
            System.out.println(responseBody);
        }
        httpGet.setURI(new URI("http://www.tworld.co.kr/normal.do?serviceId=S_BILL0070&viewId=V_CENT0261"));
        responseBody = httpclient.execute(httpGet, responseHandler);
       
        System.out.println("result = " + responseBody);
    }
}
[/code]

 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon 이창우 2011.12.10 11:45  댓글주소  수정/삭제  댓글쓰기

    4.0 으로 오면서 org.apache.commons.httpclient 패키지가 통으로 사라져서 애먹고 있었는데

    예제 감사합니다.

 
앱엔진에 뭔가 제 사이트를 만들고 싶어서 이렇게 삽질을 하고 있는데, 망할 제한이 왜이렇게 많지-_-

암튼, "구글 앱 엔진"에서는 JPA를 지원합니다. 하지만, 이상하게도 잘 안됩니다-_- 굉장히 제한적으로 이것저것 막아둔 것 같습니다. 사실 구글 앱 엔진에서는 DataBase를 BigTable인지 뭐시기인지 그걸 사용하고, 직접적으로 접근을 못하기 때문에(전부 프로그래밍 또는 관리페이지(관리페이지도 매우 제한적인-_-)에서만 관리 가능), 이걸 이용하는 API에서도 엄청나게 뭔가 막아둔 것 같습니다.
뭐 좀 해보려고 하면 에러를 내뱉습니다. 검색해보면 구글앱엔진에서만 나는 에러입니다-_- 사실 아직 구글앱엔진이 프리뷰버전이기에 뭐라 따지지도 못하는 게 사실입니다^^ 정식버전(언제나오려나....Beta딱지 떼는데 10년넘게 걸리겠지-_-)나오면 매우 안정화가 되지 않을까 싶습니다^^

암튼, Spring3 + JPA의 조합으로 앱엔진에 올리는 건 성공했는데, 사실 스프링에서 제공하는 TransactionManager를 사용했어야 했는데, JPATemplate으로 뭔가 처리를 하면 잘 안되더군요-_- 일단 가져오고, persist하고, 이런건 잘 되는데, 왜 삭제가 안될까요-_- 삭제가 안되서 그냥JPATemplate빼고 했습니다-_-
JPATemplate사용해서 성공하신 분 트랙백좀 ㅠㅠ

0. 환경
Eclipse 3.5 + Google AppEngine Plugin + Spring 3.0.0
일단 스프링3다운로드 - http://www.springsource.org/download

1. 프로젝트 생성
New Project -> Google에 있는 Web Application Project 선택.
Project Name은 SosiSchedule. package는 com.mudchobo.
Use Google Web Toolkit은 체크해제. 사용안할꺼라....(이것도 언제한번 공부해야하는데-_-)
Finish.

2. 라이브러리 복사 및 build path추가
spring3에서는 spring.jar가 산산조각 났어요. 필요한 것만 넣으면 되는 듯.
일단 제가 사용한 것은....
org.springframework.asm-3.0.0.RELEASE.jar
org.springframework.beans-3.0.0.RELEASE.jar
org.springframework.context-3.0.0.RELEASE.jar
org.springframework.core-3.0.0.RELEASE.jar
org.springframework.expression-3.0.0.RELEASE.jar
org.springframework.orm-3.0.0.RELEASE.jar
org.springframework.web.servlet-3.0.0.RELEASE.jar
org.springframework.web-3.0.0.RELEASE.jar
그리고, jstl을 사용할 것이기에....
jstl.jar와 standard.jar
※이번버전에서는 lib폴더가 없습니다-_- 어디서 찾아야하는 거지-_- 암튼 그래서 2.5.6버전에서 가져왔습니다^^

앱엔진에서는 lib폴더 복사로 libpath가 잡히지 않네요. 그래서 각각 다 추가해줘야한다는...-_-
일단 war/WEB-INF/lib폴더에 복사 후에 복사한 파일 선택 후 오른쪽버튼 후, Build Path -> Add to Build Path 선택하면 됩니다^^

3. web.xml파일 수정
web.xml
[code]<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/sosischedule/*</url-pattern>
</servlet-mapping>

<welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>[/code]
일단 sosischedule/*요청은 spring이 받습니다.

4. dispacher-servlet.xml파일과 persistence.xml파일 생성
war/WEB-INF/폴더에 생성
dispatcher-servlet.xml
[code]<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.mudchobo" />
   
    <bean id="entityManager"
        factory-bean="EMF"
        factory-method="get" />
   
    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
</beans>[/code]

src/META-INF/ 폴더에 생성
persistence.xml
[code]<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
   
    <persistence-unit name="transactions-optional">
        <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
        </properties>
    </persistence-unit>
</persistence>[/code]

5. EMF클래스 생성.
이제 jpa접근할 수 있는 EntityManagerFactory클래스(EMF)를 생성해봅시다.
com.mudchobo.sosi.sosischedule.dao.EMF.java
[code]package com.mudchobo.sosischedule.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.springframework.stereotype.Component;

@Component
public final class EMF {
     private static final EntityManagerFactory emfInstance =
        Persistence.createEntityManagerFactory("transactions-optional");

    private EMF() {}

    public EntityManager get() {
        return emfInstance.createEntityManager();
    }
}[/code]

6. Entity클래스 생성
일단 Sosi와 Schedule이라는 Entity를 생성할 건데요. 둘의 관계는 1:N관계입니다.
com.mudchobo.sosischedule.entity.Sosi.java
[code]package com.mudchobo.sosischedule.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import com.google.appengine.api.datastore.Key;

@Entity
public class Sosi implements Serializable {
    private static final long serialVersionUID = 5448408922872112420L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Key key;
   
    private String sosiName;
   
    @OneToMany(mappedBy="sosi", cascade=CascadeType.ALL)
    private List<Schedule> scheduleList = new ArrayList<Schedule>();

    public Key getKey() {
        return key;
    }

    public void setKey(Key key) {
        this.key = key;
    }

    public List<Schedule> getScheduleList() {
        return scheduleList;
    }

    public void setScheduleList(List<Schedule> scheduleList) {
        this.scheduleList = scheduleList;
    }

    public String getSosiName() {
        return sosiName;
    }

    public void setSosiName(String sosiName) {
        this.sosiName = sosiName;
    }

    public Sosi() {
       
    }
   
    public Sosi(Key key, String sosiName) {
        super();
        this.key = key;
        this.sosiName = sosiName;
    }
}[/code]
com.mudchobo.sosischedule.entity.Schedule.java
[code]package com.mudchobo.sosischedule.entity;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

@Entity
public class Schedule implements Serializable{
    private static final long serialVersionUID = -8676837674549793653L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Key key;
   
    private String program;
   
    @ManyToOne(fetch=FetchType.LAZY)
    private Sosi sosi;
   
    public Sosi getSosi() {
        return sosi;
    }

    public void setSosi(Sosi sosi) {
        this.sosi = sosi;
    }

    public Key getKey() {
        return key;
    }

    public void setKey(Key key) {
        this.key = key;
    }
   
   
    public String getKeyString() {
        return KeyFactory.keyToString(key);
    }
   
    public String getProgram() {
        return program;
    }

    public void setProgram(String program) {
        this.program = program;
    }
   
    public Schedule() {
    }

    public Schedule(String program, Sosi sosi) {
        this.program = program;
        this.sosi = sosi;
    }
}[/code]
일단 App Engine용 JPA에서는 ID 타입이 Long이면 관계형태를 사용할 수 없더라구요. 그래서 앱엔진에서 제공하는 Key타입이 있는데, 이걸 이용해야합니다.

7. Dao만들기
com.mudchobo.sosisochedule.SosiDao.java
[code]package com.mudchobo.sosischedule.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.google.appengine.api.datastore.KeyFactory;
import com.mudchobo.sosischedule.entity.Schedule;
import com.mudchobo.sosischedule.entity.Sosi;

@Repository
public class SosiDao {
    private EntityManager em;
   
    @Autowired
    public void setEntityManager(EntityManager em) {
        this.em = em;
       
        // 소시데이터 추가
        addSosi(new Long(1), "효연");
        addSosi(new Long(2), "윤아");
        addSosi(new Long(3), "수영");
        addSosi(new Long(4), "유리");
        addSosi(new Long(5), "태연");
        addSosi(new Long(6), "제시카");
        addSosi(new Long(7), "티파니");
        addSosi(new Long(8), "써니");
        addSosi(new Long(9), "서현");
    }
   
    public void addSosi(Long id, String sosiName) {
        em.getTransaction().begin();
        em.persist(new Sosi(KeyFactory.createKey(Sosi.class.getSimpleName(), id), sosiName));
        em.getTransaction().commit();
    }
   
    @SuppressWarnings("unchecked")
    public List<Sosi> getSosiList() {
        return em.createQuery("select s from Sosi s").getResultList();
    }

    public Sosi getSosi(Long sosiId) {
        return em.find(Sosi.class, sosiId);
    }
   
    @SuppressWarnings("unchecked")
    public List<Schedule> getScheduleList(final Long sosiId) {
        Query q = em.createQuery("select s.scheduleList from Sosi s where s.key = :key");
        q.setParameter("key", KeyFactory.createKey(Sosi.class.getSimpleName(), sosiId));
        return (List<Schedule>) q.getSingleResult();
    }
   
    public void addSchedule(Long sosiId, String program) {
        em.getTransaction().begin();
        Sosi sosi = em.find(Sosi.class, sosiId);
        sosi.getScheduleList().add(new Schedule(program, sosi));
        em.getTransaction().commit();
    }
   
    public void deleteSchedule(String scheduleKey) {
        em.getTransaction().begin();
        Schedule schedule = em.find(Schedule.class, scheduleKey);
        em.remove(schedule);
        em.getTransaction().commit();
    }
}[/code]
EntityManager받을 때 디폴트로 데이터를 넣어줘야 합니다(아까 위에서 말했듯이 프로그래밍적으로만 테이블을 생성할 수 있어서 이런 형태로 데이터를 넣어줘야합니다ㅠㅠ)

일단 실행해보고 데이터가 잘 생성되었는지 보려면 아래와 같은 주소로 접속해보면 됩니다.
http://localhost:8888/_ah/admin
일단 보고 삭제까지는 되는데, 테이블 생성같은 건 안되더라구요. 그리고 여기서 보여지는데에는 한글이 깨지는데 나중에 출력해보면 잘 나오니 걱정마시길-_-
사용자 삽입 이미지
8. Service 클래스 생성
com.mudchobo.sosischedule.service.SosiService.java
[code]package com.mudchobo.sosischedule.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.mudchobo.sosischedule.dao.SosiDao;
import com.mudchobo.sosischedule.entity.Schedule;
import com.mudchobo.sosischedule.entity.Sosi;

@Service
public class SosiService {
   
    @Autowired
    private SosiDao sosiDao;
   
    public List<Sosi> getSosiList()
    {
        return sosiDao.getSosiList();
    }
   
    public Sosi getSosi(Long sosiId) {
        return sosiDao.getSosi(sosiId);
    }
   
    public List<Schedule> getScheduleList(Long sosiId) {
        return sosiDao.getScheduleList(sosiId);
    }
   
    public void deleteSchedule(String scheduleKey) {
        sosiDao.deleteSchedule(scheduleKey);
    }

    public void addSchedule(Long sosiId, String program) {
        sosiDao.addSchedule(sosiId, program);
    }
}[/code]
Service에서 하는 역할은 뭐 없네요-_-

9. Controller생성
스프링3.0에서 새로 추가된 기능인 REST기능입니다.
com.mudchobo.sosischedule.controller.SosiController.java
[code]package com.mudchobo.sosischedule.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.mudchobo.sosischedule.entity.Schedule;
import com.mudchobo.sosischedule.entity.Sosi;
import com.mudchobo.sosischedule.service.SosiService;

@Controller
public class SosiController {
    private static String PREFIX = "/sosischedule";
   
    @Autowired
    private SosiService sosiService;
   
    @RequestMapping(value="/", method=RequestMethod.GET)
    public String index(Model model) {
        List<Sosi> sosiList = sosiService.getSosiList();
        model.addAttribute("sosiList", sosiList);
       
        return "index";
    }
   
    @RequestMapping(value="/schedule/{sosiId}", method=RequestMethod.GET)
    public String getSchedule(
            @PathVariable("sosiId") Long sosiId,
            Model model) {
        Sosi sosi = sosiService.getSosi(sosiId);
        List<Schedule> scheduleList = sosiService.getScheduleList(sosiId);
        model.addAttribute("scheduleList", scheduleList)
            .addAttribute("sosi", sosi);
       
        return "sosi";
    }
   
    @RequestMapping(value="/schedule/{sosiId}/add", method=RequestMethod.POST)
    public String addSchedule(
            @PathVariable("sosiId") Long sosiId,
            @RequestParam("program") String program,
            Model model
            ) {
        sosiService.addSchedule(sosiId, program);
       
        return "redirect:" + PREFIX + "/schedule/" + sosiId;
    }
   
    @RequestMapping(value="/schedule/{sosiId}/{scheduleKey}", method=RequestMethod.GET)
    public String removeSchedule(
            @PathVariable("sosiId") Long sosiId,
            @PathVariable("scheduleKey") String scheduleKey,
            Model model) {
        sosiService.deleteSchedule(scheduleKey);
       
        return "redirect:" + PREFIX + "/schedule/" + sosiId;
    }
}[/code]

10. View jsp파일 생성
소시 리스트를 보여주는 index파일 입니다.
war/WEB-INF/jsp/index.jsp
[code]<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="UTF-8"%>
<%@ page isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>소녀시대 스케줄</title>
</head>
<body>
    <div>
        스케줄 확인하기
        <ul>
        <c:forEach var="sosi" items="${sosiList}">
            <li><a href="/sosischedule/schedule/${sosi.key.id}">${sosi.key.id}. ${sosi.sosiName}</a></li>
        </c:forEach>
        </ul>
    </div>
</body>
</html>[/code]

해당 소시의 스케줄을 보여주는 스케줄 파일입니다.
war/WEB-INF/jsp/sosi.jsp
[code]<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="UTF-8"%>
<%@ page isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>소녀시대 스케줄</title>
</head>
<body>
    <div>
        스케줄 확인하기
        <ul>
        <c:forEach var="sosi" items="${sosiList}">
            <li><a href="/sosischedule/schedule/${sosi.key.id}">${sosi.key.id}. ${sosi.sosiName}</a></li>
        </c:forEach>
        </ul>
    </div>
</body>
</html>[/code]
리다이렉트를 위한 파일입니다. 기존 index.html파일 지우시고, index.jsp파일 생성
index.jsp
[code]<% response.sendRedirect("/sosischedule/"); %>[/code]

앱엔진에 올려보았습니다.
http://2.latest.mudchobosample.appspot.com/sosischedule/

잘 되는 것 같네요.
 
Posted by 머드초보

댓글을 달아 주세요

  1. 2010.02.08 20:00  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • 머드초보 2010.02.12 00:05  댓글주소  수정/삭제

      안녕하세요~ 답변이 늦었네요 ㅠㅠ
      근데, 파일 다 올린 것 같은데...
      저도 만들어놓고 그냥 올린거다보니 ㅠㅠ
      뭐가 빠진건가요? ㅠㅠ

  2. BlogIcon 아따따따뚜겐 2012.03.08 17:07  댓글주소  수정/삭제  댓글쓰기

    우왕 짱입니당

  3. 나그네 2012.11.21 16:26  댓글주소  수정/삭제  댓글쓰기

    안녕하세요.. 구글 앱 엔진 검색하다 들렀습니다
    죄송한데 혹시 괜찮다면 위에서 보여주신 소스를 좀 부탁드려도 될까요??
    가능하시면 actors00@nate.com 으로 부탁좀 드릴게요

  4. BlogIcon timberland españa tiendas 2012.12.24 09:24  댓글주소  수정/삭제  댓글쓰기

    Bruxelles célèbre par une rétrospective de grande ampleur l'expressionniste flamand Constant Permeke (1886-1952). Cet Anversois aimait à dessiner et peindre les marins et paysans des Flandres, http://timberlandbotases.com timberland mujer 2012, qu'il représentait en athlètes aux corps anguleux et massifs et leurs femmes, nues et charnues, http://timberlandbotases.com timberland pro, tout en rondeurs. Il aimait aussi les paysages plats et les cieux de son pays natal. Dans tous les cas, il allait à l'essentiel avec une rare vigueur et sans craindre la provocation. L'accrochage met en valeur ces qualités et propose d'inattendues et justes comparaisons avec deux artistes actuels, Marlene Dumas c?té nus et Thierry de Cordier c, http://timberlandbotases.com timberland baratas?té nuées. Retrouvez cet article sur lemonde.frAnne Frank, passion nippone'Lawrence d'Arabie' en blu-rayLa belle pagaille de Skip & DieFiguration de gauche, abstraction de droiteLa couleur d'une affiche dévoile-t-elle tout d'un film?? Inscrivez-vous aux newsletters du Monde, http://timberlandbotases.com timberland españa catalogo 2011.frDevenez fan de Yahoo, http://timberlandbotases.com timberland earthkeepers! Actu sur Facebook et suivez-nous sur Twitter, http://timberlandbotases.com botas timberland mujer.
    Related articles:


    http://sanmarinosd.com/forums/topic.php?id=967873&replies=1#post-1031745 http://sanmarinosd.com/forums/topic.php?id=967873&replies=1#post-1031745

    http://www.renaissancemoms.com/forums/topic.php?id=595981&replies=1#post-634156 http://www.renaissancemoms.com/forums/topic.php?id=595981&replies=1#post-634156

    http://www.networkleisure.com/social/blog_entry.php?user=fanshuan9441&blogentry_id=945812 http://www.networkleisure.com/social/blog_entry.php?user=fanshuan9441&blogentry_id=945812