blah blah

groovy – CharsetToolkit 등등

이번에도 groovy.

간단한 작업 위해 만든 스크립트인데 나중에 써먹을 만한게 몇가지 있어서 적어봅니다.

작업 개요 : 특정 폴더 하위에 있는 모든 폴더의 자바 파일의 인코딩을 MS949 에서 UTF-8 로 전환

간단하니 일단 소스 부터.

import groovy.io.FileType
import groovy.util.CharsetToolkit

folder = "E:\\encoding_test\\data"
result = "E:\\encoding_test\\result"

new File(folder).eachDirRecurse {                            // (1)
    subDir = it.path.replace(folder, "")
    new File("${result}\\${subDir}").mkdirs()           // (2)
}

new File(folder).traverse(type:FileType.FILES, nameFilter:~/.*\.java/) {
    charSet = new CharsetToolkit(it).getCharset();          // (3)
    target = result + it.path.replace(folder, "")

    if(charSet.toString() != "UTF-8") {
        new FileOutputStream(target).withWriter('UTF-8') { writer ->        // (4)
            new FileInputStream(it).withReader('MS949') { reader ->
                writer << reader
            }
        }
    }
    else {
        new AntBuilder().copy (file : it , tofile : target)                           // (5)
    }
}

(1)의 eachDirRecurse() 는 recursive 하게 디렉토리 정보를 구하는 메서드입니다.

( 제 컴 환경에서는 그냥 eachDir() 만으로도 되던데 딴 분 컴에서는 제일 상위의 서브 디렉토리만 찾는 문제가 있더군요. OS 차이 때문일까요? )

(2)에서는 mkdirs() 를 이용해서 결과 파일 저장할 디렉토리 하위에 작업 대상 디렉토리 구조와 동일하게 서브 디렉토리를 만들어 주었습니다.

(3)이 이번 작업하면서 알게된 꽤 유용한 groovy 에서 지원되는 기능인데 … groovy.util.CharsetToolkit 라는 캐릭터셋 처리 위한 패키지가 있더군요.

CharsetToolkit 에서 제공되는 getCharset()이란 메서드를 이용해서 파일의 캐릭터셋을 쉽게 알아낼 수 있습니다.

이걸 쓴 이유는 작업하려는 파일들에 MS949, UTF-8 파일이 섞여 있어서 무조건 인코딩을 바꿔주면 원래 UTF-8 형식이었던 파일은 한글이 깨지는 문제가 발생하더군요.

그래서 처리 전 파일의 캐릭터셋을 미리 확인해서 UTF-8인 경우에는 그냥 인코딩 변환 없이 결과 디렉토리로 복사만 해주었습니다.

(4) 이하 몇 줄은 ‘MS949’ 형식 파일을 UTF-8로 변환해주는거구요.

(5)번은 파일 복사하는 부분입니다.

자바와 마찬가지로 File 클래스에 보면 renameTo()  라는 메서드가 있어서 rename 이나 move 등의 용도로 쓸 수 있습니다. (renameTo 쓰는 예는 지난 번 글에 있으니 참고하세요)

하지만 copy 를 위한 메서드는 따로 있지 않으므로 직접 구현해야 하는데 대신 AntBuilder의 copy 기능을 이용하면 쉽게 처리할 수 있습니다.

( AntBuilder는 별도로 import 처리할 필요도 없습니다 )

이게 전부입니다. 딴거야 자바로 처리해도 크게 아쉬운거 없지만 groovy.util.CharsetToolkit 은 알아두면 꽤 유용하게 쓸 수 있을 듯 싶네요.

Advertisements
blah blah

groovy 사용 간단 예 – gpath, withBatch 등

이번에는 groovy.

******

프로젝트 지원 나와서 간단한 작업 하나를 받았습니다.
특정 디렉토리 안에 있는 동일한 구조의 xml 파일들에서 몇 개의 element 값을 추출해서 DB 에 넣는거랍니다.

일단 뭘로 짤까 잠시 고민했는데 텍스트 파일 처리하는 이 정도의 작업이면 역시나 스크립트 언어가 적격이라 판단했습니다.
그리고 이 작업 맡을 분이 자바 사용자니 딴거보다는 적응이 용이하겠다 싶어서 스크립트 언어 중에서 groovy 를 선택했습니다.

