이전 포스트에서 사용한 애플스크립트.

이름은 InternalKeyboardEnabler.


그냥 쉘 스크립트 였는데, 매번 터미널 들어가기 귀찮아 애플스크립트로 작성하고 Application으로 저장했다.

안에 내용을 보면 내부 키보드 드라이버를 로드/언로드하는 것뿐이 없다.


실행하면 Enable 메뉴와 Disable 메뉴가 다음과 같이 나오는데,

Enable 메뉴는 노트북 키보드 사용가능하게, Disable은 키보드 사용을 불가능하게 한다.

Diable시 다음과 같이 경고(?)가 나오지만, 무시하고 OK를 선택하면 리붓 전까지는 맥북 키보드가 동작하지 않을것이다.


InternalKeyboardEnabler 다운로드

InternalKeyboardEnabler.zip


Posted by xHuro
,

A87등 bootloader가 올라가있는 atmega에서 isp를 통하지 않고 hex파일을 전송하는 놈인데...

(자세한 용어는 기억이 가물가물... 임베디드 안한지가 10년째...)


윈도우용만 있어서 맥에서 컴파일만 한것. 소스는 여기(http://www.obdev.at/products/vusb/download.html)에...


참고로 매버릭스에서 컴파일하고 실행해봤기 때문에 32bit 환경은 장담못하겠다.



Posted by xHuro
,
http://www.experimentsingameprogramming.com/chapter1.php

개인적으로 DirectX에 관한 기본을 어느 정도 익히고 난후에 보면 좋을 튜토리얼이다. 2D와 3D를 같이 사용하므로 기본 기술을 간단히 리뷰할 수 있을 꺼라 생각한다. 

나름대로 변경한 소스가 있기는 한데, 소스 정리후에 올려야겠다.
Posted by xHuro
,
http://www.voidspace.org.uk/python/mock.html
Posted by xHuro
,
#서론
TurboGears의 TGWebService는 현재 1.2.0까지 릴리즈된 상태이다. decorator를 이용하여 간단하게 WSDL을 자동으로 생성해주는 기능이 강력하지만(이전 포스트 참조), wsvalidate decorator를 사용할 때 Custom Objects에 대해서는 WSDL을 생성해주지 않는 문제가 발생한다. (편법으로 전혀 사용하지 않는 메소드를 하나 만들고, 그 메소드의 wsexpose에 Custom Objects 넣어주면 문제점을 피해갈 수 있으나, 정상적인 방법도 아닐 뿐더러 소스 코드도 깔끔해지지 않는다. 이전 포스트가 이런 편법이었다.)

이번 포스트에서는 Java와 연동에 앞서, 위의 WSValidate 문제점을 살펴보고, 커스터마이징해보도록 한다.

#필요한 Library
TGWebService(http://code.google.com/p/tgws/) : 사이트가 code google로 이사했다

#WsValidate의 문제점
TGWebService의 README 파일의 Custom Objects as Input 항목에서 설명하는데로 실행하면 WSDL에 반영되지 않는 문제점이 생긴다.

아래의 소스는 이전 포스트의 WebService 클래스를 조금 수정한 것이다. (wsexpose에는 아무것도 없고, wsvalidate에만 Custom Objects를 넣어줬다)

  @CodeGeass
from tgwebservices.controllers import WebServicesRoot, \
    WebServicesController, wsexpose, wsvalidate

from tgwebservices.runtime import typedproperty, unsigned
class Person(object):        
    name = ''
    id = ''

class CodeGeass(WebServicesRoot):
    @wsexpose()
    @wsvalidate(Person)
    def getPerson(self, value):
        person = value
        print person.id
        print person.name

이전 포스트와 동일하게 WSDL을 확인해보자.
http://localhost:8080/codegeass/soap/api.wsdl
   <wsdl:types>
     <xsd:schema elementFormDefault="qualified" targetNamespace="http://localhost:8080/codegeass/soap/types">
          <xsd:element name="getPerson">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="types:Person"/>
              </xsd:sequence>
            </xsd:complexType>

          </xsd:element>
          <xsd:element name="getPersonResponse">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="result" type="xsd:string"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
      </xsd:schema>

   </wsdl:types>
   <wsdl:message name="getPersonRequest" xmlns="http://localhost:8080/codegeass/soap/types">
       <wsdl:part name="parameters" element="types:getPerson"/>
   </wsdl:message>
   <wsdl:message name="getPersonResponse" xmlns="http://localhost:8080/codegeass/soap/types">
      <wsdl:part name="parameters" element="types:getPersonResponse"/>
   </wsdl:message>
... 후략
: 눈 씻고 찾아봐도, getPerson의 value type인 types:Data를 찾을 수 없다. 결국 이 WSDL을 사용하는 Client들은 types:Data에 대한 정보를 알 수 없게 되어, 이 메소드를 사용할 수 없게 된다.

#WSDL에 wsvalidate Decorator의 Custom Objects Type을 넣어주기 위한 해결책
편법적인 방법을 말고 좀더 근본적인 문제를 해결해 보도록 한다. TGWebService가 설치되어 있는 디렉토리를 둘러보면, controller.py가 있다. 이 안은 WebService 클래스안 각 메소드의 decorator 기능이 정의되어 있다.

  @wsexpose에 대한 정의
def wsexpose(return_type=basestring):
    """Exposes a method for access via web services. Only one value
    can be returned because many languages only support a single
    return value from methods.
    
    @param return_type the class that will be returned. This is used to
                       help statically typed clients."""
  ... 중략

        # a list denotes that the user is returning an array of the type
        # given by the first element in the array
        # we need to keep track of the complex types so that they can be
        # defined as appropriate for the given protocol
        if isinstance(return_type, list):
            rt = return_type[0]
            if rt not in primitives:
                register_complex_type(fi, rt)
        elif return_type not in primitives:
            register_complex_type(fi, return_type)

        return newfunc
    return entangle
: 볼드 표시된 부분을 자세히 살펴보자.
이전 포스트에서 언급하였듯이 wsexpose는 해당 메소드의 return_type을 설정하는 decorator이다. 볼드 표시한 부분은 return_type을 확인해서 알맞게 type을 등록하는 것을 알 수 있다.(return_type이 list인지, primitives인지, not primitives인지...)

  @wsvalidate에 대한 정의
def wsvalidate(*args, **kw):
    """Validates and converts incoming parameters. Also registers the
    parameters used by the method for use by statically typed languages.
    Method parameters can be specified via positional or keyword arguments.
    You should pass in the class used for each parameter."""
    def entangle(func):
        fi = register(func)
        input_types = dict()

        # match up the validators with the function parameters
        # the validators list doesn't include self, but the parameters list
        # does. So, the parameters list is offset by one higher.
        for i in range(0, len(args)):
            argtype = args[i]
            input_types[fi.params[i]] = argtype

        input_types.update(kw)
        fi.input_types = input_types
        return func
    return entangle
: wsvalidate는 위 wsexpose처럼 inpyt_type을 체크하고 type을 등록하는 부분이 없다. 그래서 아래와 같이 추가해준다. (wsexpose와 비슷하다.)

  @수정된 wsvalidate에 대한 정의
        # the validators list doesn't include self, but the parameters list
        # does. So, the parameters list is offset by one higher.
        for i in range(0, len(args)):
            argtype = args[i]
            input_types[fi.params[i]] = argtype

            if isinstance(argtype, list):
                rt = argtype[0]

                if rt not in primitives:
                    register_complex_type(fi, rt)
            elif argtype not in primitives:
                register_complex_type(fi, argtype)
 ... 후략

TurboGears를 멈추고, 리스타트하고 다시 WSDL을 확인해보자
<wsdl:types>
     <xsd:schema elementFormDefault="qualified" targetNamespace="http://localhost:8080/codegeass/soap/types">
        <xsd:complexType name="Person">
          <xsd:sequence>
            <xsd:element name="id" type="xsd:string"/>
            <xsd:element name="name" type="xsd:string"/>

          </xsd:sequence>
        </xsd:complexType>
          <xsd:element name="getPerson">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="types:Person"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="getPersonResponse">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="result" type="xsd:string"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
      </xsd:schema>
   </wsdl:types>
: 볼드 표시된 부분이 추가되었음을 알 수 있다.

#또 하나의 문제점...
이로써, 프로젝트 진행 중에 발생한 Custom Object는 해결이 되었으나, 또하나의 문제점이 발생하였다. wsvalidate에 List type([int], [float], [custom]...)을 적용하면 역시 WSDL에 반영되지 않는 것이다.

다음과 같이 array로 선언하고, WSDL을 확인해보자.
  @List type에 대한 CodeGeass
from tgwebservices.controllers import WebServicesRoot, \
    WebServicesController, wsexpose, wsvalidate

from tgwebservices.runtime import typedproperty, unsigned
class Person(object):        
    name = ''
    id = ''

class CodeGeass(WebServicesRoot):
    @wsexpose()
    @wsvalidate([Person])
    def getPerson(self, value):
        person = value
        print person.id
        print person.name

  @List type에 대한 WSDL
<wsdl:types>
     <xsd:schema elementFormDefault="qualified" targetNamespace="http://localhost:8080/codegeass/soap/types">
        <xsd:complexType name="Person">
          <xsd:sequence>
            <xsd:element name="id" type="xsd:string"/>
            <xsd:element name="name" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
          <xsd:element name="getPerson">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="types:Person_Array"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="getPersonResponse">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="result" type="xsd:string"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
      </xsd:schema>
   </wsdl:types>
: 도대체 types:Person_Array에 대한 정의는 어디에 숨었을까?

#WSDL에 wsvalidate Decorator의 List type 적용하기 위한 해결책
TGWebservice 소스중 soap.py를 분석해보자. 소스의 soapController 클래스는 일반 WebService 클래스의 wsexpose, wsvalidate Decorator의 분석 결과(위의 controller.py에서 체크한 결과)를 wsdl.html 템플릿으로 내뱉어주는 클래스다. 소스 코드의 마지막 부분에 있는 내부 메소드, _initialize_arrays 메소드를 잠시 들여다 보자.

  @soap.py 소스
...전략

    def _initialize_arrays(self):
        arrays = set()

        for func in self.wscontroller._ws_funcs.values():
            input_types = func._ws_func_info.input_types
            soap_type(func._ws_func_info.return_type, arrays)

        for cls in self.wscontroller._ws_complex_types:
            for key in ctvalues(cls):         
                soap_type(getattr(cls, key), arrays)
        self.arrays = arrays
: 일반 WebService 클래스(self.wscontroller)에 정의되어 있는 메소드의 return type을 arrays에 담는 작업(첫번째 loop)과 Custom Object type(두번째 2중 loop)을 arrays에 담는 작업을 이 _initialize_arrays에서 담당한다. 이 arrays는 이후 wsdl.html에서 loop를 돌면서 wsdl:type에 대해 출력한다.

이 메소드를 기준으로 소스를 분석해보면 return type(wsexpose)에 대해서만 처리할 뿐, input type(wsvalidate)에 대한 처리가 없음을 알 수 있다. 소스를 다음과 같이 추가하여 해당 메소드의 input type에 대한 처리하도록 한다.

  @수정된 soap.py 소스
    def _initialize_arrays(self):
        arrays = set()

        for func in self.wscontroller._ws_funcs.values():
            input_types = func._ws_func_info.input_types
            soap_type(func._ws_func_info.return_type, arrays)

            for it_key in input_types:        
                soap_type(input_types[it_key], arrays)

        for cls in self.wscontroller._ws_complex_types:
            for key in ctvalues(cls):         
                soap_type(getattr(cls, key), arrays)
        self.arrays = arrays

다시 한번 WSDL을 확인해보자
  @수정된 List type에 대한 WSDL
  <wsdl:types>
     <xsd:schema elementFormDefault="qualified" targetNamespace="http://localhost:8080/codegeass/soap/types">
        <xsd:complexType name="Person">
          <xsd:sequence>
            <xsd:element name="id" type="xsd:string"/><xsd:element name="name" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
          <xsd:element name="getPerson">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="types:Person_Array"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="getPersonResponse">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="result" type="xsd:string"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
        <xsd:complexType name="Person_Array">
          <xsd:sequence>
            <xsd:element maxOccurs="unbounded" name="item" nillable="true" type="types:Person"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:schema>
   </wsdl:types>
: 전에 없었던 Person_Array에 대한 정의가 생겼음을 확인할 수 있다. Person_Array 안 element의 type 역시 Person으로 정의된 걸 알 수 있다.

#결론
지금까지 wsvalidate Decorator에 Custom Object나 List Type을 적용할 경우 WSDL에 반영되지 않는 문제점과 그 해결책을 확인해보았다. Python 자체가 Dynamic Typed Language라서 Input Type에 대한 체크를 하지 않는 것 같은데, 실질적으로 내가 참여한 프로젝트는 Java와 Python 사이를 WebService
로 연동하기 때문에, 수정전의 WSDL로는 Java로 클라이언트를 생성할 수가 없었다.

앞으로 TGWebServices가 버전업될 때 마다 패치하기 보다는, 반영되는 것을 기대하는 것이 좋을 듯 싶다.
Posted by xHuro
,
#서론
간만에 python과 TurboGears의 정리 포스트다. 대략 2달전쯤 끄적여놓은 건데, 이제서야 정리하게 되었다.
TurboGears의 TGWebService 라이브러리는 간단하게 WebService 메소드와 WSDL을 생성해준다.
ZSI는 TGWebService가 생성한 WSDL로 WebService Client를 위한 클래스들을 생성해준다.

이번 포스트에서는 TGWebService와 ZSI를 이용해서 간단한 WebService Server/Client를 생성해본다.
필요한 라이브러리는 다 설치되어 있다는 가정하에 진행하도록 한다.


#필요한 Python Library


#TurboGears의 새 프로젝트 생성하기
tg-admin quickstart CodeGeass


#WebService로 제공할 클래스와 메소드를 정의하기
from tgwebservices.controllers import WebServicesRoot, \
    WebServicesController, wsexpose, wsvalidate

from tgwebservices.runtime import typedproperty, unsigned
class Person(object):        
    name = ''
    id = ''

class CodeGeass(WebServicesRoot):
    @wsexpose(Person)
    @wsvalidate(Person)
    def getPerson(self, value):
        person = Person()
        person.id = value.name
        person.name = value.id

        return person

- @wsexpose는 getPerson이 반환하는 class의 type이다.
- @wsvalidate는 getPerson이 호출될때의 parameter type이다.
- type에는 built-in type(int, str 등)과 user-define class(단 object 상속해야한다)들이 포함될 수 있다.


#TurboGears의 root controller에 TGWebService 연결하기(controller.py에 하위 클래스 추가)
from turbogears import controllers, expose, flash
from codegeass import CodeGeass
# from model import *        
# import logging             
# log = logging.getLogger("codegeass.controllers")

class Root(controllers.RootController):
    codegeass = CodeGeass("http://localhost:8080/codegeass/")

    @expose(template="codegeass.templates.welcome")
    def index(self):
        import time          
        # log.debug("Happy TurboGears Controller Responding For Duty")
        flash("Your application is now running")
        return dict(now=time.ctime())

- codegeass = .... 이 부분이 앞에서 만들어놓은 CodeGeass class를 root controller와 연결하는 구문이다. 생성자의 parameter는  CodeGeass의 instance가  web상에서 호출되는 URI를 다 넣어준다. (http://localhost:8080/codegeass/ 에서 codegeass/가 해당된다. 이해 잘 안된다면 TurboGears의 controller에 대해 구글링해서 공부하는 것이 좋다)


#TurboGears 서버 구동하기
./start-codegeass.py


#TGWebService로 생성된 WSDL 확인하기
http://localhost:8080/codegeass/soap/api.wsdl

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="CodeGeass" xmlns:types="http://localhost:8080/codegeass/soap/types" xmlns:soapenc="http://www.w3.org/2001/09/soap-encoding" targetNamespace="http://localhost:8080/codegeass/soap/" xmlns:tns="http://localhost:8080/codegeass/soap/">
   <wsdl:types>
     <xsd:schema elementFormDefault="qualified" targetNamespace="http://localhost:8080/codegeass/soap/types">
        <xsd:complexType name="Person">
          <xsd:sequence>
            <xsd:element name="id" type="xsd:string"/><xsd:element name="name" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
          <xsd:element name="getPerson">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="types:Person"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="getPersonResponse">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="result" type="types:Person"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
      </xsd:schema>
   </wsdl:types>

   <wsdl:message name="getPersonRequest" xmlns="http://localhost:8080/codegeass/soap/types">
       <wsdl:part name="parameters" element="types:getPerson"/>
   </wsdl:message>

   <wsdl:message name="getPersonResponse" xmlns="http://localhost:8080/codegeass/soap/types">
      <wsdl:part name="parameters" element="types:getPersonResponse"/>
   </wsdl:message>
   <wsdl:portType name="CodeGeass_PortType">
      <wsdl:operation name="getPerson">
         <wsdl:input message="tns:getPersonRequest"/>
         <wsdl:output message="tns:getPersonResponse"/>
      </wsdl:operation>
   </wsdl:portType>

   <wsdl:binding name="CodeGeass_Binding" type="tns:CodeGeass_PortType">
      <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
      <wsdl:operation name="getPerson">
         <soap:operation soapAction="getPerson"/>
         <wsdl:input>
            <soap:body use="literal"/>
         </wsdl:input>
         <wsdl:output>
            <soap:body use="literal"/>
         </wsdl:output>
      </wsdl:operation>
   </wsdl:binding>

   <wsdl:service name="CodeGeass">
      <wsdl:documentation>WSDL File for CodeGeass</wsdl:documentation>
      <wsdl:port binding="tns:CodeGeass_Binding" name="CodeGeass_PortType">
         <soap:address location="http://localhost:8080/codegeass/soap/"/>
      </wsdl:port>
   </wsdl:service>
</wsdl:definitions>

- xml로 정의된 Person, 그리고 getPerson을 볼 수 있다.
- 몇개의 URL이 더 있는데, TGWebServices의 README.txt의 URLs를 참고하면 된다.


#ZSI로 WebService client를 위한 코드 생성하기
wsdl2py --url http://localhost:8080/codegeass/soap/api.wsdl

-WSDL을 이용해서 코드를 생성한다.(Code Generation from WSDL and XML Schema 참고) 위의 명령을 실행하면 다음과 같은 python 파일이 생성된다.
CodeGeass_services.py : TGWebServices에서의 portType, Locator 등의 Class가 있는 파일
CodeGeass_services_types.py : TGWebServices에서 정의된 Data Type이 정의된 Class가 있는 파일  


#생성된 코드를 이용하여 WebService Client 만들기
from CodeGeass_services import *
from CodeGeass_services_types import ns0

loc = CodeGeassLocator()
port = loc.getCodeGeass_PortType()
req = getPersonRequest()

inputMessage = ns0.Person_Def(None, None)
inputMessage._id = "dada"
inputMessage._name = "lulush"

req._value = inputMessage
res = port.getPerson(req)

returnMessage = res._result
print "Result ID:", returnMessage._id
print "Result name:", returnMessage._name

Locator와 Port를 설정하고, 호출할 메소드의 Request(getPerson+Request)에 파라메터를 넣는다.
그리고 Port를 통해 호출하면 값을 받아올 수 있다.
req._value 와 res._result는 위의 WSDL의 <xsd:element name="getPerson">, <xsd:element name="getPersonResponse">에서 하위 노드의 value 값과 매칭된다.


#실행

위의 소스 코드를 실행하면, inputMessage의 _id와 _name이 swap되어 나오면 정상적으로 처리된 것이다.


#결론
TGWebService를 사용해서 간단히 WSDL을 만들고, 그 WSDL을 ZSI로 코드 생성하고 클라이언트 만드는 방법을 알아보았다. 여기서는 TGWebService의 WSDL을 사용했는데, 조금 응용하면 자바, php등의 WSDL을 가지고 Client를 만들 수 있으며, 역으로 다른 시스템에서 TGWebService의 WSDL로 Client를 생성할 수도 있다.
다음 포스팅은 Java의 XFire와 TGWebService의 연동 방법에 대해 알아본다.
Posted by xHuro
,
cx_Oracle을 사용하다보면, utf-8로 인코딩된 데이터들을 table에 insert하면 모조리 ???로 바뀌는 상황이 발생한다.
cx_Oracle의 encoding이 US-ASCII 로 잡혀있기 때문이다.
다음 코드로 encoding을 확인할 수 있다.

import cx_Oracle
conn = cx_Oracle.connect('test/test@TYPED')
conn.encoding

US-ASCII 일 경우, 다음 처럼 NLS을 설정하면 쉽게 해결된다.

import os
os.environ['NLS_LANG'] = '.UTF8'

import cx_Oracle
conn = cx_Oracle.connect('test/test@TYPED')
conn.encoding
Posted by xHuro
,
사용자 삽입 이미지

내 로컬에서의 vim 환경

python을 위한 vim 플러그인들 : runscript.vim, taglist.vim, python.vim

이클립스 플러그인 중의 하나인 pydev를 사용해도 되긴하지만, 서버에서 직접 코딩하는 경우가 많기 때문에 여러가지 플러그인을 설치했다. (다시 한번 vim의 확장성에 감탄하는 중...)
Posted by xHuro
,
TurboGears에서 SQLObject를 이용한 Unit Test시 dburi을 설정하지 않으면, 기본적으로 sqlite(memory DB)로 설정된다. Unit Test시에는 아래와 같이 초기에 설정해준다.

from turbogears import testutil, database
database.set_db_uri("db2://test:test@dada.pe.kr")


여기서 db2 대신, 각각 쓰고 있는 DB로 설정하면된다.
Posted by xHuro
,
class RegisterTest(unittest.TestCase):
    pass

def suite():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(RegisterTest))
    return suite


unittest.TextTestRunner(verbosity=2).run(suite())

unittest.TestCase를 사용할 때 TestSuite에 넣고 Test 실행하기.
각 test case에 대해 comment를 달면, comment도 같이 출력되어 보기가 좋다.

시간날때 좀더 업데이트 해야겠다.

Posted by xHuro
,
from time import time

def test:
    pass

start = time()

for index in range(0,1000):
    test()

end = time()

print 'total time : ' + str(end - start)


간단한 python code. 별거 아닌데, 익숙치가 않아서 모듈들이 헷갈린다.
기억력을 믿지 말고 검색과 기록을 생활화 해야한다^^
Posted by xHuro
,
생성 날짜로 보니 어제쯤에 4.1 SNAPSHOT이 release 된 거 같다. 라이브러리 저장소가기
요즘 메시지 큐를 프로젝트에 적용하고 있는데 며칠동안 삽질한 결과, 만족할만한 결과가 나오길래 포스팅해도 될꺼 같다는 판단하에 남겨놓는다.

● 개발환경
  - H/W : Dell insprion 6400
  - OS : Windows XP
  - Platform : JDK 1.5
  - IDE : Ecilpse 3.1 + WDT

● 사용라이브러리
- 꽤나 많다. 대부분 activeMQ 라이브러리 안에 포함되어있다. 포함되지 않은 것들만 소개한다.
- activeMQ 4.1 SNAPSHOT
- jakarta commons
  - net 1.4.1
  - collection 3.1
  - dbcp 1.2.1, pool .12 (DB 사용시에 필요^^)
- xercesImpl (XML 파서)
- geronimo : Geronimo 시작하기(activeMQ 내부적으로 사용되는 라이브러리기 때문에 굳이 읽을 필요는 없다)

● 참고문서
- 라이브러리 안에 간단한 예제를 보면 이해하기 쉽다.
  -  examples이라는 폴더 안에 있다.

● 선수지식
- AciveMQ 홈페이지 가서 최소한 JMS의 내용이라도 이해하고 있어야 쉽다.

● 구조
- example과 다른 것은 없지만, 약간 변경했다.

역시 이클립스다.


● 설명
- 어설프지만 개념은 요렇다! 자세한 건 AciveMQ 홈페이지에서...
- kr.pe.dada.main.ActiveMQSample.java : 다운로드
main 클래스. 먼저 Broker(Queue)를 생성한 다음, Consumer를 생성하고, Producer를 이용해서 5개의 메시지를 Broker에게 넘긴다. Thread.sleep이 사용된 이유는 각각 라이브러리를 로딩하는 데 시간이 걸리기 때문이다.

- kr.pe.dada.broker.ActiveMQBroker.java : 다운로드
지정된 xml 설정파일을 xbean 이용해서 읽는다. 굳이 xml 설정파일을 읽지않고 쓰려면 example의 예제처럼 BrokerService를 생성하고 하나하나 setter 메소드로 설정해주면 된다 :)

- kr.pe.dada.broker.ActiveMQProducer.java : 다운로드
Message를 보낼 목적지(Destination)을 설정하고, 현재 시간을 Message에 실어 Broker로 보낸다. (send 메소드) session 생성시에 유의할 점이 있는데, 나중에 간단히 설명하도록 하겠다.

- kr.pe.dada.broker.ActiveMQConsumer.java : 다운로드
Producer가 Broker에게 던지면, Broker가 다시 Consumer에게 Event(MessageListner를 통해서..)를 알린다. 역시 Producer와 동일하게 Destination을 정하고(같은 Destination을 사용해야한다.), Listner 2개를 등록한다.

- activemq.xml : 다운로드
Broker에 대한 전반적인 설정 xml. 내용이 많으니 Xml Configuration을 참고하자. 내가 만든 예제는 JDBC를 이용하여 Queue를 DB로 이용한 예제다. DB말고 메모리, 파일로도 사용가능하니 꼭 읽어봐야한다^^

● 포럼
- nabble

● 삽질기
  1. Ack 문제
- 버그인 듯한데...  Producer와 Consumer에서 session 생성시 다음과 같이 되어있다.
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
여러가지 장애처리를 염두해두느라, Producer와 Broker를 돌리고 있는 도중 Consumer를 죽여봤다. 즉, Message가 계속 Queue에 쌓이는 상태에서 Consumer를 따로 실행하면, 일단 Message를 잘 받는다. 하지만, 다시 죽이고 실행하면 이전의 Message를 또 받는 것이다. (dequeue가 제대로 되질 않는다. nabble 포럼에서는 AUTO_ACKNOWLEDGE의 버그일 꺼라 추측하고 있다. 자동으로 ACK을 날려주는 옵션인 듯한데, 자동으로 ACK을 날려주지 않는다!)

포럼에서는 여러가지 해결책을 제시하고 있는데, 나의 경우는 다음과 같이 처리했다. Consumer와 Producer 동일하게 처리한다.
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);

