달력

4

« 2024/4 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

이 글은 하얀말님의 2008년 9월 8일의 미투데이 내용입니다.

:
Posted by 하얀 말
/*
 * $HeadURL$
 * $Revision$
 * $Date$
 * ====================================================================
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

import java.io.File;
import java.io.InputStream;
import java.io.StringWriter;

import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.FileRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.w3c.dom.Document;

import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;

/**
 *
 * This is a sample application that demonstrates how to use the Jakarta
 * HttpClient API.
 *
 * This application sends an XML document to a remote web server using HTTP POST
 *
 * @author Sean C. Sullivan
 * @author Ortwin Glueck
 * @author Oleg Kalnichevski
 * @author Paul King
 */
public class PostSOAP {

  /**
   *
   * Usage: java PostSOAP http://mywebserver:80/ SOAPAction c:\foo.xml
   *
   * @param args
   *            command line arguments Argument 0 is a URL to a web server
   *            Argument 1 is the SOAP Action Argument 2 is a local filename
   *
   */
  public static void main(String[] args) throws Exception {
    boolean isBeautify = false;

    if (args.length != 3 && args.length != 2) {
      System.out.print("Usage: java -classpath <classpath> [-Dorg.apache.");
      System.out.print("commons.logging.simplelog.defaultlog=<loglevel>] Post");
      System.out.print("SOAP <url> <filename> [<soapaction>] [-beautify]\n");
      System.out.print("<classpath> - must contain the commons-httpclient.jar");
      System.out.print(" and commons-logging.jar\n");
      System.out.print("<loglevel> - one of error, warn, info, debug, trace\n");
      System.out.print("<url> - the URL to post the file to\n");
      System.out.print("<filename> - file to post to the URL\n");
      System.out.print("<soapaction> - the SOAP action header value");
      System.out.print("(optional)\n");
      System.out.println("-beautify - show pretty result(optional)\n\n");
      System.exit(1);
    }
    // Get target URL
    String strURL = args[0];
    String strSoapAction = "";

    if (4 == args.length) {
      // Get SOAP action
      strSoapAction = args[2];

      if ("-beautify".equals(args[3])) {
        isBeautify = true;
      }
    }

    if (3 == args.length) {
      if ("-beautify".equals(args[2])) {
        isBeautify = true;
      } else {
        strSoapAction = args[2];
      }
    }


    // Get file to be posted
    String strXMLFilename = args[1];
    File input = new File(strXMLFilename);
    // Prepare HTTP post
    PostMethod post = new PostMethod(strURL);
    // Request content will be retrieved directly
    // from the input stream
    RequestEntity entity = new FileRequestEntity(input,
        "text/xml; charset=ISO-8859-1");
    post.setRequestEntity(entity);
    // consult documentation for your web service
    post.setRequestHeader("SOAPAction", strSoapAction);
    // Get HTTP client
    HttpClient httpclient = new HttpClient();
    // Execute request
    try {
      int result = httpclient.executeMethod(post);
      // Display status code
      System.out.println("Response status code: " + result);
      // Display response
      System.out.println("Response body: ");

      if (isBeautify) {
        System.out.println(PostSOAP.beautify(post
            .getResponseBodyAsStream()));
      } else {
        System.out.println(post.getResponseBodyAsString());
      }
    } finally {
      // Release current connection to the connection pool once you are done
      post.releaseConnection();
    }
  }

  public static String beautify(InputStream response) {
    StringWriter sFormattedXML = new StringWriter();

    try {
      Document oDocument = DocumentBuilderFactory.newInstance()
          .newDocumentBuilder().parse(response);
      OutputFormat format = new OutputFormat(oDocument, "UTF-8", true);
      format.setIndent(4);
      format.setIndenting(true);
      format.setPreserveSpace(false);

      XMLSerializer serial = new XMLSerializer(sFormattedXML, format);
      serial.asDOMSerializer();
      serial.serialize(oDocument.getDocumentElement());
    } catch (Exception e) {
      e.printStackTrace();
    }

    return sFormattedXML.toString();
  }
}

