메인 컨텐츠로 가기

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

Google App Engine으로 XML 데이터 가져오기

Google App Engine의 지속적 오브젝트 데이터베이스로 XML 파일에 로컬로 저장된 대용량 데이터 업로드

Mr Joseph P. McCarthy, Software Developer, IBM
Photo of Joseph McCarthy
Joseph McCarthy is a Java Developer in the Dublin Software Lab. He joined IBM in July 2002 after graduating from the University of Limerick with a Bsc in Computer Systems, and a Graduate Diploma in Computer Engineering.

요약:  Google App Engine은 2008년 4월에 출시되었고, Python을 사용하여 CSV 파일에 저장된 대용량 데이터를 업로드하는 메소드가 포함되어 있습니다. 그 다음 해에는 Java™ 언어 지원도 추가되었습니다. 지금까지 App Engine은 대용량 업로드에 대한 Java 원시 지원이 없고, CSV는 대용량 업로딩 도구로서 지원되는 유일한 데이터 스토리지 매체로 남아있습니다. 이 기사에서는 XML 문서에서부터 App Engine 지속적 데이터베이스에 데이터를 저장하는 다양한 메소드에 대해 알아봅니다.

원문 게재일:  2010 년 9 월 07 일 번역 게재일:   2010 년 12 월 21 일
난이도:  중급 원문:  보기 PDF:  A4 and Letter (89KB | 25 pages)Get Adobe® Reader®
페이지뷰:  3207 회
의견:  


백그라운드

2008년 4월에 시작된 Google App Engine(GAE)(링크는 참고자료 참조)은 Google에서부터 서비스를 호스트하는 무료 웹 애플리케이션이다. 초기에는 Python에서 개발된 애플리케이션만 지원했으며, 2009년 4월에는 Java 언어 지원이 추가되었다.

자주 사용하는 약어

  • API: Application Programming Interface
  • CPU: Central processing unit
  • CSV: Comma-separated values
  • HTML: HyperText Markup Language
  • IDE: Integrated development environment
  • REST: REpresentational State Transfer
  • SAX: Simple API for XML
  • SOAP: Simple Object Access Protocol
  • UI: User Interface
  • URI: Uniform Resource Identifier
  • URL: Uniform Resource Locator
  • WSC: Web Services Connector
  • WSDL: Web Services Description Language
  • W3C: World Wide Web Consortium
  • XML: Extensible Markup Language

애플리케이션에 대해 제공된 개발 환경은 개발 도중에 데이터를 지속하도록 로컬 데이터베이스를 작성하고, 사이트는 그 자체로 데이터를 지속적 오브젝트나 엔티티로 저장할 수 있다. 이러한 엔티티는 Java Data Object(JDO) 어노테이션으로 어노테이션을 작성한 Plain Old Java Objects(POJOs)를 사용하여 작성된다. 하지만, 그 환경은 두 개의(로컬과 배치된 것) 데이터베이스 사이에 직접 데이터를 업로드하는 방법은 없다. Python 환경은 CSV 형식에서 저장된 데이터의 대용량 업로드를 허용한다. 이는 공식적으로 Java 언어에서 데이터의 원시 대용량 업로드를 지원하지 않는다. 권장 메소드는 애플리케이션의 Python 버전을 사용하여 데이터를 업로드하고 Java 클래스를 사용하여 데이터를 액세스하는 것이지만, 이는 Python의 작업 지식이 필요하며 CSV 형식에 표현되는 데이터의 성능에 의존한다.

XML은 유연한 텍스트 기반 형식이다. 최근 몇 년간 온라인과 오프라인 애플리케이션은 다양한 방법으로 사용하기 위해 점점 더 XML로 데이터를 저장한다. 인터넷 전반에 걸친 XML의 확산에도 불구하고 GAE는 XML 문서에 저장된 데이터에 대한 대용량 업로드 서비스를 제공하지 않는다.

SAX는 XML에 대한 직렬형 액세스 구문 분석기이다. XML 문서에 대해 SAX 기반 구문 분석기를 쓰면 문서를 구문 분석하는 동안 문서의 다양한 요소가 발생할 때에 (문서의 시작, XML 요소의 시작, 요소의 끝, 문자 및 기타 등등) 트리거하는 몇 가지 콜백 메소드를 사용할 수 있다.


간단한 XML 지속성

XML 문서에서 GAE의 데이터 저장소로 추가하는 가장 간단한 메소드는 애플리케이션의 일환으로 문서를 업로드하고, 사용자 정의 SAX 기반 구문 분석기를 사용하여 문서의 각 항목을 기반으로 애플리케이션에서 클래스를 작성하는 것이다.

조직에서 직원의 목록이 들어있는 간단한 XML 문서를 살펴보고, 이를 GAE 프로젝트에 추가하여 요소에 대한 클래스를 작성하고 이를 엔티티로 저장하자.

XML 문서 employees.xml(목록 1 참조)에는 간단한 형식이 있다. 각 employee에는 하나의 속성이 있고(id), 네 개의 요소인 firstName, surName, emailAddresshireDate가 있다.

employees.xml 파일은 이 기사 내내 사용될 것이다. 이 기사에 설명된 파일과 모든 기타 소스 파일을 다운로드에서 다운로드할 수 있다.


목록 1. employees.xml
    
<?xml version="1.0" encoding="UTF-8"?>
<employees>
    <employee id="1">
        <firstName>Rickey</firstName>
        <surName>Torres</surName>
        <emailAddress>rickey.torres@employer.com</emailAddress>
        <hireDate>1996-09-17</hireDate>
    </employee>
    <employee id="2">
        <firstName>Karisa</firstName>
        <surName>Moore</surName>
        <emailAddress>karisa.moore@employer.com</emailAddress>
       <hireDate>1996-04-08</hireDate>
    </employee>
    <employee id="3">
        <firstName>Aaron</firstName>
        <surName>Wilson</surName>
        <emailAddress>aaron.wilson@employer.com</emailAddress>
        <hireDate>2000-01-05</hireDate>
    </employee>
</employees>