그리고 Message를 주고 받을 때, 다음을 호출한다. Consumer에서는 OnMessage 메소드에서 Producer에서는 Send 메소드 호출후, acknowledge를 하면, 양쪽으로 ACK을 날림으로써 Message주고 받은 것을 알려준다.
message.acknowledge();

  2. mysql InnoDB engine 문제
- DB에 Table 생성시 에러가 발생
Table 'messagequeue.ACTIVEMQ_ACKS' doesn't exist

innoDB를 사용함으로써, PK의 데이터 사이즈(bytes)가 초과되어 발생하는 문제다. 아래 페이지를 참고해서 다음과 같이 처리한다. Unable to create ACTIVEMQ_ACK table
<persistenceadapter>
<journaledjdbc usejournal="false" journallogfiles="5" datadirectory="../activemq-data" datasource="#ds">
<statements>
<statements stringiddatatype="VARCHAR(128)" msgiddatatype="VARCHAR(128)" containernamedatatype="VARCHAR(128)">
</statements>
</statements></journaledjdbc>
Posted by xHuro
,
http://castor.codehaus.org/
http://www.geocities.com/sireenmalik/details.html
Posted by xHuro
,
회사에서 XMLRPC를 사용할 부분이 생겨서 간단하게 테스트용으로 만들었다.
v3.0이 나온지 얼마되지 않고 v2.0과 다른 부분이 많기 때문에 삽질을 꽤나했었다. (아직 홈페이지에 가면 v2.0의 jar 파일을 다운로드 받을 수 있다) 이번 테스트는 웹 애플리케이션용으로 만들어졌다.