Apache HTTP Client의 예제 중 하나인 PostSOAP에 XML을 예쁘게 보여주는 beautify(InputStream) method를 추가했습니다. 참고로

import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;

이 두 import문에서의 OutputFormat, XMLSerializer의 package 이름은 IBM JDK 1.5(J9 VM이라고도 하죠)에 들어있는 Xerces의 OutputFormat이나 XMLSerializer의 package 이름을 따른 것입니다. 흔히들 많이 쓰는 Sun JDK의 Xerces는 package 구조를 Sun이 손을 댄 통에 package 이름이 다릅니다. 따라서 Sun JDK에서는 위 import문을 아래와 같이 바꾸세요.
import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

:
Posted by 하얀 말
2008. 7. 21. 11:06

SAAJ Computing에 관한 독백2008. 7. 21. 11:06


SAAJ는 SOAP with Attachments API for Java의 약자로, 말 그대로 SOAP with Attachment를 Java로 다루기 위한 API 모임입니다. 물론 JAXP나 HTTP로 생짜로 짤 수도 있겠습니만, 아무래도 class library가 있다면 좀 편하겠지요. SOAP with Attachment(이하 SwA)는 Web Service의 표준 protocol인 SOAP이, 첨부 file등을 붙일 수 있도록 확장한 것입니다. 비슷한 것으로는 DIME이 있긴 한데 아무래도 SwA가 더 힘을 받는 것 같습니다. 아울러,


  • SwA가 첨부 파일을 지원하는 mechanism은 e-mail에 file을 첨부하기 위해 고안한 MIME을 이용합니다. 이 사항은 SAAJ coding을 보면서 자세히 살펴보죠.
  • SAAJ가 지원하는 SOAP의 규격은 1.1, 1.2 모두를 지원하며 default는 1.1입니다. 이 글에서 명시적인 SOAP 명세 version이 없으면 1.1입니다.
  • Java SE 5 이하에서 SAAJ를 쓰려면 별도의 jar file이 필요했으나 Java SE 6부터는 기본으로 들어 있습니다. 따라서 본 글은 SAAJ를 Java SE에 포함시킵니다.
  • SOAP 자체에 대한 이해가 필요하시면 SOAP Tutorial을 공부하세요. 간단합니다.

SAAJ에서의 SOAP message 얼개#



SwA Message 얼개는 위 그림과 같습니다. 하나의 SOAP message는 1개의 SOAP part와 0개 이상의 Attachment Part로 나뉘며, SOAP Part는 SOAP Body는 꼭 있어야 하고 SOAP Header는 있어도 되고 없어도 됩니다. Attachment Part는 MIME Header들과 그에 따른 Content(즉 첨부 file)이 있습니다. SAAJ에는 이들 SOAP message, header, body, Attachment에 해당하는 class들이 존재하며 SAAJ를 쓴다는 것은 이들 class의 instance를 얻어 조작하는 것을 뜻합니다.


참고로 Attachment Part가 없는 SwA message는 아래 그림과 같이 SOAP Message 그 자체입니다.


SOAPMessage#


본격적으로 시작하기에 앞서, SAAJ에 관한 class들은 대부분 javax.xml.soap이라는 package 안에 들어있으며, 이 글에서 package에 대한 별도의 명명이 없는 class는 바로 이 javax.xml.soap package에 들어있다고 보시면 됩니다.


먼저 SOAP Message에 해당하는 객체를 획득해 보죠. 이 객체를 얻으려면 먼저 이 객체의 factory class인 MessageFactory 객체를 생성해야 합니다. MessageFactory 객체는 MessageFactory의 static method인 newInstance()라는 method를 호출함으로써 얻습니다. 그리고 우리가 정말로 얻고자 하는 객체인 Message를 표현하는 class는 SOAPMessage라는 class인데 이 class에 대한 객체는 MessageFactory의 createMessage()를 호출하면 얻을 수 있습니다. 이러한 동작을 수행하는 code는 아래와 같습니다.


  1. MessageFactory mf = MessageFactory.newInstance();
  2. SOAPMessage msg = mf.createMessage();

만약 SOAP 1.2 spec을 따르는 SOAP Message를 만들고 싶다면,

  1. MessageFactory mf = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
    