그럼 본격적으로 …

xml 은 대충 이런 식.

<?xml version=”1.0″ encoding=”UTF-8″?>
<abouthjs>
<titleimg>../img/news/tit_notice.gif</titleimg>
<contenttitle>어쩌구 저쩌구 24HR FAQ)</contenttitle>
<appendimg/> <viewdate>2010-11-10 13</viewdate>
<contents>
<contentimg/>
<content>어쩌구 저저꾸 24HR rule 관련 Customer Advisory…</content>
</contents>
<servicekind>T</servicekind>
<localekind/>

</abouthjs>

여기서 contenttitle, content, viewdate 등의 값을 추출해야 합니다.

groovy 소스부터 보겠습니다.

import groovy.io.FileType
import groovy.sql.*

def getXmlElement(xmlFile) {
def xml = new XmlSlurper().parse((xmlFile.path).replace(“\\”, “/”))   // (3)
def eleList = []
eleList.add(xml.contenttitle.text())                                  // (4)
eleList.add(xml.contents.content.text())
eleList.add(xml.viewdate.text())
return eleList
}

def insertDb(resultMap) {
def db = Sql.newInstance(“jdbc:oracle:thin:@dbUrl:port:dbSid”, “id”, “pw”, “oracle.jdbc.driver.OracleDriver”) // (5)
db.withBatch(100, “INSERT INTO test (val1, val2, val3) VALUES (?, ?, ?)”) { ps ->                               // (6)
resultMap.each { k, v ->
ps.addBatch(v)                                                // (6-1)
}
}

db.close()
}

def folder = args?.size() ? args[0] : “.”                            // (1)
resultMap = [:]
new File(folder).traverse(type:FileType.FILES, nameFilter:~/.*\.xml/, maxDepth:0) {    // (2)
resultMap[it.getName()] = getXmlElement(it)                                                         // (2-1)
}

insertDb(resultMap)

단순한 작업이라 달랑 이게 전부입니다.

groovy 의 아주 기초 문법들은 일단 패스하고 이 작업에 사용한 groovy 의 몇가지 기능만 추려보겠습니다.

  • job1. 디렉토리에서 파일 목록 구하기 : File
  • job2. XML 에서 데이타 추출 : gpath
  • job3. DB 처리 : groovy sql

소스 실행 방법은

C:\>groovy XmlTest.groovy C:\data\xml

( 소스 파일명은 XmlTest.groovy, XML 파일이 있는 디렉토리는 C:\data\xml 이라고 했습니다. )

소스 실행하면 진입점은 (1)이 됩니다.
인자로 준 디렉토리를, 인자가 없을 경우에는 현재 디렉토리를  변수로 받습니다.

job1에 해당하는게 (2)입니다.
지정한 디렉토리에서 파일 목록 구하는 방법은 꽤 많은데 이번 경우에는 traverse(…)라는 함수를 사용해봤습니다.
xml 파일 목록만 구하기 위해 nameFilter:~/.*\.xml/ 를, 검색할 디렉토리의 하위 디렉토리는 찾을 필요 없기에 maxDepth:0 등의 패러미터를 주었습니다.
(2-1)에서는 루프 돌면서 각각의 파일 정보(it)를 넘겨서 원하는 element 값들을 되돌려받아 resultMap 이라는 Map 에 파일명을 키값으로 담습니다.

job2에 관련된게 getXmlElement(…)입니다.
(3)의 XmlSlurper().parse 에서 xml 파일 읽어들였고 (4)에서 원하는 element 값들 추출합니다.

(4)에서 쓰인게 gpath 란 기능인데 이건 xpath 의 groovy 구현체라고 생각하면 됩니다.
xml 샘플의 구조와 xml.contents.content.text() 를 매칭시켜보면 gpath 가 얼마나 간편하게 특정 노드의 데이타를 가져올 수 있는지 알 수 있습니다.
xpath 란거 이름만 들어봤지 실제 써보지 않았는데 xml 데이타 추출하는데서 만큼은 DOM이나 SAX 쓰는 것보다 무지 편한 것 같습니다.
자바5 부터는 javax.xml.xpath 패키지가 기본 포함되었다 하니 앞으로 자바 코딩할 일 있을 때면 xpath 를 애용하게 될 것 같네요.