목록 2에는 다음 POJO 클래스로 이를 표현한 다음에 JDO로 어노테이션을 작성하는 방법을 보여준다(어노테이션을 작성한 클래스에 적절한 Java 가져오기 및 set 함수가 가정됨).


목록 2. Employee.java
POJO Employee.java Annotated Employee.java
Public class Employee {

    private Long id;
    private String firstName;
    private String surName;
    private String emailAddress;
    private Date hireDate;

    public Employee() {

    }

    public void setFirstName(String firstName) {
       this.firstName = firstName;
    }
    public void setSurName(String surName) {
       this.surName = surName;
    }
    public void 
       setEmailAddress(String emailAddress) {
       this.emailAddress = emailAddress;
    }
    public void setHireDate(Date hireDate) {
       this.hireDate = hireDate;
    }
    public void setId(Long id) {
       this.id = id;
    }
}

@PersistenceCapable(identityType = 
                   IdentityType.APPLICATION)
public class Employee {

    @PrimaryKey
    @Persistent(valueStrategy = 
                   IdGeneratorStrategy.IDENTITY)
    private Long  id;
    @Persistent
    private String firstName;
    @Persistent
    private String surName;
    @Persistent
    private String emailAddress;
    @Persistent
    private Date hireDate;

    public Employee() {

    }
}


더 복잡한 XML 문서는 Simple도 사용할 수 있다. 튜토리얼로의 링크는 참고자료를 참조한다.

Java 언어에서 SAX 구문 분석기를 작성하려면 org.xml.sax.helpers.DefaultHandler 클래스를 확장하고 목록 3과 같이 문서를 구문 분석해야 하는 메소드를 오버라이드한다.


목록 3. EmployeeHandler.java

package com.xmlimport.employee;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.xmlimport.XMLImportPersistenceManagerFactory;

public class EmployeeHandler extends DefaultHandler {
    private static final Logger log = Logger.getLogger(EmployeeHandler.class.getName());
    private static final SimpleDateFormat hireDateFormat = 
				new SimpleDateFormat("yyyy-MM-dd");

    private Stack<Employee> employeeStack;
        private ArrayList<Employee> employees;
        private PersistenceManager pm = null;
        private String characters;
	
        public EmployeeHandler() {

                SAXParserFactory factory = SAXParserFactory.newInstance();
                try {

             pm = XMLImportPersistenceManagerFactory.get().getPersistenceManager();
             SAXParser saxParser = factory.newSAXParser();
             saxParser.parse(new InputSource("./employees.xml"), this);
             pm.makePersistentAll(employees);

                } catch (Throwable t) {

                    t.printStackTrace();

                }
                finally {

                    pm.close();
                }

	}

    public void startDocument() 
                throws SAXException {

                employeeStack = new Stack<Employee>();
                employees = new ArrayList<Employee>();
		
    }

        public void startElement(String namespaceURI, 
                             String localName,
                             String qualifiedName,
                             Attributes attributes) 
                throws SAXException {

                if (qualifiedName.equals("employee")) {

                    Employee employee = new Employee();
                    employee.setId(Long.parseLong(attributes.getValue("id")));
                    employeeStack.push(employee);

                }

        }

        public void endElement(String namespaceURI, 
                           String simpleName,
                           String qualifiedName) 
                throws SAXException {

                if (!employeeStack.isEmpty()) {

                    if (qualifiedName.equals("employee")) {

                        employees.add(employeeStack.pop());


                    } 
                    else if (qualifiedName.equals("firstName")) {

                        Employee employee = employeeStack.pop();
                        employee.setFirstName(characters);
                        employeeStack.push(employee);

                    } 
                    else if (qualifiedName.equals("surName")) {

                        Employee employee = employeeStack.pop();
                        employee.setSurName(characters);
                        employeeStack.push(employee);

                    } 
                    else if (qualifiedName.equals("emailAddress")) {

                        Employee employee = employeeStack.pop();
                        employee.setEmailAddress(characters);
                        employeeStack.push(employee);

                    } 
                    else if (qualifiedName.equals("hireDate")) {

                        Employee employee = employeeStack.pop();
                        try {
                        employee.setHireDate(hireDateFormat.parse(characters));
                        } catch (ParseException e) {

                    log.log(Level.FINE, "Could not parse date {0}", characters);
                        }
                        employeeStack.push(employee);

                    }

                }

        }

        public void characters(char buf[], int offset, int len) 
                throws SAXException {

                characters = new String(buf, offset, len);

        }

}

새 <employee> 요소를 구문 분석할 때마다 새 Employee 오브젝트를 작성하고 이를 Stack에 추가한다. 각각 다른 요소가 구문 분석될 때에 Employee 오브젝트를 스택에서 빼내어 연관된 설정 함수를 호출하고 오브젝트를 다시 스택으로 밀어 넣는다. employee close 요소가 구문 분석될 때에 스택에서부터 완료된 오브젝트를 빼고 이를 List 오브젝트에 추가한다. 전체 문서가 구문 분석될 때에 PersistenceManager로 List에서 각 오브젝트를 지속한다.

"Using the DataStore with JDO" 안내서에 의하면 PersistentManager 오브젝트를 작성하는 것은 CPU 시간 측면에서 매우 비용이 많이 든다. 안내서에서는 정적 최종 변수를 사용하여 애플리케이션 시작 시에 오브젝트를 작성하면 그 다음에 필요할 때마다 이 오브젝트를 받으라고 권장한다.

목록 4에 PersistenceManagerFactory 클래스를 보여준다. 이 동일한 클래스는 패키지 이름과 클래스 이름을 변경하여 각 GAE 프로젝트에 사용할 수 있음을 주목하자.


목록 4. XMLImportPersistenceManagerFactory.java

