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

암튼, "구글 앱 엔진"에서는 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
<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>

일단 sosischedule/*요청은 spring이 받습니다.

4. dispacher-servlet.xml파일과 persistence.xml파일 생성
war/WEB-INF/폴더에 생성
dispatcher-servlet.xml
<?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>


src/META-INF/ 폴더에 생성
persistence.xml
<?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>


5. EMF클래스 생성.
이제 jpa접근할 수 있는 EntityManagerFactory클래스(EMF)를 생성해봅시다.
com.mudchobo.sosi.sosischedule.dao.EMF.java
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();
}
}


6. Entity클래스 생성
일단 Sosi와 Schedule이라는 Entity를 생성할 건데요. 둘의 관계는 1:N관계입니다.
com.mudchobo.sosischedule.entity.Sosi.java
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;
}
}

com.mudchobo.sosischedule.entity.Schedule.java
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;
}
}

일단 App Engine용 JPA에서는 ID 타입이 Long이면 관계형태를 사용할 수 없더라구요. 그래서 앱엔진에서 제공하는 Key타입이 있는데, 이걸 이용해야합니다.

7. Dao만들기
com.mudchobo.sosisochedule.SosiDao.java
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();
}
}

EntityManager받을 때 디폴트로 데이터를 넣어줘야 합니다(아까 위에서 말했듯이 프로그래밍적으로만 테이블을 생성할 수 있어서 이런 형태로 데이터를 넣어줘야합니다ㅠㅠ)

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

Service에서 하는 역할은 뭐 없네요-_-

9. Controller생성
스프링3.0에서 새로 추가된 기능인 REST기능입니다.
com.mudchobo.sosischedule.controller.SosiController.java
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;
}
}


10. View jsp파일 생성
소시 리스트를 보여주는 index파일 입니다.
war/WEB-INF/jsp/index.jsp
<%@ 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>


해당 소시의 스케줄을 보여주는 스케줄 파일입니다.
war/WEB-INF/jsp/sosi.jsp
<%@ 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>

리다이렉트를 위한 파일입니다. 기존 index.html파일 지우시고, index.jsp파일 생성
index.jsp
<% response.sendRedirect("/sosischedule/"); %>


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

잘 되는 것 같네요.
  1. 비밀방문자

    관리자만 볼 수 있는 댓글입니다.

    • 머드초보

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

[로그인][오픈아이디란?]

submit
GTUG(Google Technology User Group)는 말 그대로 구글의 기술(Android, GWT, App engine, wave, 등등)을 사용하는 유저 모임입니다.

GTUG는 전세계적으로 존재하는 아이리스.......-_-가 아니고, 구글 기술을 사용하는 사용자들의 모임인데요. 지금 현재 한국에서만 없어서 이 모임을 만들게 되었다고 하네요. 음 좋은 현상이네요.

두번째 놀러간 구글코리아 사무실이군요.... 사장님께 건의해서 우리 회사 사무실도 구글처럼....-_- 왠지 미로처럼 느껴지는 사무실-_- 게다가 마음껏 먹을 수 있는 저 일용한 식량들-_-

GTUG Seoul 홈페이지입니다.
http://seoul.gtugs.org/

사용자 삽입 이미지

1. GTUG 소개 - 최우형(http://seanchoe.com/)
이 분도 sean이군요. 우리 사장님도 sean인데-_- Seoul GTUG가 생기게된 계기가 미키김씨의 트위터를 보고 왜 국내엔 없어? 라는 말 때문에 만들게 되었다고 하네요. 세계적으로 GTUG는 다음 DevDay와 같은 행사를 하는 듯 합니다. 캠프를 하면서 밤샘 코딩을 하고, 구글 기술을 이용해서 코딩을 하는 뭐 그런 행사를 하나 봅니다. 음....국내에서도 하면 재미있겠네요^^ 암튼, 모임이 활성화 되었으면 좋겠네요. 사실 구글 기술이 참 대단하지만, 국내에선 많이 사용하질 않아요. 이번 기회에 많이 활성화 되었으면 좋겠네요^^

2. Web2.0을 통한 발전적인 협업 - 강팀장(http://www.ebizstory.com/)
음 무슨 말인지는 잘 모르겠지만, 구글 캘린더, 닥스 등의 유틸을 이용해서 협업하자는 건가요?-_- 오픈 소스를 통해 예를 설명하셨는데, 오픈소스는 망이라는 인프라때문에 성장하게 되었는데, 사람간의 관계도 망으로 치면 협업으로 발전할 수 있다는 그런 얘기인 듯.
앞으로 서비스는 네트워크(sns 등)와 관련된 서비스가 뜰 것이라고 하는데, 요즘 계속 뜨고 있지 않나요-_-
그리고, 이런 도구를 이용해서 커뮤니케이션의 문제를 해결하는 것이 우선이라는 말인 듯 하네요. 개발과 기획의 트러블, 개발자들 간의 트러블 등 일하다보면 진짜 많은 충돌이 일어나죠^^ 커뮤니케이션의 부재라고 할 수 있죠^^
그리고, 마지막으로 공유의 중요성을 얘기했습니다. 저도 공유의 중요성을 실천하며 블로그에 저만의 삽질로그를 기록하는데-_- 방문자들과 커뮤니케이션을 하고 그러는데에 좀 도움이 되는 것 같네요^^

3. 스마트 폰으로 본 모바일 동향 - 최재규
요즘 최대의 관심사 스마트폰이군요. 아이폰이 출시되면서 국내에서도 스마트폰 급열풍이 불고 있죠^^ 재미있는 강의였어요^^ 현재 스마트폰의 벤더는 춘추전국시대인 듯-_-
Nokia, MS, Apple, Google, Samsung 등 다양하죠. 지금은 옛날 OS전쟁과 같은 전쟁을 하고 있다고 합니다. 앞으로 어떻게 될지 궁금하군요.
그리고, 내년되면 안드로이드폰이 많이 나올 것이라고 전망을 하는군요. 국내에서도 SKT나 KT, LGT 전부다 내년에 안드로이드비중을 늘리겠다는 뉴스가 나왔었죠. 안드로이드폰을 많이 도입하는 이유는 애플은 혼자서 다 먹는 수익모델인 반면, 안드로이드는 그렇지 않기 때문이죠. 사실 애플 아이폰을 KT에서 미친듯이 팔아도 KT에서 남는 건 별로 없다고 하죠. 하지만 안드로이드는 소스도 오픈되어 있고, 통신사 만의 커스텀마이즈가 가능하기 때문에(제발 SKT 드로이드폰에다가는 손대지 말아죠 ㅠㅠ), 뭐 다 같이 수익을 낼 수 있는 모델을 구성할 수 있다고 합니다. 게다가 MS Mobile처럼 os에 라이센스 비용도 들어가지 않구요. 구글은 모바일광고시장을 개척해서 돈을 벌게 될 것이라고 하고. 대세는 안드로이드인가.....-_-
게다가 아이폰 어플 개발은 Object-C라는 C기반 언어를 채용한 반면, 안드로이드는 Java를 채택했죠. 이 분말에 의하면 많은 개발자 포섭의 이유라고 하네요. Java개발자가 많아서 그런 듯.
암튼, 요즘 스마트폰은 계속 발전하고 있습니다. 근데 요즘은 스마트폰이아니라 그냥 작은 컴퓨터죠-_- 뭐 스마트폰으로 못하는 게 없어요. 소비자들의 니즈가 계속 변화하는데, 그 변화에 대한 빠른 대처만이 스마트폰의 전쟁에서 살아남을 수 있을 듯 합니다^^
그리고, 지금 플랫폼 전쟁이고, 차후에 컨텐츠 전쟁이라고 했는데, 지금 뭐 동시에 일어나는 것 같은 느낌이 좀 드는데-_- 앱스토어에서는 이미 많은 컨텐츠를 확보를 했죠. 이미 나올 수 있는 건 다 나온 상태인 듯 ^^ 뭐 암튼 전쟁입니다^^ 누가 승리하든간에 이 전쟁으로 많은 IT발전이 다가오겠죠^^

4. 구글 App Engine과 클라우드 컴퓨팅 소개 - 이현남(http://opencloud.kr/)
App Engine은 시간관계상 못했는데요. App Engine에 대해서 좀 더 듣고 싶었는데-_-
암튼, 클라우드 컴퓨팅. 효율적인 자원활용을 할 수 있게 도와주는 컴퓨팅이죠. 사실 사용자들은 이게 클라우드컴퓨팅인지 잘 모르지만, 뒷단에서는 클라우드 컴퓨팅인게 클라우드 컴퓨팅이죠(뭔소리야-_-). 암튼, App Engine도 클라우드 컴퓨팅 같은 것이라고 하네요. App Engine에 올린 웹서비스가 트래픽이 올라가면 구글에서 알아서 트래픽 조정 및 자원할당을 해주는 뭐 그런 형태죠^^ 암튼, 효율성을 높여서 비용을 낮추는.....-_-(비용을 낮춘다고 하면 고객들이 참 좋아하죠!)

5. About GWT(Google Web Toolkit) - 윤진호(http://blog.naver.com/bosimno1)
GWT가 뭔지 잘 몰랐는데, 설명을 듣고 나니 좀 알것 같네요. 그냥 클라이언트랑 서버랑 뭐 합체형태로 개발하는 툴킷인가보네요. 브라우저별로 JS파일도 따로 내려줘서 Cross Browsing까지 해결해주는 좋은 툴이네요. 여러가지 컴포넌트들도 있어 손쉽게 개발할 수 있는 것인 듯.
하지만, 오늘 예제가 없어서-_- 정확한 실체는 파악하지 못했군요 ㅠ 예제를 볼라고 했는데, 시간이 없어서 못했네요^^ 난 시간이 남아도는데, 그냥 좀 해주시지.....ㅠㅠ

오늘 모임에서 조금 아쉬웠던 것은 구글 기술에 대한 부재(?)입니다. 음 구글 기술에 대해 좀 더 발표를 했었으면 좋았을텐데^^ App Engine이나 GWT는 매우 흥미로운 기술인데, 좀 더 실체를 보고 싶네요. 구글 닥스와 캘린더 연동이나 구글wave 관련 효율적인 커뮤니케이션 예를 보여주면 매우 좋을 듯^^
그래도 많은 정보를 얻어간 세미나였던 것 같아요. 앞으로 계속 되었으면 좋겟네요^^
  1. OpenID Logo 외계인

    저희 학교 선배님도 갔었는데 사진 올려주신다고 하네요. 저도 다음에 꼭 가보고 싶어요.

  2. OpenID Logo http://seanchoe.myopenid.com/

    와~ 참석해 주시고 좋은 후기도 남겨 주셔서 너무 감사해요!^^ 다음 모임에는 좀 더 구글 기술에 집중 할 수 있도록 잘 운영해 보도록 하겠습니다!

    • 머드초보

      앗~ 운영자님께서~ ^^
      방문해주셔서 감사합니다 ^^ 저도 열심히 활동하고 싶네요^^

[로그인][오픈아이디란?]

submit
구글은 참 재미있는 것을 많이 만드네요.
이번에 만든 구글웨이브도 참 재미있는 서비스입니다^^

사용자들과 쉽게 커뮤니케이션을 할 수 있는 방법을 생각해내다가 이런 서비스를 만들게 된게 아닌가 싶습니다.
근데, 처음에 접근하게 되면 이게 뭔가라는 느낌의 화면으로 다가옵니다. 처음에 접근할 때 조금 힘든 면이 있네요. 저도 누가 설명해주지 않았으면 대체 뭐하는 건지 몰랐을테니까요. 회사동료분의 설명으로 조금 이해를 했습니다-_-

아래 동영상을 보면 조금 이해가 갈지 모르겠네요. 하지만 써보지 않고 아래 동영상부터 본다면 혼란이 올 것 같기도 한데....우선 한번 써보게 되면 아래동영상이 좀 이해가 갈 듯

구글 웨이브의 기본적인 기능 15가지


우선 처음에 띄웠을 때 화면입니다.
사용자 삽입 이미지
우선 간단하게 Wave를 설명하자면, New Wave를 통해 새로운 Wave를 작성하게 되면 사용자를 초대해서 같이 글을 편집하고 얘기할 수 있는 실시간 커뮤니케이션 도구라고 보시면 됩니다.

상대방이 Replay을 작성하든, 글을 고치든 간에 실시간으로 편집하는 게 다 보입니다-_-
FireBug로 Net요청에 대해서 보게 되면 글을 쓸 때마다 해당 channel로 변경된 것을 날리나 봅니다. 그래서 해당 channel에 접속한 사용자에게 변화를 보여주게 되는 듯(개발자다보니 이런 것에만 관심이......-_-)
사용자 삽입 이미지
그리고 구글다운 사용자중심의 UI가 장점입니다. 구글 웨이브는 그런 UI의 결정판인 듯-_- UI도 많이 고민한 흔적이 보이네요^^ 저런 거 개발하려고 하면 정말 힘들텐데....역시 대단하네요^^

결론은 구글웨이브는 올인원(?)을 노리는 것 같습니다. 저 같은 경우에는 올인원을 좋아합니다-_- 올인원이라는 것을 처음 본 곳은 아마 Windows 설치이미지에서 본 것 같네요. 다양한 버전을 한 CD이미지에서 설치를 할 수 있는 것이였는데, 다 설치할 이유는 없었지만 모든 것이 하나에 다 있다는 것이 참 기분이 좋았어요.
이런 구글웨이브도 보면 문서편집(위키형태)과 사용자들과의 채팅 및 다양한 커뮤니케이션(위젯을 통해 게임 같은 것도 가능하겠죠^^), 이메일, 네이트온 같은 메신저 등~ 그냥 기존에 나와있는 SNS서비스를 버로우 시키기 위한 올인원 시스템인 듯 합니다.
하지만, twitter나 me2day와 같은 마이크로블로그, 싸이월드나 facebook같은 서비스도 SNS를 지향하고 있지만, 구글웨이브와는 조금 틀린 성격이라고 느껴지는군요. 이것들의 경쟁자라고 느껴지진 않네요. 서로 공존할 수 있는 부분인 듯 합니다. 하지만 구글웨이브에서 트위터같은 서비스도 맘만 먹으면 API를 이용해서 가져올 수 있는 서비스를 만들 수 있을 듯 합니다. 그렇게 되면 구글웨이브가 통합을 할 수 있을지도.....-_-
  1. 코즈

    그렇군요.
    도대체가 뭐하는 물건인지 당신은 설명을 들었건만,,
    왜 나에겐 알려주지 않는 겁니까 ㅋㅋ

  2. OpenID Logo 로로스타

    heojeongho@googlewave.com 친추 점요

[로그인][오픈아이디란?]

submit
그냥 막 하면 잘 안되더군요. 구글링을 해보니 여러 블로그에서 이런 시도를 한 흔적들이 있었습니다-_- App Engine이 자바를 지원한다고 할 때부터 외국에서는 다양한 시도를 하나봅니다-_- 이번 Spring BlazeDS Integration도 누가 먼저 시도를 한 흔적이 있었네요.

이번 Spring BlazeDS Integration 1.0.1릴리즈 기념과 Spring교육 끝난 기념으로 간만에 삽질해봤습니다.
하지만, messaging 등의 심화적인 것은 못해보구요. 우선 서비스를 가져오는지만 해봤습니다.

삽질환경

- IDE
Eclipse3.5와 구글앱앤진 플러그인 - http://code.google.com/intl/ko-KR/eclipse/docs/download.html
Flex Builder 3.0.2
JDK 1.6.0 U14
- 라이브러리
Spring Framework 2.5.6
BlazeDS 3.2.0.3978
Spring BlazeDS Integration 1.0.1
Jackson 1.2.0
Cglib 2.1.3

1. App Engine 프로젝트 생성
프로젝트 생성하고 나서 라이브러리들을 다 복사합니다. 저는 아래와 같이 라이브러리를 복사했습니다.
기존App Engine lib, spring.jar, spring-webmvc.jar, blazeds.war에 있는 lib, jackson-core-lgpl-1.2.0.jar, jackson-mapper-lgpl-1.2.0.jar, cglib-nodep-2.1_3.jar, org.springframework.flex-1.0.1.RELEASE.jar
그리고, appengine-web.xml파일에 한줄 추가합니다.
<sessions-enabled>true</sessions-enabled>

이거 필요한건지는 잘 모르겠군요-_-
WEB-INF폴더아래 blazeds.war파일에 들어있는 flex폴더를 복사합니다(*-config.xml의 파일이 있는 것)

2. 서비스 클래스 생성
이제 서비스를 만들어봅시다. 초간단 헬로우서비스를-_-
src폴더에 만들어봅시다. 전 com.mudchobo.springblazedsserver.service패키지에 HelloService클래스를 생성했음!
HelloService.java
package com.mudchobo.springblazedsserver.service;

public class HelloService {

public String sayHello(String name) {
return "Hello, " + name;
}
}


3. 설정파일 생성 및 설정
스프링관련 설정을 해야해요. web.xml에서 디폴트로 설정된 servlet설정을 지우고 아래를 추가
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/config/*-context.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- SpringDispatcherServlet -->
<servlet>
<servlet-name>flex</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>flex</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>

flex라는 이름의 서블릿을 만들었으니 스프링 설정파일이름인 flex-servlet.xml을 생성합니다.
flex-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:flex="http://www.springframework.org/schema/flex"
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-2.5.xsd
http://www.springframework.org/schema/flex
http://www.springframework.org/schema/flex/spring-flex-1.0.xsd">

<flex:message-broker />

<flex:remoting-destination ref="helloService"/>
</beans>

flex라는 네임스페이스를 제공하는데요. <flex:message-broker />이 한줄로 모든 설정이 되어버립니다. M1 삽질했을 때에는 네임스페이스 없어서 bean써주고, 다 설정했던 기억이 나네요. 네임스페이스로 한줄로-_-

remoting-destination태그는 destination을 설정하는 건데, 해당 bean을 ref하면 해당 bean이름으로 destination으로 flex에서 가져올 수 있어요.
그럼 서비스를 설정할 설정파일을 생성해봅시다. configlocation을 /config/*-context.xml을 잡았는데, /config/services-context.xml파일을 만들어봅시다^^
services-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<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-2.0.xsd">

<bean id="helloService" class="com.mudchobo.springblazedsserver.service.HelloService" />

</beans>

방금 만든 HelloService를 bean으로 설정.

마지막으로 flex/services-config.xml에서 default-channels를 추가합시다.
<services>
<service-include file-path="remoting-config.xml" />
<service-include file-path="proxy-config.xml" />
<service-include file-path="messaging-config.xml" />
<default-channels>
<channel ref="my-amf"/>
</default-channels>
</services>

그리고 이것도 추가해야해요.
<system>
<manageable>false</manageable>
....
</system>

이거 추가안하면 앱엔진에서 이런 에러로그를 뿜음-_-
org.springframework.beans.factory.BeanCreationException: Error creating bean with name '_messageBrokerDefaultHandlerMapping': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '_messageBroker': Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanInitializationException: MessageBroker initialization failed; nested exception is java.lang.NoClassDefFoundError: java.lang.management.ManagementFactory is a restricted class. Please see the Google App Engine developer's guide for more details.

관리자 기능이라고 하는 것 같은데, 정확히 뭔지는 잘 모르겠지만, 끄면 잘 됩니다-_-

4. 클라이언트 프로젝트 생성
flex project를 생성할 때 이렇게 생성해주면 편합니다.
Flex Project선택 -> Project name쓰고, Application server type은 J2EE, Create combined Java~~는 체크해제, Use remote object access service는 체크하고, Next.
그 다음 Serverlocation 셋팅을 Root folder는 AppEngine의 war폴더를 지정해주면 되구요.
Root URL은 앱엔진 기본 실행 경로인 http://localhost:8080하면 되구요. Context root는 /로 지정하면 됩니다.
그러면 디버그나 Run시에 localhost:8080/프로젝트명/프로젝트명.html로 실행이 돼요.
코드는
SpringBlazeDSClient.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">

<mx:RemoteObject id="srv" destination="helloService" />
<mx:TextInput id="inputName" />
<mx:Button label="전송" id="btnConfirm" click="srv.sayHello(inputName.text)" />
<mx:Label id="labelResult" text="{srv.sayHello.lastResult}" />

</mx:Application>

이 코드 너무 활용하는 것 같아-_- 암튼 destination은 helloService로 설정했기 때문에 이걸로 지정하면 됩니다.

5. 이제 배포 및 실행
이제 swf파일도 appengine프로젝트에 생성하고, AppEngine을 배포하고 실행하면 또다른 에러를 보실 수 있습니다-_-
[RPC Fault faultString="Detected duplicate HTTP-based FlexSessions, generally due to the remote host disabling session cookies. Session cookies must be enabled to manage the client connection correctly." faultCode="Server.Processing.DuplicateSessionDetected" faultDetail="null"]
와....미쳐버립니다. 이건 뭔가....검색해보니 앱엔진이 여러 서블릿배포할 때 1개의 클라이언트 정보를 동일하게 배포를 해서 어쩌구 라고 번역기를 돌리니 써있네요-_- 이걸 픽스한 jar파일이 돌아다닙니다-_-
기존 flex-messaging-core.jar파일을 위 파일로 교체해주면 되더군요.

이제 실행하면 잘 될겁니다.
실행주소입니다.
http://mudchobo1.appspot.com/SpringBlazeDSClient/SpringBlazeDSClient.html


사용자 삽입 이미지

스크린샷.....

messaging이나 security적용한 것도 한번 해봐야겠네요.

참고사이트
http://www.adobe.com/jp/devnet/flex/articles/google_app_eng_w_beazeds_p2.html
http://martinzoldano.blogspot.com/2009/04/appengine-adobe-blazeds-fix.html
  1. 빠방

    좋은 예제 감사드립니다 (꾸벅)
    덕분에 spring blazeds intergration 1.0.1과 ibatis 연동에 성공했습니다.
    이전에 올려주신 연동예제와 새로 릴리즈된 1.0.1 예제가 아니었으면 짧은 영어실력때문에 도저히 알아먹지 못해고 포기해버렸을꺼에요 ㅠㅠ

    • 머드초보

      아네 도움이 되셨다니 다행이네요.
      저도 짧은 영어실력이어서....ㅠㅠ

  2. 옹씨루

    좋은 정보 감사합니다.

[로그인][오픈아이디란?]

submit
SpringFramework가 되는지 삽질하던 중 데모에 있는 샘플을 하나잡아서 convert를 시켰습니다-_-
놀랍게도 잘 되네요. 하는 도중 약간의 혈압이 상승했지만요-_-

우선 샘플주소입니다.
http://springguestbook.appspot.com

소스코드 주소입니다. trunk/SpringGuestBook입니다^^
http://my-svn.assembla.com/svn/mudchobosample

마치...그냥 앱엔진에서 제공하는 샘플이랑 같아보여서 사기를 치는 것 같지만, Model2방식의 예제입니다ㅠ

우선 하면서 가장 처음에 겪는 문제점입니다.
Your Web Application Project must be configured to use a JDK in order to use JSPs.

JSP를 쓰려면 jdk를 설정해야한다는 뜻인데요. 보통 JDK를 깔고, 이클립스를 실행하면 디폴트로 JRE가 잡혀있어요. 이걸 JDK로 추가해서 바꿔주시면 돼요.

이클립스에서 Window -> Preferences -> Java -> Installed JREs선택.
Add -> Standard VM -> JRE home에서 Directory선택해서 JDK주소를 찾으세요.
디폴트로 설치하셨다면 C:\Program Files\Java\jdk1.6.0_12 여기에 있을겁니다.
폴더만 선택하고, Finsh를 누르면 추가가 되었습니다. 체크박스를 jdk로 옮겨주세요. 그러면 저 위에 에러 안날꺼에요.

그다음으로 겪는 문제점은....-_- 앱엔진에서 지원안하는 클래스를 쓰는 곳이 있는 것 같아요.
spring mvc를 사용하려면 spring.jar하고 spring-webmvc.jar 두개만 있으면 되는데요. 이거 두개 lib폴더에 넣어놓고 서버에 디플로이 시키면
exception is java.lang.NoClassDefFoundError: javax/naming/NamingException
App engine로그를 볼 수 있는데, 거기서 로그를 보면 클래스를 찾을 수 없다고 나와요. spring-orm.jar에서 쓰는 것 같더라구요. 어차피 구글앱엔진은 jdo만 지원해서 jdo only라이브러리가 있습니다-_- 그걸로 바꿔주시면 돼요.
전 라이브러리복사할 때
spring-beans.jar, spring-context-support.jar, spring-context.jar, spring-core.jar, spring-jdbc.jar, spring-orm-jdo-only-2.5.6.jar, spring-test.jar, spring-tx.jar, spring-web.jar, spring-webmvc.jar를 복사했네요.
여기서 spring-orm-jdo-only-2.5.6.jar는 저도 어디서 받은거라.....-_-
그러고 디플로이하면 잘 됩니다.

또 한가지 문제점은 eclipse에서 제공하는 dynamic web project에서는 WEB-INF/lib에 library파일을 복사하면 자동으로 클래스를 코드힌트로 쓸 수 있는데, 이놈은 코드힌트를 할 수 없어요-_- 그래서 수동으로 추가를 해줘야해요.
프로젝트 이름에 Properties를 선택하고, Java Build Path에서 Add JARs에서 추가한 spring파일 등을 선택해서 추가해줘야 에러가 안나네요^^(이건 뭐 다른 방법이 있을 지도.....-_-저에게 최선의 방법이였다는 ㅠ)

또 JDO라는 걸 전혀 몰라서 조금 고생했는데, Hibernate같은 orm이더군요. 사실 아직도 잘 모르겠습니다. 좀 더 알아봐야할 것 같네요. orm하면 Hibernate랑 JPA밖에 없는 줄 알았는데, 뭐 디게 많네요ㅠ

또.....-_- localhost에서 Datastore테스트를 하면 war/WEB-INF/appengine-generated라는 폴더가 생겨요. 디비가 저장되는 것 같은데, 저게 있는 상태에서 디플로이하면 안돼요. 지우고 하면 됩니다.

또 이런저런 문제가 있었는데, 기억이 안나네요. 그래도 정말 대단한 것 같습니다. 대세는 클라우드-_-

참고자료
http://peterbacklund.blogspot.com/2009/04/running-spring-on-google-app-engine.html
http://groups.google.com/group/google-appengine-java/browse_thread/thread/f1a541fe52e172dd

  1. 이학도

    좋은 자료 감사.^^

  2. 이학도

    svn repo는 www.assembla.com에서 돈주고 호스팅 받았어요?

    • 머드초보

      저기assembla는 svn이랑 trac같은 개발환경을 제공하는데요.
      public공간으로 만들면 무료이고, private공간을 만들면 돈들어가요.
      다 오픈하면 무료에요 ^^

[로그인][오픈아이디란?]

submit