job3에 해당하는건 insertDb(…)입니다.
groovy 역시 sql 사용 위해서는 jdbc 쓰므로 자바 코딩할 때 처럼 (5)번 써주면 되고 기타 groovy sql 기본 사용법은 패스.
이번 코딩에서는 groovy 1.8.1 버전 부터 지원되기 시작한 자바의 batchUpdate 에 해당하는 withBatch를 써보았습니다.

자바의 batchUpdate는 아시다시피 다수의 sql문을 배열에 담은 후 한꺼번에 처리해주어 성능상의 이점을 얻을 수 있죠.
마찬가지로 (6-1) addBatch 를 통해 복수의 xml 파일에서 추출한 값들을 순차적으로 넣어줍니다.

(6)의 insert 문 보면 필요한 bind 변수가 3개 인데 (6-1) ps.addBatch(v) 한 줄로 처리되었나 궁금하신 분도 계실겁니다.
여기서 v 는 소스 따라가보면 getXmlElement 에서 리턴한 eleList 라는 List 타입의 변수값입니다.
즉, v 에는 [“Test page”, “테스트 컨텐츠”, “2011-11-10 13”] 과 같은 형태로 값이 들어가 있는 셈이므로 저 한 줄 깔끔하게 처리됩니다.

batchUpdate 안쓰고 일반적인 방식으로 하자면 다음 정도로 하면 될겁니다.

def db = Sql.newInstance(…)
new File(folder).traverse(
type:FileType.FILES,
nameFilter:~/.*\.xml/,
maxDepth:0
) {
resultList = getXmlElement(it)
db.execute “INSERT INTO test (val1, val2, val3) VALUES (?, ?, ?)”, resultList
};db.close()

이상입니다.

*****

위 내용은 프로토타입으로 작성한거고 실제 구현했던거는 제약 조건이 꽤 많아서 소스 전면 수정( 한 300백 라인 정도)했습니다.

이 때 추가된 걸로 몇가지 간단하지만 나중에 재활용할만한거 두 어가지만 추가 정리

def getDurationTime() {
def timeStart = new Date()

….

def timeStop = new Date()
TimeDuration duration = TimeCategory.minus(timeStop, timeStart)

log.info duration
}

첨부 파일 처리할 때 쓴거로 유니크한 파일명 부여 위해서 uuid 사용한거

def getUuidFileName(fExt) {
uuid32 = getUUID()
if (!fExt.isAllWhitespace() && fExt.size() < 4) {
uuid32 = uuid32.substring(0, uuid32.size() – fExt.size() – 1) + “.” + fExt
}

return uuid32
}

 

 

 

blah blah

java properties 추출 위한 엑셀 매크로

이번에는 excel macro. 급조한거라 함수명이나 로직이 조악하기는 하지만 …

**********

자바 웹 개발 시 웹상의 다국어 페이지 관리 등을 위해 ResourceBundle 방식을 이용해서 메시지나 레이블 등을 properties 파일에 넣어 활용하곤 합니다.

그런데 보통 실제 웹 디플로이 전에는 관리를 위해 이 컨텐츠들을 엑셀로 취합 관리하는 경우가 일반적이고 변경 시에는 여기 내용을 복사해서

  • 텍스트 파일에 붙여 넣은 후 한글 등의 경우에는 native2ascii 를 실행해서 변환해주거나
  • 이클립스 사용자들은 이 추가 작업이 불편해서 PropetiesEditor 등을 사용하는 듯 합니다.

(옛날에는 보통 이런 식으로 했었지만 아마 요즘은 진일보한 방법 쓰고 있겠죠? 방법 공유 좀 해주세요 )

오늘은 엑셀에서 관리하는 내용을 properties 파일로 자동 추출하기 위해 짜본 간단한 매크로를 공유 차원에서 적어봅니다.

( 물론 POI 나 JExcelApi 등 이용해서 자바로도 충분히 가능한 작업입니다만 이 작업을 수행할 담당자가 개발자가 아니란 점을 고려하면 … )

  A B C D