라고 하면 됩니다.


SOAPPart / SOAPEnvelope#

위에서 언급했듯, SOAP message는 SOAP Part와 Attachment Part로 이루어지고 SOAP Part는 반드시 존재합니다. 이 단락에서는 SOAP Part를 살펴보겠습니다. SOAP Part를 나타내는 class는 SOAPPart입니다. 그리고 SOAPPart 객체는 SOAPMessage 객체의 getSOAPPart() method를 통해 얻습니다. code로는 아래와 같죠.


  1. SOAPPart sp = mf.getSOAPPart();

SOAP spec을 보시면 아시겠지만 SOAP은 SOAP Envelope이라는 구조를 가지고 이 SOAP Envelope이 SOAP Header, SOAP Body를 rkwlrh 있습니다. SAAJ에서는 이 SOAP Envelope을 나타내는 class가 있으며 SOAPEnvelope이라 합니다. 이 SOAPEnvelope 객체는 다음과 같이 얻을 수 있죠.


  1. SOAPEnvelope se = sp.getEnvelope();

SOAP Envelope에 Namespace, Attribute 추가하기#

이 SOAPEnvelope은 SOAP의 <SOAP-ENV:envelope/> element에 대응합니다. <SOAP-ENV:envelope/>이라는 element 이름을 보셔도 알 수 있듯, <SOAP-ENV:envelope/>은 SOAP-ENV라는 XML Namespace prefix가 걸려 있으며, 이 prefix에 대응하는 URI는 "http://schemas.xmlsoap.org/soap/envelope/" 입니다. 그런데 때때로 이 <SOAP-ENV:envelope/>에 또다른 Namespace나 attribute를 추가할 수도 있습니다. 이를 위해 SOAPEnvelope은 Namespace 추가용으로 addNamespaceDeclaration(String prefix, String uri), Attribute 추가용으로 addAttribute(Name attrName, String value), addAttribute(QName attrName, String value)라는 method를 가지고 있습니다. 이들에 대한 code 예시는 아래와 같습니다.


  1. /* schemaLocName은 xsi:schemaLocation 이라는 이름을 나타내는 Name, 또는 QName 객체 */
  2. se.addNamespaceDeclaration("eb", "http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd");
  3. se.addAttribute(schemaLocName, "http://schema.xmlsoap.org/soap/envelope http://www.oasis-open.org/committees/ebxml-msg/schema/envelope.xsd");

위 code는 <SOAP-Env:Envelope/>에 eb라는 Namespace와 xsi:schemaLocation이라는 attribute를 추가하는 일을 합니다. 그런데 addAttribute() method의 첫번째 인자로 들어가는 Name, QName은 무엇하는 객체일까요?


Name / QName#

element나 attribute를 만들기 위해서는 당연히 그 element나 attribute에 대한 이름이 필요합니다. 그런데 XML에서 element나 attribute에 대한 이름은 XML Namespace의 적용을 받습니다. 따라서 element나 attribute에 대한 이름은 단순히 이름을 나타내는 String으로는 부족하고 거기에 prefix, uri에 대한 정보를 함께 가지고 있어야 합니다. 이러한 이름 정보를 표현하기 위해 SAAJ에서는 Name이란 class를 제공하며 이 Name에 대한 객체는 아래 SOAPFactory라는 객체에서 얻을 수 있습니다. code로는 아래와 같습니다.


  1. Name schemaLocName = SOAPFactory.newInstance().createName("schemaLocation", "xsi", "http://www.w3.org/2001/XMLSchema-instance");

위 code는 xsi:schemaLocation이라는 이름을 생성합니다.


이러한 이름을 표현하는 방법에는 Name 대신 javax.xml.namespace.QName을 쓰는 방법도 있습니다. 위 code와 동일한 작용을 하는, QName을 쓰는 code는 아래와 같습니다.


  1. QName schemaLocName = new QName("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation", "xsi");


SOAPHeader / SOAPBody#