● 개발환경
  - H/W : Dell insprion 6400
  - OS : Windows XP
  - Web Server : Tomcat 5.0.28
  - Platform : JDK 1.5
  - IDE : Ecilpse 3.1 + WDT

● 사용 라이브러리
  - Apache XML-RPC
  - ws-commons-util-1.0-SNAPSHOT.jar

● 참고문서
  - http://ws.apache.org/xmlrpc/server.html
  - http://ws.apache.org/xmlrpc/client.html
  - http://www.petrovic.org/blog/?p=127 (상당히 많이 참고했다.)

● 선수지식
  - 웹 프로그래밍 (Servlet 프로그래밍)

● 구조

이클립스에서 캡쳐했다.


● 서버
  - Server.java : 다운로드
  소스 내용은 진짜 간단하다. XmlRpcServlet을 상속받는 것밖에 없다. 그 외에는 보통의 Servlet 클래스와 다른 면이 없다.

  - XmlHandler.java : 다운로드
클라이언트에 의해 호출될 Class다. add 메소드는 간단히 두 수를 넘겨받아 더한 후 그 값을 리턴하는 메소드다.

  - XmlRpcServlet.properties : 다운로드
위의 두 클래스를 연결해 주는 properties 파일이다. 단순히 Client가 호출할 이름과 XmlHandler.java만 쓰여져있다. 이것이 v2.0과 큰 차이점 중 하나인데, 이전 버전에서는 setHandler라는 메소드를 통해서 mapping되었다면, 이번 버전에서는 상위 클래스에서 자동적으로 이 파일을 읽어서 알아서 mapping해준다.
주의할 점은 org.apache.xmlrpc.webserver 안에 존재해야 한다는 것이다. (임의로 디렉토리를 만든 다음, 이 파일을 넣어주면 된다)