1        
2   메시지 관리    
3 일련번호 영문 국문 중문
4 EST00001 Detail Schedule 상세 스케줄 详细船期
5 EST00002 Route Information 라우트 정보 路径信息

가령 엑셀의 문서 형식이 위와 같다고 할 때 매크로 소스는 아래와 같습니다.

소스의 VBA 문법에 관해서 설명은 건너뛰겠습니다.

데이타 추출해서 텍스트 파일로 저장 시 유니코드 값으로 전환하는 기능은 get_codepoints(…)에 구현해 놓았습니다. 여기서 핵심은 Hex(AscW(Mid$(msg, i, 1))) 입니다.

이 매크로 그런대로 잘 잘동했습니다만 중문의 간체의 유니코드를 처리 못하는 문제가 있더군요.

위 엑셀 샘플에서 详细船期 란 데이타 중 ‘详细’가 간자체입니다. 해당 글자의 번자는 ‘詳細’라고 하던데 아무튼 위에 언급한 Hex(…) 구문만으로는 ‘详细’의 정확한 값을 못가져옵니다.

그래서 간체 문제는 중문의 LocaleID (LCID) 값을 지정해주는 것으로 일단 해결했습니다 .

txt = txt & StrConv(Hex(Asc(Mid$(msg, i, 1))), vbFromUnicode, 1028) 이런 식으로 하면 되네요.

대충 기본적인 문제들 해결한 VAB 매크로 소스는 다음과 같습니다.

Sub ExportText()
    Dim fso As Object
    Dim dict As Dictionary, dict1 As Dictionary
    Dim v As Variant
    Dim sPath As String
    Dim key_col As String

    title_str = GetTitle()
  
    Set dict = New Dictionary
    With dict
       .Add “en”, title_str
       .Add “ko”, title_str
       .Add “zh”, title_str
    End With
   
    Set dict1 = New Dictionary
    With dict1
       .Add “ko”, Array(4, 4)
       .Add “en”, Array(5, 5)
       .Add “zh”, Array(6, 6)
    End With

    es_pos = pFindRowPos(“ESW”)
    es_title = vbCrLf & “#ES(e-Service)” & vbCrLf
   
    last_row = GetLastRow(“Sheet1”, “A”)
    For i = 3 To last_row
        key_col = Cells(i, 1)
        If i = es_pos Then
            For Each v In dict.keys
                dict(v) = dict(v) & es_title
            Next
        End If
       
        If key_col <> “” Then
            For Each v In dict.keys
                dict(v) = GetPropertieString(dict(v), key_col, Cells(i, dict1(v)(0)), v)
            Next
        End If
    Next i
   
    sPath = ActiveWorkbook.Path

    Workbooks.Open sPath & “\contents_v1.0.xlsx”
        find_pos = pFindRowPos(“WS”)
        last_row = GetLastRow(“Sheet1”, “A”)
       
        For i = find_pos To last_row
            key_col = Cells(i, 1)
            If key_col <> “” Then
                For Each v In dict.keys
                    dict(v) = GetPropertieString(dict(v), key_col, Cells(i, dict1(v)(1)), v)
                Next
            End If
        Next i
    ActiveWorkbook.Close

    Set fso = CreateObject(“scripting.filesystemobject”)
    For Each v In dict.keys
        r = CreateFile(fso, sPath, v, dict.Item(v))
    Next
    Set fso = Nothing
   
    MsgBox “done”
End Sub

Function GetTitle()
    GetTitle = “##############################” & vbCrLf _
             & “# Message Properties” & vbCrLf
             & “# 1. AM(Admin)” & vbCrLf _
             & “#################################” & vbCrLf _
             & “#CM(Common)” & vbCrLf
End Function

Private Function pFindRowPos(stext As Variant, _
  Optional searchdirection As XlSearchDirection = xlNext, _
  Optional searchorder As XlSearchOrder = xlByRows) As Long

    Dim lresult As Long, org As Range

    Set org = Cells.Find(what:=stext, LookIn:=xlValues, _
    lookat:=xlPart, searchorder:=searchorder, _
                 searchdirection:=searchdirection, _
    MatchCase:=False, searchformat:=False)

    If Not org Is Nothing Then lresult = org.Row

    pFindRowPos = lresult

    Set org = Nothing