package com.xmlimport;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class XMLImportPersistenceManagerFactory {

    private static final PersistenceManagerFactory pmfInstance = JDOHelper.
        getPersistenceManagerFactory("transactions-optional");

    private XMLImportPersistenceManagerFactory() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

마침내 EmployeeHandler를 호출하고 Employee 오브젝트를 작성하는 서블릿을 작성해야 한다. 그 다음에 서블릿 정의를 web.xml 파일에 추가하면 /CreateEmployee URL은 여기로 다시 이동할 것이다. 목록 5는 web.xml 파일을 보여준다.


목록 5. web.xml

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
    <servlet>
    <servlet-name>CreateEmployeeServlet</servlet-name>
    <servlet-class>com.xmlimport.servlet.CreateEmployeeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    <servlet-name>CreateEmployeeServlet</servlet-name>
    <url-pattern>/CreateEmployee</url-pattern>
    </servlet-mapping>
</web-app>

목록 6은 서블릿을 보여준다.


목록 6. CreateEmployeeServlet.java

package com.xmlimport.servlet;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.xmlimport.employee.EmployeeHandler;

public class CreateEmployeeServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

	    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {

        new EmployeeHandler();
        PrintWriter out = response.getWriter();
        out.println(“Employees Created”);


    }

	
}

이러한 파일을 표준 GAE Java 프로젝트에 추가하고 테스트 서버를 시작한다. 브라우저에 http://localhost:8080/CreateEmployee/를 입력하면, 잠시 후에 Employees Created 메시지가 화면에 나타날 것이다. http://localhost:8080/_ah/admin/datastore에서 로컬 데이터 저장소 뷰어를 열면 새로 작성된 Employee 오브젝트를 볼 수 있다.


수동으로 입력된 XML 데이터 지속하기

이 솔루션은 배치된 사이트에 대해 매우 실용적이지는 않다. Employee 오브젝트의 새 설정을 작성하려면 employees.xml 파일을 작성하고 매번 이를 appspot.com에 배치해야 한다. 핸들러의 동작을 약간 변경해 보자. 기존 파일을 구문 분석하는 것이 아니라 핸들러를 변경하여 서블릿의 양식에서부터 텍스트 입력을 구문 분석한다.

먼저 서블릿을 텍스트 영역 입력 상자와 제출 단추가 포함된 양식으로 Java Server Page(JSP)를 열도록 변경한다. 입력 상자에서 입력한 텍스트를 EmployeeHandler로 보내는 단추를 클릭한다. 정확히 예전과 같이 텍스트가 구문 분석될 것이며, 각 새 Employ 오브젝트는 PersistenceManager로 지속적이 될 것이다.

목록 7과 같이 createEmployee.jsp로 다시 이동하는 doGet 메소드를 변경한다.


목록 7. EmployeeServlet.java에서 리팩토링된 doGet 메소드

public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException {

    RequestDispatcher view = request.
        getRequestDispatcher("/createEmployee.jsp");
    view.forward(request, response);
		} 

war 디렉토리의 루트 폴더에서 createEmployee.jsp 파일을 작성한다. 목록 8을 참조하자.


목록 8. createEmployee.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!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=ISO-8859-1">
<title>Create all employee</title>
</head>
<body>

    <form action="/CreateEmployee" method="POST">
        <textarea name="employeeXML" cols="25" rows="25">
        </textarea>
        <input type="submit" value="Create Employee(s)"/>
    </form>
</body>
</html>

양식에서 POST 메소드를 사용하기 때문에 사용자가 Create Employee(s) 단추를 클릭할 때에 호출되는 doPost() 함수를 CreateEmployeeServlet 클래스에 추가해야 한다(목록 9 참조).


목록 9. EmployeeServlet.java로 doPost 메소드 추가