● 클라이언트
- Client.java : 다운로드
안에 보면 상당히 간단하다. 위의 참고 문서(apache에서의 Client 설명)를 보면 설명이 잘 되어있다. xmlServer를 설정한 후, 위의 properties에서 mapping한 이름의 메소드를 호출하면서 parameter를 넘겨준다. 그 후 XmlHandler에서 리턴하는 값을 출력하도록 되어있다.
Posted by xHuro
,
Posted by xHuro
,

이번 프로젝트를 진행하면서 사용한 프레임 워크를 나열해보았다.
조만간(언제일지 모르지만) 따로따로 분해를 해보는 포스트를 작성해야겠다.

View : Velocity-Tools &Struts-Tiles
Presentation : Struts
Business : Spring
Persistence : Ibatis

Cache : OS-cache
Logger : Log4j

Posted by xHuro
,
eclipse의 어떤 설정을 건드렸는데, 뭘 건드렸는지 몰라도 계속 access denied 예외가 떴었다.
4시간 동안 삽질하다가 그냥 프로젝트를 새로 만들고 Copy & Paste 하니까 잘 돌아간다.

tomcat의 DBCP를 사용하기 위해서는 총 3군데에서 수정하고, 코드에 한 부분을 추가해야 한다.

server.xml에서의 설정은 다음과 같다.(자세한 설명은 http://tomcat.apache.org/tomcat-5.5-doc/index.html 부분을 참고하도록 한다.)
<GlobalNamingResources>

...
<Resource name="jdbc/dada_db" auth="Container"
type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="username" password="passwd" driverClassName="org.gjt.mm.mysql.Driver"
url="jdbc:mysql://localhost:3306/dada_db?autoReconnect=true"/>

</GlobalNamingResources>


eclipse에서 war를 만들지 않고 바로 톰캣에 올릴 때는 $CATALINA/conf/Catalina/localhost/xxxxx.xml 파일안을 다음과 같이 편집한다. (xxxxx는 context명으로 되어있다.)
<Context path="/test" reloadable="true" docBase="D:\test\test" workDir="D:\test\test\work" >
<ResourceLink name="jdbc/dada_db" global="jdbc/dada_db" auth="Container" type="javax.sql.DataSource"/>
</Context>


war를 만들었을 경우에는 server.xml 안의 Host 태그를 다음과 같이 편집하면 된다.
<Context path="/test" reloadable="true" docBase="D:\test\test" workDir="D:\test\test\work" >
<ResourceLink name="jdbc/dada_db" auth="Container" type="javax.sql.DataSource"/>
</Context>


두 개의 차이점은 global 속성에서 차이가 난다. 마지막 설정은 어플리케이션의 web.xml에서 편집한다.
마지막 부분에 다음을 추가해준다.
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/dada_db</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>


마지막으로 자바 소스 코드에서 Connection을 얻어올 때 다음과 같이 처리한다.
Context ctx = null;
DataSource dataSource = null;
Connection con = null;
try {
ctx = new InitialContext();
dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/dada_db");
con = dataSource.getConnection();
} catch (NamingException e) {
System.out.println("Naming : " + e.toString());
} catch (SQLException e) {
System.out.println("Connection : " + e.toString());
}
Posted by xHuro
,
http://jakarta.apache.org/velocity/tools/struts/

  • 기본 라이브러리를 WEB-INF/lib에 복사
    velocity-1.4.jar
    velocity-tools-1.2.jar
    velocity-tools-generic-1.2.jar
    velocity-tools-view-1.2.jar

  • web.xml에서의 velocity 설정부분
    <servlet>
    <servlet-name>velocity</servlet-name>
    <servlet-class>org.apache.velocity.tools.view.servlet.VelocityViewServlet</servlet-class>
    <init-param>
    <param-name>org.apache.velocity.toolbox</param-name>
    <param-value>/WEB-INF/toolbox.xml</param-value>
    </init-param>
    <init-param>
    <param-name>org.apache.velocity.properties</param-name>
    <param-value>/WEB-INF/velocity.properties</param-value>
    </init-param>
    <load-on-startup>10</load-on-startup>
    </servlet>
    ...
    <servlet-mapping>
    <servlet-name>velocity</servlet-name>
    <url-pattern>*.vm</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
    <welcome-file>index.vm</welcome-file>
    </welcome-file-list>

  • Velocity Configuration (velocity.properties)
    velocimacro.library = /WEB-INF/VM_global_library.vm
    velocimacro.permissions.allow.inline = true
    velocimacro.permissions.allow.inline.to.replace.global = false
    velocimacro.permissions.allow.inline.local.scope = false
    velocimacro.context.localscope = false
    ※ 좀 더 많은 설정은 Velocity User's Guide 참고
  • Posted by xHuro
    ,
    아주 기초적인 건데... 모르고 있었습니다.
    우분투를 처음 설치하면 vi 편집기가 흑백으로만 나오죠^^

    다음과 같이 하고 vi 편집기를 다시 실행해보세요..
    색상이 문법에 따라 잘 나올껍니다^^
    dada@ubuntu:~$ echo "syntax enable" >> .vimrc
    Posted by xHuro
    ,