End Function

Function GetLastRow(data_sheet As String, key_column As String)
    Worksheets(data_sheet).Activate
    With ActiveSheet
        LastRow = .Cells(.Rows.Count, key_column).End(xlUp).Row
    End With
   
    GetLastRow = LastRow
End Function

Function GetPropertieString(curr_txt As String, key_col As String, cel_val As String, lang As Variant)
    Dim result As String
    result = curr_txt & key_col & ” = ” & GetCodePoints(cel_val, lang) & vbCrLf
   
    GetPropertieString = result
End Function

Private Function CreateFile(fso As Object, sPath As String, prop_file As Variant, msg_str As String)
    Set lang_prop = fso.CreateTextFile(sPath & “\message_” & prop_file & “.properties”, True)
    With lang_prop
        .Write msg_str
        .Close
    End With
End Function

Function GetCodePoints(msg As String, lang As Variant)
    Dim txt As String
    Dim iChar As Integer
   
    msg = Replace(msg, Chr(10), ” “)
    For i = 1 To Len(msg)

        iChar = AscW(Mid$(msg, i, 1))

        If iChar = 63 Then
            If lang = “zh” Then
                If Mid$(msg, i, 1) = “?” Then
                    txt = txt & “?”
                Else
                    txt = txt & StrConv(Hex(Asc(Mid$(msg, i, 1))), vbFromUnicode, 1028)  ‘간체
                End If
            Else
                txt = txt & “?”
            End If
        ElseIf iChar = 160 Then              ‘공백 특수문자 처리
            txt = txt & ” ”
        ElseIf (iChar < 128 And iChar > 0) Then ‘Or (iChar > 8210 And iChar < 8304) Then   ‘“ ” ‘ ’ 처리
            txt = txt & Mid$(msg, i, 1)
        Else
            uni = StrConv(Hex(AscW(Mid$(msg, i, 1))), vbLowerCase)
            If Len(uni) = 2 Then
                uni = “00” & uni             ‘ub7, ub0, ue1
            End If
            txt = txt & “\u” & uni
        End If
    Next i

    GetCodePoints = txt
End Function

  1. Dictionary 란걸 사용했는데 이걸 쓰려면 scrrun.dll 을 사용할 수 있도록 엑셀의 ‘도구 > 참조’에서 Microsoft Scripting Runtime’ 을 선택해주어야만 합니다. 
  2. pFindRowPos 라고 특정 문자열이 들어있는 셀의 row 위치 구하는 함수는 http://excelvbamacro.com/how-to-find-row-position-of-a-particular-text.html 에서 구했습니다.

 덤으로 이 기능을 ruby 로 간단하게 구현한 테스트 코드도 적어 봅니다.

require ‘win32ole’
require ‘active_support’
 
#excel = WIN32OLE::new(‘excel.Application’)
#sheet = excel.Workbooks.Open(‘E:\test1.xlsx’).Worksheets(‘Sheet3’)
 
excel = WIN32OLE::connect(‘excel.Application’)
sheet = excel.activesheet
sheet.Range(‘B4:E200’).columns.each { |col| col.cells.each { |cell| puts cell.Value + ” : ” + ActiveSupport::JSON.encode(cell.Value) } }
 
루비 소스는 제대로 쓰려면 한참 다듬어야 하지만 그래도 기본 기능은 들어가 있으니 … ( ActiveSupport::JSON.encode(cell.Value)가 unicode 의 code print 값 구하는 부분입니다.)
아직 간자 문제 등은 처리하지 않은 상태인데 엑셀에서 해결한 방식처럼 encode 함수 사용 시에 Locale 코드값을 설정하는 방법이 있는지 좀 찾아봐야겠습니다.
blah blah

ruby 기초 예 – multiple values return, Variable Arguments

수작업으로 해야할 일 생겨서 스크립트로 처리하고 간단히 메모했던 몇 건들을 블로그에 나눠서 옮겨 적어본다.

처음 건은 간만에 ruby 로 했던 …

*********************