SOAP message에서 실제 data를 탑재하는 부분은 SOAP Header와 SOAP Body입니다. SAAJ에서는 이들을 SOAPHeader, SOAPBody class로 표현하며 이들에 대한 객체는 아래 코드와 같이 얻을 수 있습니다.


  1. SOAPHeader sh = se.getHeader();
  2. SOAPBody sb = se.getBody();

SOAPHeader나 SOAPBody는 각각 <SOAP-ENV:header/>, <SOAP-ENV:body/>에 대응하며 <SOAP-ENV:envelope/>과 마찬가지로 addNamespaceDeclaration(String prefix, String uri), addAttribute(Name attrName, String value), addAttribute(QName attrName, String value)을 써서 필요로 하는 Namespace나 attribute를 추가할 수 있습니다. 그리고 하위 element도 추가할 수 있죠.


SOAP Header / SOAP Body에 data 싣기#

SOAPHeader나 SOAPBody에는 필요한 XML element 등을 만들어서 필요한 data를 SOAP message에 적재할 수 있습니다.


SOAP Header에 data 싣기#

SOAPHeader 객체의 addChildElement(Name), addChildElement(QName)이라는 method가 SOAP Header에 하위 element를 추가하는 일을 수행하며, 추가된 하위 element를 가리키는 SOAPElement 객체를 반환합니다.


아래 code는 SOAP Header에 하위 element로 <eb:CPAId/>라는 element를 추가하고, 거기에 "banca.03.N01.test"라는 text 값을 추가하는 예시입니다.


  1. SOAPElement messageHeader = sh.addChildElement(new QName("http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd", "MessageHeader", "eb"));

위 code에 대응하는 XML 문서는 대략 다음과 같습니다.


  1. <SOAP-ENV:Header xml:eb="http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd">
  2.   <eb:MessageHeader/>
  3. </SOAP-ENV:Header>

이 SOAPElement 객체에도 addChildElement(QName name), addChildElement(Name name), addNamespaceDeclaration(String prefix, String uri), addAttribute(Name attrName, String value), addAttribute(QName attrName, String value)이 있으며, 필요한 하위 Element, Namespace나 Attribute를 추가할 수 있습니다. 또한 SOAPHeaderElement에는 setTextContent(String content)라는 method가 있는데 이는 element에 문자열로 된 내용을 추가합니다.


아래는 <eb:MessageHeader/>에 <eb:CPAId>banca.03.N01.test</eb:CPAId>라는 하위 element를 만드는 code입니다.


  1. messageHeader.addChildElement(new QName("http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd", "CPAId", "eb")).setTextContent("banca.03.N01.test");

SOAPBody에 data 싣기#

SOAPBody에도 addChildElement(Name), addChildElement(QName)가 있으며 이 method들이 반환하는 SOAPElement 객체에 Attribute나 하위 element, content를 추가할 수 있는 것은 SOAPHeader와 같습니다. 그런데 SOAPBody에는 SOAPHeader에 없는 독특한 기능이 있는데, 바로 addDocument(org.w3c.dom.Document doc), addFault() method들입니다.


addDocument(org.w3c.dom.Document doc)는 XML 문서 자체를 한꺼번에 SOAPBody에 적재하는 method입니다. 편리하겠지요?


그럼 addFault()는 뭐하는 것일까요? SOAP spec을 보시면 아시겠지만 어떤 처리를 하다 error가 발생하면 그 error에 대한 정보를 SOAP message에 실을 수 있습니다. 바로<SOAP-ENV:Body/> element 안에 들어가는 <SOAP-ENV:Fault/> element죠. 바로 addFault()가 이 element를 추가하는 역할을 수행합니다. addFault()는 SOAPFault 객체를 반환하며 SOAPFault는 SOAPElement의 자식 class입니다.


AttachmentPart#

AttachmentPart는 쉽게 말해 첨부 file 부분을 말합니다. SwA를 통해 첨부 file을 붙이는 작업은 보통 다음의 4가지 일로 나눌 수 있습니다.


  1. SOAPMessage 객체에서 AttachmentPart 객체 얻어오기
  2. Content 추가하기
  3. Content-ID 설정하기
  4. 가공한 AttachmentPart를 SOAPMessage에 추가하기