public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException {

    new EmployeeHandler(request.getParameter("employeeXML");
    RequestDispatcher view = request.
        getRequestDispatcher("/createEmployee.jsp");
    view.forward(request, response);

}

이 메소드에서 양식의 텍스트 영역으로 입력한 텍스트를 추출하고 이를 EmployeeHandler로 전송한다. 텍스트가 구문 분석되고 Employee 오브젝트가 작성되면 사용자를 빈 양식이 있는 동일한 페이지로 다시 이동한다.

텍스트를 구문 분석하려면 File 오브젝트에서 텍스트를 구문 분석하는 것이 아니라 텍스트 영역에서 텍스트를 허용하도록 EmployeeHandler의 생성자의 최종 변경을 수행한다. 목록 10과 같이 텍스트를 StringReader 오브젝트로 추가하고 이 새 오브젝트를 구문 분석한다.


목록 10. String 매개변수를 사용하는 EmployeeHandler 생성자

public EmployeeHandler(String employeeXML) {

    SAXParserFactory factory = SAXParserFactory.newInstance();
    try {

        pm = XMLImportPersistenceManagerFactory.get().getPersistenceManager();
        SAXParser saxParser = factory.newSAXParser();
        saxParser.parse(new InputSource (new StringReader(employeeXML)), this);
        pm.makePersistentAll(employees);

    } catch (Throwable t) {

        t.printStackTrace();

    }
    finally {

        pm.close();

    }

}
    

이제 appspot.com으로 먼저 업로드하지 않고 어느 employee 파일이나 웹 애플리케이션으로 추가할 수 있다.


XML 데이터를 업로드하는 웹 서비스 사용하기

이 솔루션은 텍스트 영역에 입력할 수 있는 최대 문자수와 GAE로 전송하는 요청 시 Google이 강제하는 30초 제한시간 둘 다로 제한된다. 문서가 구문 분석되지 않고 오브젝트가 30초 이내로 지속되면 서버에서 예외가 발생하고 오브젝트가 작성되지 않을 것이다.

SOAP는 XML 메시지가 인터넷을 넘어 전송 및 수신되도록 허용하는 프로토콜이다. 각 employee를 작성하기 위해 한 번에 하나씩 GAE에서 실행하는 SOAP 서비스를 사용할 것이다. 동일한 핸들러 클래스를 이전과 같이 다시 사용할 수 있지만 서버에서 실행하는 것이 아니라 클라이언트로 사용할 것이다. Employee 오브젝트를 List에 추가하지 않고 연관된 정보가 SOAP 서비스로 전송되어 GAE에서 동일한 오브젝트를 작성한 다음에 지속된다.

Spring은 SOAP를 비롯하여 RMI, CORBA 및 HTTP 기반 프로토콜을 지원하여 네트워크를 넘어 Java 오브젝트의 RPC 스타일 내보내기 및 가져오기를 허용하는 원격 액세스 프레임워크가 들어있는(다른 모듈 중에) SpringSource에서 개발된 오픈 소스 애플리케이션 프레임워크이다.

Force.com Web Service Connector(WSC)는 스트리밍 구문 분석기를 사용하여 구현된 고성능 웹 서비스 클라이언트 스택이다. WSC를 통해서도 훨씬 더 간편하게 Force.com API(Web Services/SOAP 또는 Asynchronous/REST API)를 사용한다. WSC는 문서(doc) 리터럴 랩핑된 웹 서비스를 호출하는 데 사용할 수 있다. GAE로 사용하기 위한 버전이 사용 가능하다—링크는 참고자료 참조.

cloudwhiz 블로그는 GAE에서 SOAP 웹 서비스를 구현하는 방법에 대한 세부사항을 제시하고(세 부분으로 된 기사의 링크는 참고자료 참조), 이 기사의 마지막 부분에서는 오브젝트를 작성하고 이를 지속하는 SOAP 서비스를 사용하는 방법을 설명할 것이다.

먼저 Web Service Definition Langage(WSDL) File을 사용하는 웹 서비스를 정의해야 한다. 이는 웹 서비스로 처리할 수 있는 조작과 오브젝트를 지정한다. 7행의 targetNameSpace 정의인 http://xmlimport.appspot.com을 주목하자. 이는 나중에 사용자 정의 언마샬러의 규정된 이름으로 사용될 것이다.

이 기사의 목적상 하나의 서비스인 CreateEmployeeService와 하나의 조작인 createEmployee만 필요하다. complexType createEmployeeRequest를 조사하면 이는 기본적으로 id가 속성이 아니라 요소인 점을 제외하고 employees XML 파일에서 항목의 XML Schema Definition(XSD)이다. (목록 11 참조.)


목록 11. employeeService.wsdl

<?xml version="1.0"?>
<definitions 
    name="CreateEmployeeService" 
    targetNamespace="http://xmlimport.appspot.com/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://xmlimport.appspot.com/"
    xmlns="http://schemas.xmlsoap.org/wsdl/">
    <types>
        <schema targetNamespace="http://xmlimport.appspot.com/" 
            xmlns="http://www.w3.org/2001/XMLSchema">
            <element name="createEmployeeRequest" 
                 type="tns:createEmployeeRequest"/>
            <element name="createEmployeeResponse" 
                 type="tns:createEmployeeResponse"/>
            <element name="getEmployeeRequest" 
                 type="tns:getEmployeeRequest"/>
            <element name="getEmployeeResponse" 
                 type="tns:getEmployeeResponse"/>
            <complexType name="createEmployeeRequest">
                <sequence>
                    <element name="firstName" type="string"/>
                    <element name="surName" type="string"/>
                    <element name="emailAddress" type="string"/>
                    <element name="hireDate" type="date"/>
                    <element name="id" type="int"/>
                </sequence>
            </complexType>
            <complexType name="createEmployeeResponse">
                <sequence>
                    <element name="success" type="boolean"/>
                </sequence>
            </complexType>
            <complexType name="getEmployeeRequest">
                <sequence>
                    <element name="successful" type="boolean"/>
                    <element name="firstName" type="string"/>
                    <element name="surName" type="string"/>
                    <element name="emailAddress" type="string"/>
                    <element name="hireDate" type="date"/>
                    <element name="id" type="long"/>
                </sequence>
            </complexType>
            <complexType name="getEmployeeResponse">
                <sequence>
                    <element name="id" type="long"/>
                </sequence>
            </complexType>
        </schema>
    </types>
    <message name="createEmployeeRequest">
        <part name="parameters" element="tns:createEmployeeRequest"/>
    </message>
    <message name="createEmployeeResponse">
        <part name="parameters" element="tns:createEmployeeResponse"/>
    </message>
    <message name="getEmployeeRequest">
        <part name="parameters" element="tns:getEmployeeRequest"/>
    </message>
    <message name="getEmployeeResponse">
        <part name="parameters" element="tns:getEmployeeResponse"/>
    </message>
    <portType name="EmployeeService">
        <operation name="createEmployee">
            <input message="tns:createEmployeeRequest"></input>
            <output message="tns:createEmployeeResponse"></output>
        </operation>
        <operation name="getEmployee">
            <input message="tns:getEmployeeRequest"></input>
            <output message="tns:getEmployeeResponse"></output>
        </operation>
    </portType>
    <binding name="EmployeeServicePortBinding" type="tns:EmployeeService">
        <soap:binding style="document" 
              transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="createEmployee">
            <soap:operation soapAction=""/>
            <input>
                <soap:body use="literal"></soap:body>
            </input>
            <output>
                <soap:body use="literal"></soap:body>
            </output>
        </operation>
        <operation name="getEmployee">
            <soap:operation soapAction=""/>
            <input>
                <soap:body use="literal"></soap:body>
            </input>
            <output>
                <soap:body use="literal"></soap:body>
            </output>
        </operation>
    </binding>
    <service name="CreateEmployeeService">
        <documentation>Create Employee Service</documentation>
        <port name="EmployeeServicePort" 
              binding="tns:EmployeeServicePortBinding">
            <soap:address location="http://localhost:8080/soap/"/>
        </port>
    </service>
</definitions>

Force.com WSC의 GAE 버전을 사용하여 WSDL에서부터 jar 파일을 빌드하고(SOAP 메시지를 전송하고 수신하기 위한 필수 클래스 사용) 다음 명령을 사용하여 lib 폴더로 둘 다 추가한다.

java -classpath wsc-gae-16_0.jar com.sforce.ws.tools.wsdlc <WSDL input file> 
              <JAR output file>

작성된 출력 jar 및 wsc-gae.jar 파일을 둘 다 프로젝트의 lib 폴더에 추가한다.

Spring 프레임워크 jar 파일을 다운로드한다(링크는 참고자료 참조).

다음 jar 파일을 프로젝트의 lib 폴더로 추가한다.

  • org.springframework.aop.jar
  • org.springframework.asm.jar
  • org.springframework.aspects.jar
  • org.springframework.beans.jar
  • org.springframework.context.jar
  • org.springframework.context.support.jar
  • org.springframework.core.jar
  • org.springframework.expression.jar
  • org.springframework.instrument.jar
  • org.springframework.instrument.tomcat.jar
  • org.springframework.jdbc.jar
  • org.springframework.jms.jar
  • org.springframework.orm.jar
  • org.springframework.oxm.jar
  • org.springframework.test.jar
  • org.springframework.transaction.jar
  • org.springframework.web.jar
  • org.springframework.web.portlet.jar
  • org.springframework.web.servlet.jar
  • org.springframework.web.struts.jar
  • spring-oxm.jar
  • spring-oxm-tiger.jar
  • spring-ws-core.jar
  • spring-ws-core-tiger.jar
  • spring-ws-security.jar
  • spring-ws-support.jar
  • spring-xml.jar

이 기사를 조사하는 동안 필자는 SpringSource 3.0의 최종 릴리스를 사용했고 이후 버전은 테스트하지 않았다.

GAE는 Spring AxiomSoapMessageFactory 클래스의 전제조건인 로컬 파일 시스템으로의 쓰기를 허용하지 않는다. 다행히 간단한 해결책으로는 목록 12와 같이 클래스를 확장하고 아무 것도 하지 않는 afterPropertiesSet() 메소드를 오버라이드하는 것이다. 이렇게 하면 쓰기 허용에 대한 Spring 검사를 충족할 것이다.


목록 12. 사용자 정의 AxiomSoapMessageFactory 클래스

package com.xmlimport.service.soap;
import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory;
public class XMLImportMessageFactory extends AxiomSoapMessageFactory {

    public void afterPropertiesSet() throws Exception {
    // Do nothing. 
    // This is because the method checks for write access, which GAE does not allow
	}
}

Force.com WSC를 사용하기 위해 목록 13과 같이 사용자 정의 마샬러 및 언마샬러가 필요하다.


목록 13. 사용자 정의 Marshaller 클래스

package com.xmlimport.service.soap;
public class EmployeeServiceMarshaller extends TransformerObjectSupport 
        implements Marshaller, Unmarshaller {

        public final void marshal(Object graph, Result result) 
            throws XmlMappingException, IOException {

        try {
                    XMLizable xmlObject = (XMLizable)graph;
                    ByteArrayOutputStream xmlBuffer = new ByteArrayOutputStream();

                    // Assumes all services under same name space at present.
                    QName qName = new QName("http://xmlimport.appspot.com/",
                    StringUtils.
                    uncapitalize(xmlObject.getClass().getSimpleName()));

            // Use the Force.com WSC API to generate the XML from the given object.
            XmlOutputStream xout = new XmlOutputStream(xmlBuffer, true);
            xout.startDocument();
            xmlObject.write(qName, xout, new TypeMapper());
            xout.endDocument();
            xout.close();

            // Setup an XMLStreamReader to parse the generated XML buffer.
            XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
          XMLStreamReader xmlStreamReader = 
        xmlInputFactory.createXMLStreamReader(new StringReader(xmlBuffer.toString()));

    	// Copy the contents of the XMLStreamReader into the StaxResult.
    	XMLStreamWriter xmlStreamWriter = 
                                    ((StaxResult)result).getXMLStreamWriter();
    	org.apache.axiom.om.util.CopyUtils.reader2writer(xmlStreamReader,
                    xmlStreamWriter);
        }
        catch (XMLStreamException xse) {

                throw new MarshallingFailureException(
                        "Failed to copy generated object XML into StaxResult.", xse);
	    
        }
}

    public final Object unmarshal(Source source) throws XmlMappingException, IOException {
        XMLizable xmlObject = null;

        if (source != null) {
            try {
             XMLStreamReader xmlStreamReader = ((StaxSource)source).getXMLStreamReader();
                xmlStreamReader.next();

                // Use localName of top element to work out the name of the java class.
                StringBuilder className = new StringBuilder("com.sforce.soap.");
                className.append(StringUtils.capitalize(xmlStreamReader.getLocalName()));
       
                // Create an instance of this class to bind to the XML.
                xmlObject = (XMLizable)Class.forName(className.toString()).newInstance();
       
                // Transform the StaxSource into a StreamResult 
                //so that we get the XML String.
                StringWriter out = new StringWriter();       
                transform(source, new StreamResult(out));

                // Use the XML String with the Force.com WSC 
                //to populate the properties of the object. 
                XmlInputStream xin = new XmlInputStream();
                xin.
                        setInput(new ByteArrayInputStream(out.toString().getBytes()),
                                    "UTF-8");
                xmlObject.load(xin, new TypeMapper());
                } catch (ClassNotFoundException cnfe) {
                        throw new UnmarshallingFailureException(
        "A Force.com WSC generated class was not found that matches the XML message.",
                                    cnfe);
                } catch (IllegalAccessException iae) {
                        throw new UnmarshallingFailureException(
                "Failed to instantiate instance of the Force.com WSC generated class.",
                                    iae);
                } catch (InstantiationException ie) {
                        throw new UnmarshallingFailureException(
                "Failed to instantiate instance of the Force.com WSC generated class.");
                } catch (ConnectionException ce) {
                        throw new UnmarshallingFailureException(
                "Failed to parse XML String using Force.com pull parser.", ce);
                } catch (PullParserException ppe) {
                        throw new UnmarshallingFailureException(
                "Failed to parse XML String using Force.com pull parser.", ppe);
                } catch (TransformerException te) {
                        throw new UnmarshallingFailureException(
                "Failed to transform StaxSource to StreamResult.", te);
                } catch (XMLStreamException xse) {
                        throw new UnmarshallingFailureException(
                "Failed to parse top level element in message payload.", xse);
                }
        }

        return xmlObject;
    }

    /**
     * Assumes that all marshalling and unmarshalling is handled by this implementation.
     */
    @SuppressWarnings("unchecked")
        public boolean supports(Class clazz) {
        return true;
    }
}

마샬링 함수는 웹 서비스에서부터 javax.xml.transform.Result로 수신된 결과를 변환하는 데 사용된다. 마샬링 함수에서 qName은 WSDL에서의 targetNameSpace와 동일한 것임을 참고하자.

언마샬링 코드는 역으로 사용된다—웹 서비스로 사용된 Java 오브젝트로 HTTP를 넘어 수신된 javax.xml.transform.Source를 변환한다. 작성되는 클래스의 이름은 source 매개변수에서 저장된 XML에서부터 추출된다. WSC 코드인 com.sforce.soap를 사용하여 작성된 클래스 패키지의 기본값 이름은 이를 앞에 삽입하여 완전한 이름을 작성한다(예: com.sforce.soap.CreateEmployeeRequest). 반영(Reflect)이 이 클래스의 인스턴스를 작성하는 데 사용되며, 마지막으로 속성은 소스에서 xml의 나머지를 사용하여 설정된다.

supports 함수는 서비스가 모든 클래스를 처리하는 것을 지정한다.

Spring 프레임워크 구성 파일인 ws-servlet.xml을 목록 14와 같이 작성하고 이를 GAE 프로젝트의 war 디렉토리에서 WEB-INF 폴더에 넣는다. 이 파일은 서비스에 대한 클래스 이름, 클래스가 사용하는 마샬러와 언마샬러, 서비스에 대한 WSDL의 위치 및 위에서 작성한 사용자 정의 MessageFactory가 들어있다(목록 12 참조). WSDL 파일이 서비스 bean의 constructor-arg 요소에서 정의한 것(employeeService)과 동일한 폴더에 있어야 한다. 이 파일은 web.xml 파일로 프로젝트의 WEB-INF 폴더에 위치한다.


목록 14. ws-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"
            xsi:schemaLocation="http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean class="org.springframework.ws.server.endpoint.mapping.SimpleMethodEndpointMapping">
        <property name="endpoints" ref="createEmployeeService"></property>
    </bean>

<bean id="createEmployeeService" class="com.xmlimport.service.soap.CreateEmployeeService">
        <property name="marshaller" ref="createEmployeeServiceMarshaller" />
        <property name="unmarshaller" ref="createEmployeeServiceUnmarshaller" />
    </bean>

<bean id="createEmployeeServiceMarshaller" 
class="com.xmlimport.service.soap.EmployeeServiceMarshaller"></bean>

<bean id="createEmployeeServiceUnmarshaller" 
class="com.xmlimport.service.soap.EmployeeServiceMarshaller"></bean>

<bean id="employeeService" 
    class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
        <constructor-arg value="/WEB-INF/wsdl/employeeService.wsdl"/>
    </bean>

<bean id="messageFactory" class="com.xmlimport.service.soap.XMLImportMessageFactory">
        <property name="payloadCaching" value="false"/>
        <property name="attachmentCaching" value="false"/>
    </bean>

</beans>

목록 15와 같이 서블릿 정의를 web.xml로 추가하여, /soap/* URI로의 모든 요청은 Spring 프레임워크로 전달되고, 거기에서부터 프레임워크 구성에서 정의한 서비스 클래스로도 전달된다. 서블릿 이름은 ws이고, 관례 상 프레임워크 구성 파일은 <servlet-name>-servlet.xml이며, 이전 단계에서 이름은 ws-servlet.xml이다.


목록 15. SOAP 서블릿 정의 web.xml

<servlet>
    <servlet-name>ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet
                                                                   </servlet-class>
    <init-param>
        <param-name>transformWsdlLocations</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>ws</servlet-name>
    <url-pattern>/soap/*</url-pattern>
</servlet-mapping>

</web-app>

마지막으로 웹 서비스 클래스 자체를 쓰자. 각 요청이 언마샬링 함수로 수신되면서 해당 오브젝트를 작성하여 서비스에서 연관 함수로 전달된다. handlecreateEmployeeRequest 함수에서 필수 정보를 추출하여 CreateEmployeeRequest 오브젝트에서부터 Employee 오브젝트를 작성한 다음에 새 오브젝트를 지속적으로 만든다. 함수의 이름은 handleCreateEmployeeRequest(Java 언어에서 관례와 같은 대문자 "C" 사용)이 아니라 handlecreateEmployeeRequest(소문자 "c" 사용)임을 참고하자.

서비스 클래스인CreateEmployeeService를 쓰자(목록 16 참조). 각 요청이 수신되면서 Employee 오브젝트를 작성하는 필수 정보가 CreateEmployeeRequest 오브젝트에서부터 추출된 다음에 새 오브젝트가 지속적이 된다. 함수의 이름은 handlecreateEmployeeRequest("create"에 대해 소문자 "c" 사용)임을 참고하자.


목록 16. CreateEmployeeService.java

package com.xmlimport.service.soap;

import org.springframework.ws.server.endpoint.adapter.MarshallingMethodEndpointAdapter;

import com.sforce.soap.CreateEmployeeRequest;
import com.sforce.soap.CreateEmployeeResponse;
import com.xmlimport.employee.Employee;
import javax.jdo.PersistenceManager;
import com.xmlimport.XMLImportPersistenceManagerFactory;

public class CreateEmployeeService extends MarshallingMethodEndpointAdapter {

    Logger log = Logger.getLogger(CreateEmployeeService.class.getName());

    public CreateEmployeeResponse handlecreateEmployeeRequest(CreateEmployeeRequest 
                                                                 createEmployeeRequest) {

        Employee employee = new Employee();
        employee.setFirstName(createEmployeeRequest.getFirstName());
        employee.setSurName(createEmployeeRequest.getSurName());
        employee.setEmailAddress(createEmployeeRequest.getEmailAddress());
        employee.setId(createEmployeeRequest.getId());
        employee.setHireDate(createEmployeeRequest.getHireDate().getTime());
PersistenceManager pm = XMLImportPersistenceManagerFactory.get().getPersistenceManager();
        try {

            pm.makePersistent(employee);			

        }
        finally {

            pm.close();

        }
        CreateEmployeeResponse createEmployeeResponse = new CreateEmployeeResponse();
        createEmployeeResponse.setSuccess(true);

        return createEmployeeResponse;

    }

}

프로젝트를 appspot.com으로 컴파일하고 배치한다. 브라우저에서 애플리케이션 <application name>.appspot.com/soap/wsdl/employeeServices.wsdl에 대해 WSDL에 액세스할 수 있는지 확인한다. 필자는 soapUI 도구(다운로드 링크는 참고자료 참조)를 사용하여 SOAP 및 REST 서비스를 테스트한다. 이전과 동일한 URL에서부터 soapUI를 사용하여 WSDL 파일을 열면, soapUI는 firstName, surName 및 기타 등등에 대한 공백 요소를 통해 SOAP 요청 메시지를 자동으로 작성한다. 이에 대한 일부 값을 입력하고 초록색 화살표를 클릭하여 요청을 제출한다. 응답은 success 요소를 true로 설정해야 한다. GAE 애플리케이션 대시보드에서부터 Data Viewer를 사용하여 오브젝트가 작성되었는지 확인한다.


XML 문서에서부터 대용량 업로드

soapUI는 하나의 항목을 작성하는 데 충분한 반면에 employees XML 파일에서 각 항목을 작성하려면 파일을 구문 분석하고 각 항목으로 서비스를 호출하는 클라이언트 프로그램이 필요하다. 이 클라이언트는 웹 서비스의 작성 도중에 작성된 동일한 jar 파일을 사용하지만, GAE별 버전이 아니라 WSC jar 파일의 클라이언트 버전을 사용한다.

목록 17은 EmployeeHandler의 새 버전이다.


목록 17. EmployeeHandler.java

package com.xmlimport.client;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.sforce.soap.Connector;
import com.sforce.soap.SoapConnection;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.ConnectorConfig;

public class EmployeeHandler extends DefaultHandler {

    private static final Logger log=Logger.getLogger(EmployeeHandler.class.
            getName());
    private static final SimpleDateFormat hireDateFormat = 
                    new SimpleDateFormat("yyyy-MM-dd");

    private Stack<Employee> employeeStack;
        private String characters;

        public EmployeeHandler() {

                    SAXParserFactory factory = SAXParserFactory.newInstance();
                    try {

                        SAXParser saxParser = factory.newSAXParser();
                        saxParser.parse(new InputSource("./employees.xml"), this);

                    } catch (Throwable t) {

                        t.printStackTrace();

                    }

        }

    public void startDocument() 
                    throws SAXException {	

                    employeeStack = new Stack<Employee>();
		
    }

        public void startElement(String namespaceURI, 
                                                String localName,
                                                String qualifiedName,
                                                Attributes attributes) 
                    throws SAXException {

                    if (qualifiedName.equals("employee")) {

                        Employee employee = new Employee();
                        employee.setEmployeeId(new Integer(attributes.getValue("id")));
                        employeeStack.push(employee);

                    }

        }

        public void endElement(String namespaceURI, 
                                                String simpleName,
                                                String qualifiedName) 
                    throws SAXException {

                    if (!employeeStack.isEmpty()) {

                        if (qualifiedName.equals("employee")) {

                                sentEmployeeSOAPMessage(employeeStack.pop());

                        } 
                        else if (qualifiedName.equals("firstName")) {

                                Employee employee = employeeStack.pop();
                                employee.setFirstName(characters);
                                employeeStack.push(employee);

                        } 
                        else if (qualifiedName.equals("surName")) {

                                Employee employee = employeeStack.pop();
                                employee.setSurName(characters);
                                employeeStack.push(employee);

                        } 
                        else if (qualifiedName.equals("emailAddress")) {

                                Employee employee = employeeStack.pop();
                                employee.setEmailAddress(characters);
                                employeeStack.push(employee);

                        } 
                        else if (qualifiedName.equals("hireDate")) {

                                Employee employee = employeeStack.pop();
                                try {

                                        employee.setHireDate(
                                                 hireDateFormat.parse(characters));

                                } 
                                catch (ParseException e) {

                                        log.log(Level.FINE, 
                                                 "Could not parse date {0}", characters);
                                }
                                employeeStack.push(employee);

                        }

                    }

    }

    public void characters(char buf[], int offset, int len) 
                    throws SAXException {

                    characters = new String(buf, offset, len);

    }

    private void sentEmployeeSOAPMessage(Employee employee) {

                    ConnectorConfig config = new ConnectorConfig();
                    config.setServiceEndpoint("http://xmlimport.appspot.com/soap/");
                    Calendar hireDate = Calendar.getInstance();
                    hireDate.setTime(employee.getHireDate());
                    System.out.println("Creating employee " + 
                                employee.getFirstName() + " " + 
                                employee.getSurName());
                    try {
                        SoapConnection soapConnection = Connector.newConnection(config);
                        boolean success = soapConnection.createEmployee(
                                        employee.getFirstName(), 
                                        employee.getSurName(), 
                                        employee.getEmailAddress(),
                                        hireDate, 
                                        employee.getEmployeeId());
                        System.out.println(success?"Success":"Failure");
                    } catch (ConnectionException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

    }


public static void main(String[] args) {

                    new EmployeeHandler();

    }

}

각 항목을 구문 분석하면서 작성된 현재 직원의 이름과, 그 작성이 성공 또는 실패 여부가 나와있는 표준 출력으로 메시지를 인쇄한다.

이 솔루션은 이전 솔루션과 동일한 제한시간 제한이 없지만, 클라이언트 XML 파일이 매우 크다면 각 오브젝트를 작성하고 지속하는 것은 appspot.com 서버에서 CPU 시간의 일별 할당량을 초과할 수 있다.


결론

이 기사에서는 XML 문서에서 데이터로부터 오브젝트를 작성하고 GAE 개발자가 사용 가능한 DataStore에서 이를 지속하는 다양한 메소드를 시연하였다. 마지막의 SOAP 기반 클라이언트와 서버 메소드를 통해 대용량 XML 데이터를 업로드하는 메소드는 이제 Java 개발자가 사용할 수 있다.



다운로드 하십시오

설명이름크기다운로드 방식
Application and service source codeXMLImport-GAEProject.zip44KBHTTP
SOAP client source codeXMLImportSOAPClient.zip24KBHTTP

다운로드 방식에 대한 정보


참고자료

교육

  • Eclipse IDE: Eclipse 개발 환경에 대한 홈페이지에 액세스하자.

  • Using the DataStore with JDO 안내서: 안내서를 참조하여 확장 가능한 웹 애플리케이션에서의 데이터 저장에 대해 더 자세히 알아보자.

  • Google App Engine: Google apps를 가동하는 동일한 시스템에서 웹 애플리케이션을 빌드하고 호스트하는 방법에 대해 더 자세히 읽어보자.

  • GAE Datastore API guide: 쿼리 엔진과 원자적 트랜잭션으로 스키마없는 오브젝트 데이터 저장소에 대한 문서를 읽어보자.

  • Bulk Data Upload for Python: 대안으로 Java가 아닌 대용량 로더 도구가 애플리케이션으로 또는 애플리케이션에서부터 데이터를 업로드하고 다운로드할 수 있는지에 대해 알아보자.

  • W3C XML 정의: 웹과 어느 곳에서나 대규모의 전자적 게시와 데이터의 광범위한 교환을 처리하는 이 간단하고 유연한 텍스트 형식에 대해 알아보자.

  • Exposing SOAP Service on GAE: Spring을 사용하여 GAE에서 SOAP 웹 서비스를 구현하는 방법에 대한 세 가지 부분으로 된 이 기사에 대해 알아보자.

  • SAX Project: XML 구문 분석기가 정보를 XML 문서에서부터 소프트웨어 애플리케이션으로 효율적으로 전달할 수 있는 방법에 대해 알아보자.

  • Simple을 사용하여 XML 직렬화하기: Java 오브젝트를 XML로 실제로 매우 쉽게 변환하는 방법(Brian Carey저, developerWorks, 2009년 11월): Simple을 사용하여 XML 문서를 POJO로 변환하는 방법에 대해 이해하자.

  • W3C SOAP 스펙: SOAP 및 표현적 메시지 구조와 메시지 교환 패턴을 사용하는 방법에 대해 알아보자.

  • Issue 5: wsc-gae-16.0.jar throws null pointer exception if no parent directory specified for jar output file: WSDL 파일에서부터 웹 서비스를 작성하기 위한 프로시저를 통해 알려진 문제에 대해 읽어보자.

  • W3C WSDL 정의: 문서 지향 또는 프로시저 지향 정보 중 하나가 들어 있는 메시지에 대해 조작하는 엔드포인트의 한 세트로 네트워크 서비스를 설명하는 하나의 XML 형식인 WSDL에 대해 더 자세히 알아보자.

  • developerWorks의 XML 영역: XML 영역에서 기술 향상에 도움이 되는 참고자료를 얻을 수 있다.

  • My developerWorks: developerWorks와 관련된 경험을 개인화할 수 있다.

  • IBM XML 인증: XML 및 관련 기술에 대한 IBM 인증 개발자가 되는 방법을 찾아볼 수 있다.

  • XML 기술 자료: developerWorks XML 영역에서 다양한 기술 관련 기사와 팁, 튜토리얼, 표준 및 IBM Redbook을 볼 수 있다. 또한 더 많은 XML 팁을 읽어본다.

  • developerWorks 기술 행사 및 웹 캐스트: 이러한 세션에 참가하여 최신 기술에 대한 정보를 얻을 수 있다.

  • Twitter의 developerWorks 페이지: 오늘 가입하여 developerWorks 트윗을 팔로우하자.

  • developerWorks podcasts: 소프트웨어 개발자의 흥미로운 인터뷰와 토론을 확인할 수 있다.

제품 및 기술 얻기

  • GAE용 JDK(Eclipse 플러그인 포함): 표준 Java 기술을 사용하여 웹 애플리케이션을 빌드하기 위해 다운로드하여 시작하고 이를 Google의 확장 가능한 인프라에서 실행하자.

  • Spring Framework 홈페이지: 이 플랫폼을 다운로드하여 엔터프라이즈 Java 애플리케이션을 빌드하고 실행하자.

  • GAE용 Force.com Web Service Connector: Google에서부터 스트리밍 구문 분석기를 사용하여 구현된 고성능 웹 서비스 클라이언트 스택을 다운로드하자.

  • wsc-gae-16_0.jar: Force.com에서부터 Force.com Web Service Connector(WSC)의 GAE 버전을 다운로드하자.

  • soapUI 도구: 주로 웹 서비스 테스팅에 사용되는 이 오픈 소스 Functional Testing Tool을 다운로드하자.

  • wsc-17_0.jar: WSC jar 파일의 독립형 버전을 다운로드하자.

  • IBM 제품 평가판을 다운로드하거나 IBM SOA Sandbox의 온라인 시험판을 살펴보고 DB2®, Lotus®, Rational®, Tivoli® 및 WebSphere®의 애플리케이션 개발 도구 및 미들웨어 제품을 사용해 볼 수 있다.

토론

필자소개

Photo of Joseph McCarthy

Joseph McCarthy is a Java Developer in the Dublin Software Lab. He joined IBM in July 2002 after graduating from the University of Limerick with a Bsc in Computer Systems, and a Graduate Diploma in Computer Engineering.

잘못된 도움말 신고

부정사용 신고

감사합니다. 이 항목은 운영자가 관심을 표시했습니다.


잘못된 도움말 신고

부정사용 신고

제출실패 신고. 나중에 다시 실행해주세요.


디벨로퍼웍스 로그인


IBM ID가 필요하세요?
IBM ID를 잊으셨습니까?


비밀번호를 잊으셨습니까?
비밀번호 변경

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

화면상에 보여지는 닉네임을 정하세요.

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

3개의 &이나 대쉬를 포함해주시고 31글자내로 제한해주세요.


developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


아티클 순위

의견

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=20
Zone=XML
ArticleID=605131
ArticleTitle=Google App Engine으로 XML 데이터 가져오기
publish-date=09072010
author1-email=josemcca@ie.ibm.com
author1-email-cc=

태그

Help
검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오.

태그를 더 많이 보거나 적게 보기 위해 슬라이더 막대를 사용하십시오.

인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다.

내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.

검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오. 인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다. 내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.