서버 쪽 구성이 변경되면서 이 틈을 타고 데이타 재 인터페이스에 대한 요청이 몰려드네요. 수작업으로 건별 처리하다 짜증나서 간만에 루비 스크립트를 짜봤습니다.
수작업으로 하려면 잔손이 많이 가서 그렇지 작업 패턴은 단순해서 구현에는 그다지 특별한 내용은 없었습니다. 그래도 몇가지 기초적이지만 재미난 것들만 뽑아서 적어봅니다.

1. ruby는 복수의 값 리턴이 가능합니다. 이런 식이죠.

def getSql(msg_id)
case msg_id
when “M025”
sql = “select …. from goods”
module_name = “goods”
when “M001”
sql = “select …. from customer”
module_name = “customer”
end

return module_name, sql
end

module_name, sql = getSql(“M025”)

 

자바 등에서는 이런 경우에 array 나 collection 등을 쓰겠지만 루비에서는 이렇게 간편하게 …

2. 루비는 Variable Arguments(가변인수)를 쓸 수 있습니다.

def fetchIf(dbh, msg_id, *seg)
….
end

fetchIf(dbh, “MDM001”, “US000025”)
fetchIf(dbh, “MDM001”, “GOODS”, “TR01”)

 

위의 함수 선언부에 *seg 라고 한 부분 보이시죠? 이런 식으로 “*”을 붙여서 가변인수를 사용할 수 있답니다.
가변 인수로 지정된 인자들은 배열 형태로 처리되므로 쉽게 사용 가능하죠.

그런데 여기서 제가 꽤 헤메었습니다.

def fetchIf(dbh, msg_id, *seg)
module_name, sql = getSql(msg_id)
rs = dbh.prepare(sql)

if seg.length == 1
rs.execute(seg[0])
else
rs.execute(seg[0], seg[1])
end

end

 

가변인자로 넘겨받은 값들을 sql 의 바인드 변수값으로 넣어주려고 하는데 도무지 깔끔하게 안되더군요.

seg.join(“,”) 과 같은 식으로 값 분리해서 넘겨주어도 안되고 기타 등등 …
그런데 허무하게도 넘겨줄 때도 그냥 가변인자 방식으로 하면 되더군요. 이렇게 한 줄로.

rs.execute(*seg)

execute 메서드에 이리 넘겨주어도 알아서 잘 처리해줍니다.

공부하는 셈치고 dbi 라이브러리 소스 파일에서 execute 구현된 부분은 찾아보니

        def execute(*bindvars)

@handle.bind_params(*bindvars)
@handle.execute


@row = DBI::Row.new(column_names, column_types, nil, @convert_types)
#end
return nil
end

대충 저런 식으로 구현되어 있습니다. 역시나 sql 바인딩할 변수를 몇 개 넘겨줄지 모르니 가변인자를 쓴 듯.

내친 김에 execute 에서 가변인자를 또다시 넘겨 호출하는 bind_params 는 또 어떻게 구현되었나 찾아보았습니다.

       def bind_params(*bindvars)
bindvars.each_with_index {|val,i| bind_param(i+1, val, nil) }
self
end

이제 대략 응용할 방법 알 듯 합니다.

( 정말 혹시나 해서 첨언하자면 1.5 부터는 java 에서도 가변인자 지원합니다. void sum(int … i) 같은 식으로요. 역시나 배열로 처리됩니다 )