하나의 SOAPMessage에 적재할 수 있는 AttachmentPart는 이론적으로는 제한이 없으므로, 추가하고 싶은 만큼 위 작업을 반복하면 됩니다.


각 단계에 대한 code 예시는 아래와 같습니다.


  1. attachment = msg.createAttachmentPart();
  2. attachment.setRawContent(new FileInputStream(contentFiles[inx]),"application/octet-stream");
    attachment.setContentId("<payload-" + inx + ">");
    msg.addAttachmentPart(attachment);

위 예에서는 content를 추가하는 작업으로 setRawContent() method를 썼습니다만 setContent(Object content, String mimeType)을 쓸 수도 있습니다. 이 두 method의 차이는, 전자의 경우는 Stream 형태 그대로 적재합니다만, 후자는 Java 객체로 적재할 data를 받습니다. 만약 Java 객체가 두번째 인자로 주어진 MIME Type과 맞지 않다면 setContent()는 SOAPException을 발생시킵니다. 위 예에서 setContent()를 쓸 경우, "application/octet-stream"이라는 MIME Type은 대응하는 Java 객체가 없기 때문에 SOAPException이 납니다.


만든 SOAP Message를 주고 받기#

SOAPMessage는 다른 system에 보내라고 만든 것이죠? SwA는 만들어진 SOAPMessage를 보내는 것에 대한 API도 제공하고 있습니다. 바로 예시 code로 들어갑시다.


  1. SOAPConnectionFactory scf =  SOAPConnectionFactory.newInstance();
  2. SOAPConnection conn = scf.createConnection();
  3. SOAPMessage responseMsg = conn.call(msg, new URL("http://127.0.0.1:27120/ebXML/msh));

그럼 받는 쪽은 어떻게 할까요? 이것에 대한 해답도 역시 SOAPConnection 객체에 있습니다. SOAPConnection 객체에는 get(Object to)이라는 method가 있는데, 이 method는 to에서 SOAP Message를 보낼 때까지 기다리다가 to에서 SOAP Message를 보내면 이를 받아내죠. to에 실제로 올 수 있는 type은 String 객체나 java.net.URL 객체이며 request를 보내는 곳의 URL을 표현합니다.


SwA 실례#

실제 SOAP with Attachment message가 어떻게 생긴 것인지 이해를 돕기 위해 아래에 실제 내용을 첨부합니다. 이 예에서는 Attachement Part가 두 개입니다.


  1. ------=_Part_0_8089714.1216110540156
    Content-Type: text/xml; charset=utf-8
    Content-Id: <SOAP_Part>

    <?xml version="1.0" encoding="utf-8" ?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schema.xmlsoap.org/soap/envelope http://www.oasis-open.org/committees/ebxml-msg/schema/envelope.xsd"><SOAP-ENV:Header xmlns:eb="http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd" xsi:schemaLocation="http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd"><eb:MessageHeader SOAP-ENV:mustUnderstand="1" eb:id="MessageHeader" eb:version="2.0"><eb:From><eb:PartyId eb:type="BANK_CD">03</eb:PartyId><eb:Role>BANK</eb:Role></eb:From><eb:To><eb:PartyId eb:type="INSR_CD">N01</eb:PartyId><eb:Role>INSR</eb:Role></eb:To><eb:CPAId>banca.03.N01.test</eb:CPAId><eb:ConversationId>20010215-111213-28572</eb:ConversationId><eb:Service>urn:bancassurance</eb:Service><eb:Action>100001_Request</eb:Action><eb:MessageData><eb:MessageId>20010215-111212-287572</eb:MessageId><eb:Timestamp>2008-07-15T17:29:00Z</eb:Timestamp></eb:MessageData></eb:MessageHeader><eb:SyncReply SOAP-ENV:actor="http://schemas.xmlsoap.org/soap/actor/next" SOAP-ENV:mustUnderstand="1" eb:id="10056109" eb:version="2.0"/></SOAP-ENV:Header><SOAP-ENV:Body xmlns:eb="http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd" xsi:schemaLocation="http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd"><eb:Manifest eb:id="Manifest" eb:version="2.0"><eb:Reference xmlns:xlink="http://www.w3.org/1999/xlink" eb:id="Reference-0" xlink:href="cid:payload-0" xlink:type="simple"><eb:Description xml:lang="ko_KR">IBK-0</eb:Description></eb:Reference><eb:Reference xmlns:xlink="http://www.w3.org/1999/xlink" eb:id="Reference-1" xlink:href="cid:payload-1" xlink:type="simple"><eb:Description xml:lang="ko_KR">IBK-1</eb:Description></eb:Reference></eb:Manifest></SOAP-ENV:Body></SOAP-ENV:Envelope>
    ------=_Part_0_8089714.1216110540156
    Content-Type: application/octet-stream
    Content-ID: <payload-0>

                긍정적인 밥

                                   함민복

    시 한편에 삼만원이면
    너무 박하다 싶다가도
    쌀이 두 말인데 생각하면
    금방 마음이 따뜻한 밥이 되네

    시집 한권에 삼천 원이면
    든 공에 비해 헐하다 싶다가도
    국밥이 한 그릇인데
    내 시집이 국밥 한그릇만큼
    사람들 가슴을 따뜻하게 덥혀줄 수 있을까
    생각하면 아직 멀기만 하네

    시집이 한 권 팔리면
    내게 삼백원이 돌아온다
    박리다 싶다가도
    굵은 소금이 한됫박인데 생각하면
    푸른 바다처럼 상할 마음 하나 없네
    ------=_Part_0_8089714.1216110540156
    Content-Type: application/octet-stream
    Content-ID: <payload-1>

                   가을

                                  함민복

    당신 생각을 켜놓은 채 잠이 들었습니다

참고 문헌#

http://java.sun.com/javaee/5/docs/tutorial/doc/bnbhf.html

이 글은 스프링노트에서 작성되었습니다.

:
Posted by 하얀 말
괄목할만한 성공

 1995년 Java라는 언어가 생겼고, Web의 부흥과 맞물려 초창기에는 Java Applet, 현재는 Java Platform EE(Enterprise Edition)로 IT의 한 축을 그었다. Applet은 잠깐 반짝 하다 말았지만, 특히 Java Platform EE의 성공은 경이적이어서, 기업 전산 환경의 필수 middleware인 Web Application Server(이하 WAS)는 당연히 Java Platform EE 명세서에 준하는 것으로 생각하고(예전엔 Netscape Application Server라는 C/C++ 기반의 WAS도 있었지만 지금은 모두 Java Platform EE 명세를 따르는 WAS만 있는 것 같다), 따라서 기업/조직 전산 환경에서 Java는 가히 예전의 COBOL의 위치를 차지한다 해도 과언이 아니다. Embedded System 쪽으로는 특히 휴대전화 쪽으로 Java Platform ME(Micro Edition)이 널리 퍼졌고, Desktop이 조금 빈약하지만 Sun Microsystems가 Swing 다듬기에 여념이 없고, 특히 Eclipse의 SWT/JFace, RCP의 출현으로 말미암아, Platform 독립적인 Desktop Application 개발에도 Java가 세를 늘리고 있다(겪어보면 알겠지만 compile도 필요 없이 내가 짠 application이 Windows에서도 돌고 Linux에서도 돈다는 것은 정말 대단히 매력적인 일이다).

 분명 Java는 문법이, 특히 C++에 비하면, 깔끔하다. Java 이전에 OOP를 한다고 하면 C++로 coding한다는 이야기였던 때가 있었는데, C도 그렇지만 이 C++도 code 자체가 대단히 난잡하다. 특히 MFC로 짠 Windows program code는 지금 봐도 그 너저분함에 질린다(그래서 개인적으로 C#의 등장을 환영한다. 최소한 Windows program도 깔끔하게 짤 수 있으니까). 그런데 이 Java는 C++에 비하면 상당히 우아한 code가 나올 수 있다. 그러다보니 요새는 'Java로 된 자료 구조', 'Java로 된 algorithm', 'Java로 된 OOP' 등, 학계에서도 교육용 목적으로 Java를 많이 가르치며 S/W 공학 같은 것을 논할 때 예제 code도 Java로 짠 것을 심심찮게 볼 수 있다.

 또한, Java는 open source 개발자들에게도 매력적이었는지 Apache 재단, Eclipse 재단 등을 위시한 수많은 곳에서 Java 관련 제품을 개발하고 정보를 교류하고 있으며 이러한 산출물들은 Java 개발을 편리하게 하고, 새내기 개발자들이 참고할만한 교재 역할을 하며, Sun Microsystems 같은 Java 명세에 칼자루를 쥐고 있는 자들이 명세를 재정할 때 idea를 주고, 아울러 독단에 빠지지 않고 실질적으로 개발자들이 원하는 바를 잘 반영하게 하는 일종의 압력으로도 작용하여, 궁극적으로 Java의 세 확산을 일조하는 선순환 구조를 만들어냈다.

 즉 현재 Java는 Thomas Kuhn이 '과학 혁명의 구조'라는 책에서 말한 바로 그 Paradigm이다.

 Java의 비대화

 위에서 살펴본 바와 같이 Java는 정말 성공적인 가도를 달려왔다. 그러나 요즈음 일종의 이상 징후가 나오는 것도 사실이다.

2004년에 나온 Java Platform SE 5는 꽤 큰 문법 추가가 있었다. 하나는 Generics요, 또 하나는 Annotation이다. 그런데 이것이 좀 code를 난잡하게 만들었다. 예를 들면,

public EnumMap(EnumMap<K, ? extends V> m)

java.util.EnumMap이라는 class의 생성자이다. JDK 1.4 이전 code였으면 public EnumMap(EnumMap m)이라고 끝났을 code이다. 물론 Generics를 도입한 결과 Type 확인을 compile 시에 할 수 있게 되어 ClassCastException 꼴은 덜 보게 되어서 결과적으로 program 안정성 향상에 기여한 것은 사실이나 위 code를 보면 "먼 소린지~"다. Java 문법도 간결했던 문법이, 슬슬 사용자의 요구를 수용하다 보니 점점 난잡해지고 있는 한 예라 하겠다. API적인 측면은 더하다. 지금 Java Platform SE 6의 API는 JDK 1.2의 API에 비하면 훨씬 방대하다. 즉, 기술이 복잡해지고 있다. 그리고 이 복잡도를 사용자들이 수용하기 어려울 때, Java는 스스로의 무게에 비틀댈 것이다. 많은 기술들이 그랬었던 것처럼. Java도 entrophy의 증가라는 열역학 법칙에 자유롭지 못한 것은 아닐까.


sciprt 언어들의 도전

Java Platform이 Web 개발에는 거대하고 복잡하여 부적절하다는 말들이 여기저기서 나오고 있다. 이들 주장은 주로 PHP나 Python, Ruby와 같은 script 언어를 좋아하는 사람들에게서 많이 나오는데, 그동안 Java 진영은 그런 언어로 개발한 Web은 고가용성 등에 문제가 있어 mission-critical한 기업 업무 개발에는 부적절하다고 주장했다(재미있는 것은 이러한 주장은 이제는 한 물 간 것으로 취급하는 TP Monitor 진영, 더 한 물 간 것으로 여기는 Mainfram 진영이 Java를 폄하할 때 하는 주장이다). 확실히, script 언어들은 개발 생산성은 좋다. 그러나 Java 진영은 IBM, Sun Microsystems, BEA, Oracle 같은 IT 업계 거물이 버티고 돈은 받지만 동작을 보증함에 비해, 동적 type 언어들은 open source 진영에서 나온 것이다 보니, Do-it-yourself인 이런 open source 결과물을 자신의 업무 환경에 턱 적용했다가 문제 생기면 본인이 스스로 뒷감당을 해야 하므로, 혁신보다 보신이 중요한 (거대) 기업 조직 생리 상 이런 것들을 채용하기는 어렵다. 실제로 그러한 언어로 된 Web 개발은 기업 규모의 전산 환경에서는 underground 취급당했다. 그러나 RubyOnRails로 대표하는 쾌속 Web 개발이 개발자들의 이목을 끌고, Google이나 Wikipedia 같은, 무시무시한 traffic을 견디는 동적 type 언어들로 개발한 site들이 출현하다 보니, 과연 고가용성 때문에 거대함과 복잡함을 감내해야 하는지, 과연 그런 것들이 고가용성에 문제가 있는지에 대한 회의가 많이 늘었다. Java는 이러한 도전에 대한 응전으로 이번 Java Platform SE 6부터 JVM 상에서 script 언어를 돌릴 수 있는 길을 열어놓았다. 하지만 script 언어라는 것이 Java를 제치고 Paradigm의 영예를 순식간에 대체할 수 있는 가능성은 적지 않다.

 
Java 새 version의 비활성화

 요새 Microsoft는 Windows Vista를 열심히 팔고 있다. 그런데 이 Windows Vista 판매의 걸림돌은 Linux나 Solaris 같은 다른 운영체제가 아니라 바로 Windows XP, Windows 2000 같은 이전 version의 Windows이다. 그런데 이 현상이 Java에도 나타나고 있으니, Java Platform SE 6가 나오고 Java Platform EE 5가 나왔건만 아직도 현장에서는 JDK 1.4와 J2EE 1.4 기반의 WAS가 판을 치고 있다. 또한 WAS 제작사들도 Java Platform EE 5 인증 받는데 그닥 열의가 없다. TmaxSoft는 Java SE Platform EE 5 기반 WAS인 JEUS 6을 개발했지만, 현재 그 회사 site는 JEUS 5 위주로 소개하고 있다. BEA는 Java Platform EE 5 기반 WAS로 이제서야 WebLogic Server 10을 내놓았고, IBM 또한 J2EE 1.4 기반인 WebSphere Application Server 6.1에 머물러 있다. Open Source WAS의 대표 주자인 JBoss Application Server도 J2EE 1.4 명세에 일부 Java Platoform EE 5를 이루는 명세의 구현체 몇 개를 추가 탑재하는 식이다. 즉, 시장의 평가는 어떻게 보면 J2EE 1.4 기반 WAS로도 충분하다는 신호를 보내는 것이다. 또한 J2EE 1.4 명세는 JDK 1.4를 명시하므로 Java Platform SE 6은 고사하고 5도 각광을 덜 받는 것이다(이것은 위에서 언급한 문법 변화를 개발자들이 잘 수용하지 않기 때문이기도 하다).


Paradigm, 그리고 변곡점

 아까도 말했듯 Java는 확고한 Paradigm이다. 그러나 현재 이 Paradigm이 현실과의 괴리를 보이는 징후가 여기저기서 포착할 수 있다. 어쩌면 우리는 Intel의 전 CEO였던 Andrew Grove가 쓴 책, "편집광만이 살아남는다(Only Paranoids Survive)"에서 나온 변곡점을 지나는 중인지 모른다. 어떤 미분 가능한 함수에서 변곡점은 기울기가 급변하는 지점인데 재미있는 것은 그 변곡점에 처해서는 그 부분이 변곡점인지 인지하기가 쉽지는 않다. 그러나 함수의 독립변수를 계속 진행시키다 보면 그 기울기가 점점 급변한다.

 어차피 Java도 현상의 문제를 타파하기 위한 도구일 뿐이다. 가끔 어떤 computing platform에 대해 종교적인 믿음을 가진 신자들을 볼 수 있는데, 그러한 자세보다는 이러한 잘 드러나지 않는 변곡점을 기민하게 인식하고 새로운 Paradigm에 신속하게 자신을 적응시키 것이 현명하다 보여진다. 즉 적자생존(適子生存)이라는 말은 우리에게도 들어맞는 말이다. 그리고 또 하나, Thomas Kuhn이 과학 혁명의 구조에서 말한 것이 잘 들어맞는지 통사적(通史的)으로 살펴 보는 것도 재미있는 일일 것이다. 그리고 Java가 entrophy의 증대를 언제까지 성공적으로 제어할 지 지켜보는 것도 역시 흥미로울 것이다.

그나저나 Ruby랑 RubyOnRails를 공부해야겠다.

==================
예전 블로그에 2007.5.22에 쓴 글
:
Posted by 하얀 말