3. 수작업의 마지막 단계는 DB 에서 필요한 정보값들 구한 다음에 이 값들을 get 방식으로 구성해서 브라우저에서 웹서비스를 호출하는 것입니다.
루비에서 이 작업을 자동으로 처리하기 위해 HTTP 클라이언트 API 를 사용했습니다.
( http://ruby-doc.org/stdlib-1.9.3/libdoc/net/http/rdoc/Net/HTTP.html 여기보면 잘 정리되어 있습니다. )

params  = { :ModuleName => module_name, :InterfaceType => if_type, :TransactionStatus => “U”, :TransactionId => tran_id }
uri.query = URI.parse(URI.encode_www_form(params))

res = Net::HTTP.get_response(uri)

 

잘 되는 듯 싶더니 수시로 URI::InvalidURIError 에러가 나더군요.

원인 찾아보니 :TransactionId => tran_id 에서 trans_id 변수에 넘어가는 인자값이 종종 ‘103|8230032’ 과 같은 형태가 되는데 “|” 가 범인인 듯.

RFC 2396 section 2.4.3 에 이런 내용이 있다고 하네요.

Other characters are excluded because gateways and other transport agents are known to sometimes modify such characters, or they are used as delimiters.
unwise = “{” | “}” | “|” | “\” | “^” | “[” | “]” | “`”
Data corresponding to excluded characters must be escaped in order to be properly represented within a URI.

 

예전에도 이 문제 피해가기 위해 트릭 쓴 적 있었는데 오래되어서 기억이 전혀 안나서 또 괜한 고생.

해법은 역시나 너무 간단했는데 bad URI 에러니 문제되는 인자값을 get 방식으로 넘겨주지 않고 다른 방식으로 넘겨주면 되는 것, 그냥 post 방식으로 넘겨주면 됩니다.

앞에 링크한 문서에 post 방식예도 잘 설명되어 있으므로 더 이상의 설명은 패스. libcurl 등으로 cURL 을 사용하는 방법도 고려해볼 수 있을텐데 이것 역시 패스.

 

blah blah

XWiki Setting

standalone 다운 받아서 잠시 써오다 팀 공용 PC 로 이전하면서 tomcat, MySQL 로 변경 작업을 했다.

일단 WAR 파일을 이용한 기본 설치 방법은 XWiki 공식 설치 문서를 참고하면 된다.

다음으로 MySQL 설정은 이 문서 ( http://platform.xwiki.org/xwiki/bin/view/AdminGuide/InstallationMySQL ) 보고 작업해주면 되는데 이 부분에서 의외로 발목 잡혀서 그 내용을 집중 정리해본다.
MySQL 이용하기 위해서는 우선 /WEB-INF/hibernate.cfg.xml 에서 hsqldb 로 설정 활성화되어 있던 부분을 코멘트 처리하고 MySQL 설정 부분을 활성화 시켜주어야 한다.
이것만 해주면 될 줄 알았다.  브라우져에서 접속해보니
javax.servlet.ServletException: Error number 3 in 0: Could not initialize main XWiki context
Wrapped Exception: Error number 3001 in 3: Cannot load class com.xpn.xwiki.store.migration.hibernate.XWikiHibernateMigrationManager from param xwiki.store.migration.manager.class

500 에러만 나고 실행되지 않는다.

복잡하게 이것 저것 고민했는데 당연하다고 여긴 것 부터 의심했어야 했다.

MySQL jdbc 드라이버가 배포 WAR 에는 들어 있지 않다. ( 위 MySQL 문서에도 언급되어 있는데 놓쳤다 ㅜㅠ )

/WEB-INF/lib 디렉토리에  jdbc 드라이버 파일을 집어 넣고 다시 실행해봤다.

이것만 해주면 정말 될 줄 알았다… 또 500 에러. DB 풀 생성 못했다며 hibernate.cfg.xml 설정 이상 없는지 점검해보라는데 아무리 봐도 이상없다.

이 에러 때문에 정말 한참 고생하다가 /WEB-INF/xwiki.cfg 파일에서 아래 부분 활성화해 주었다.

xwiki.store.hibernate.path=/WEB-INF/hibernate.cfg.xml

혹시 몰라서 이것도 활성화

xwiki.store.migration.manager.class=com.xpn.xwiki.store.migration.hibernate.XWikiHibernateMigrationManager

여전히 에러 …
hibernate.cfg.xml 에서 connection.url 기본 설정값을 인자없이 설정해보았다.

기본 : jdbc:mysql://localhost/xwiki?useServerPrepStmts=false&useUnicode=true&characterEncoding=UTF-8&sessionVariables=sql_mode=”
변경 : jdbc:mysql://localhost/xwiki

다시 실행. 드디어 텅빈 메인 화면이 뜬다.

기본 위키 문서 등이 포함되어 있는 xwiki-enterprise-wiki-**.xar 을 import 기능을 이용해서 설치하니 ( 기본 설치 시 처음에는 Admin 계정으로 로긴 안해도 이 기능 사용하게 되어 있다 ) 이제야 Xwiki 기본 꼴은 갖추고 표시된다.

  • 주 : import 하려고 xar 파일 선택해서 upload 버튼 눌러도 파일 선택하라는 메시지만 표시되고 아무 변화 없다면 위에 링크해 둔 MySQL 설정 문서에서 “max_allowed_packet” 부분 참고해서 패킷 크기를 늘여주어야 한다.

자, 이제 후속 작업.

기존 standalone 에 작성했던 컨텐츠들을 이전해와야 하는데 Xwiki 의 안내 문서에 따르면 “Administrater Wiki > Content > Export” 에서 xar 파일로 내보낸 뒤 import 하면 된다고 나와 있던데 …

아뿔싸! standalone 에 추가로 뭘 설정(치)해야 되는지 Export 화면이 텅 비어 있어서 아무 것도 할 수 없었다 ㅜㅠ

작성한 컨텐츠도 얼마 안되고 해서 단순 무식한 방법을 선택했다. 각 페이지 마다에서 제공하는 “Export as XAR” 기능 이용해서 페이지 단위로 export 한 후 “Administrater Wiki > Content > Import” 로 이전.

그럭저럭 등록사용자/그룹 정보 외에는 이전 완료.

기타

  • 기본 관리자 계정은 Admin/admin (대소문자 구분하니 주의)
  • Admin 로긴 정보를 잊어 먹거나 했을 때는 xwiki.cfg  에서 “xwiki.superadminpassword=” 부분을 활성화 시킨 후 superadmin 계정으로 로긴해서 처리하면 된다.
book

Jolt Awards 20th (2010 – 2011) – The Best Books 수상작

Jolt Awards: The Best Books 분야 수상작이 발표되었군요. http://drdobbs.com/joltawards/231500080?pgno=1

6권이 최종 후보에 올라왔고 이 중 Continuous Delivery 가 Excellence Award 를 수상했습니다.

  • Domain-Specific Languages, by Martin Fowler with Rebecca Parsons
  • The Art of Computer Programming, Volume 4A: Combinatorial Algorithms, Part 1, by Donald E. Knuth
  • The Joy of Clojure: Thinking the Clojure Way, by Michael Fogus and Chris Houser
  • Productivity Award #1: Seven Languages in Seven Weeks: A Pragmatic Guide to Learning Programming Languages, by Bruce A. Tate
  • Productivity Award #2: Mining the Social Web: Analyzing Data from Facebook, Twitter, LinkedIn, and Other Social Media Sites, by Matthew A. Russell
  • Excellence Award: Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation, by Jez Humble and David Farley
‘The Joy of Clojure’, ‘Seven Languages in Seven Weeks’ 이 두 권을 상반기에 좀 읽다 말았네요.
Design & Development

javascript array sort()

jQuery Recipes 책 보다가 sort() 문제 있길래 회사 동료들에게 잠깐 머리 식히는 퀴즈로 냈던거 정리해봤습니다.

숫자를 정렬하려고 하는데 다음과 같이 해서는 … 당연히 안됩니다. 어찌하면 될까요?

var members = [45, 10, 3, 22, 7];
members = members.sort();
console.log(members);

array.sort() 는 아스키 값으로 정렬하기 때문에 원하는 숫자 정렬 결과를 얻을 수 없습니다. 그래서 array.sort(compareFunction) 를 이용해서 처리를 해주어야 합니다 closure 를 이용해서 다음과 같이 하면 되겠죠.

var members = [45, 10, 3, 22, 7];
members = members.sort(function(a, b) {
    return a-b;
});

그런데 a-b 했을 때 내부적으로 어떻게 정렬 처리될까요?
console.log(a, ” – “, b, ” : “, a-b);
찍어본 결과로 대충 추정해보면 이렇게 진행될 것 같습니다.

45 – 10 : 10, 45
45 – 3 : 10, 3, 45
10 – 3 : 3, 10, 45
45 – 22 : 3, 10, 22, 45
10 – 22 : 3, 10, 22, 45
45 – 7 : 3, 10, 22, 7, 45
3 – 7 : 3, 10, 22, 7, 45
10 – 7 : 3, 7, 10, 22, 45