IT_Programming/XML

XML Query Manager만들어보기

JJun ™ 2006. 1. 31. 10:07


먼저 왜 이런것이 만들어지게 되었는지부터 살펴보도록 하자.
XML이 점차 대두되고 EJB1.1 spec부터 deployment descriptor가 XML문서로 작성이 되기 시작하면서
EJB Container에 대한 환경들이 server start시에 동적으로 읽어들일 수 있는 환경과 환경이
바뀌더라도 실제 소스코드는 바뀌지 않으며 text conf파일의 변경으로 작동이 될 수 있도록
하는 경향으로 바뀌었다.

이것이 무엇이던가? 기억이 나는가? 바로 loosely coupling이다. 객체와 객체간의 연결관계를
커다란 개념에서 바라보지 못한다면 그것은 tightly하게 연결될 것이고, 수정이 또한 프로그램의
소스변경까지 일으키게 하는 유연성없는 구조가 되어버릴것이다.

Spider WAF에도 적용되어 있다고 이야기했다. 여기서는 실제 프로젝트진행시에 사용되는 쿼리는
프로그램의 Data Access Object안에 코딩하여 넣는 것이 아니라 engine이 start되어지는 시점에
쿼리를 startup servlet를 통하여 읽어 들인 후 해당 쿼리를 DOM Object로, 다시 DOM Object를
자바의 우리가 쉽게 해석해낼 수 있는 객체 형태로 읽어내어 처리하도록 하였다.

하지만 이번 아티클의 코드에서는 직접 DOM Document를 조사하여 해당 쿼리들을 insert, update,
delete, select하는 예를 들겠다.

그렇다면 지금 가장 먼저 준비해야 할 것이 무엇인가?
XML을 쓴다고 했으니 파서가 당연히 필요할 것이고, 자바쪽의 객체가 필요하다 했으니 JDK도
필수일것이다.
여러분들이 만약 JDK1.4이상의 버젼을 가지고 있다면 파서가 내장되어 있기 때문에 필요가 없지만
낮은 버젼의 JDK라면 Xerces파서나 JAXP파서를 다운받아 설치하도록 하자.

설치방법을 모르는가? 아래의 아티클을 읽어보기 바란다.

http://www.javapattern.info/viewArticle.do?articleId=1047437421002

자. 또 무엇이 필요한가? 엔진어쩌구 떠들었으니 엔진이 필요한 것인가? 그것은 아니다.
선택사항일 뿐이다. 예제에서는 놀새의 주특기인 일반 애플리케이션으로 웹을 흉내내볼 것이다.

프로그램을 작성할 준비가 모두 되었는가? 그렇다면 다시 한 번 물어보겠다.
무엇부터 작성할 것인가?

이것은 여러분들이 나름대로 생각하여도 된다. 꼭 이것이 정답이다 라는 것은 없기 때문이다.

우선  시나리오를 설명하겠다.

1. 프로젝트팀이 코드상에 데이터베이스쿼리를 넣지 않도록 한다.
2. 쿼리를 저장한 저장소로는 file형태의 xml document를 사용한다.
XML의 구조는 루트원소로 Database명이 오게 되며 자식으로 table명, 쿼리들이 오도록 한다.
3. 빈번한 IO발생을 줄이기 위하여 DOM Tree를 메모리에 로드하여 사용한다.
4. XML처리에 필요한 utility성 클래스를 작성한다.




위의 2번에서 XML도큐먼트를 사용한다 하였으므로 XML에 대한 정의를 내려보겠다.
먼저 DTD부터 작성하여야 하겠지만 생략하도록 하고 직접 예제 샘플을 올려보도록 하겠다.

<?xml version="1.0" encoding="MS949"?>

<oracle>
  <sample>
    <insert>INSERT INTO poll VALUES (seq_poll_id.NEXTVAL, ?, ?, ?, ?, ?, ?, ?, 0)</insert>
    <update> UPDATE poll SET poll_count = poll_count + 1 WHERE poll_id = ? </update>
    <delete />
    <select>select * from carouser</select>
  </sample>
</oracle>

위의 경우처럼 정의를 하도록 하자.
루트원소로 oracle이 정의되어 있으며, sample이란 테이블에 CRUD쿼리가 정의되어 있도록 한다.

자, 이후에는 어떠한 클래스를 작성해야 하는 것일까?
위의 4번에서 이야기한 utility클래스를 작성해야 할것인데, 거기에 들어갈 메소드들로는
xml에서 query를 얻어오는 메소드, xml document를 조작(node insert, update, delete)하는 메소드들이
있어야 할 것이다.

보통 일반적인 환경설정의 xml같은 경우는 하위노드 3단계 이하로는 진행되는 성향이 없다.
왜냐하면 Node의 depth가 깊어지면 깊어질수록 실제 사람이 눈으로 쉽게 판독하기가
어려워지기 때문이다. 그래서 보통 유틸리티성 클래스를 작성하게 되면 3단계의 node까지
조작할 수 있는 프로그램을 많이 짜게 되는데 아래의 XmlDAO같은 경우도 그런 3단계 depth에 대한
처리를 주로하는 메소드들로 정의되어 있다.


/**
* QueryManger.java
* Written date : 2002/05/10
* Author : Choi ji woong
*/
package com.carouser.spider.manager;

import org.xml.sax.InputSource;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.apache.crimson.tree.XmlDocument;

import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXException;
import java.io.Reader;
import java.net.URL;
import java.util.Vector;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
// jaxp 1.0.1 imports
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
public class XmlDAO {

/**
*  해당 URI를 입력받아 xml의 내용을 InputSource에 입력으로 넣어 DOM파서를 이용하여 파싱한다.
* @param location 해당 xml의 위치
* @return Element
*/
    public static Element loadDocument(File location) {
        Document doc = null;
        try {
          
            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder parser = docBuilderFactory.newDocumentBuilder();
            doc = parser.parse(location);
            Element root = doc.getDocumentElement();
            root.normalize();
        /*
            //Output to standard output ; use Sun's reference imple for now
            XmlDocument xdoc = (XmlDocument) doc;
            xdoc.write(new OutputStreamWriter(System.out));
        */
            return root;
        } catch (SAXParseException err) {
            System.err.println ("URLMappingsXmlDAO ** Parsing error" + ", line " +
                        err.getLineNumber () + ", uri " + err.getSystemId ());
            System.err.println("URLMappingsXmlDAO error: " + err.getMessage ());
        } catch (SAXException e) {
            System.err.println("URLMappingsXmlDAO error: " + e);
        } catch (java.net.MalformedURLException mfx) {
            System.err.println("URLMappingsXmlDAO error: " + mfx);
        } catch (java.io.IOException e) {
            System.err.println("URLMappingsXmlDAO error: " + e);
        } catch (Exception pce) {
            System.err.println("URLMappingsXmlDAO error: " + pce);
        }
        return null;
    }
/**
*  해당 URI를 입력받아 xml의 내용을 InputSource에 입력으로 넣어 DOM파서를 이용하여 파싱한다.
*  @param location 해당 xml의 위치
*  @return Element
*/
    public static Element loadDocument(String location) {
        Document doc = null;
        try {
           URL url = new URL(location);
           InputSource xmlInp = new InputSource(url.openStream());

            DocumentBuilderFactory docBuilderFactory =
                DocumentBuilderFactory.newInstance();
            DocumentBuilder parser = docBuilderFactory.newDocumentBuilder();
            doc = parser.parse(xmlInp);
            Element root = doc.getDocumentElement();
            root.normalize();
            return root;
        } catch (SAXParseException err) {
            System.err.println ("URLMappingsXmlDAO ** Parsing error" + ", line " +
                        err.getLineNumber () + ", uri " + err.getSystemId ());
            System.err.println("URLMappingsXmlDAO error: " + err.getMessage ());
        } catch (SAXException e) {
            System.err.println("URLMappingsXmlDAO error: " + e);
        } catch (java.net.MalformedURLException mfx) {
            System.err.println("URLMappingsXmlDAO error: " + mfx);
        } catch (java.io.IOException e) {
            System.err.println("URLMappingsXmlDAO error: " + e);
        } catch (Exception pce) {
            System.err.println("URLMappingsXmlDAO error: " + pce);
        }
        return null;
    }

/**
*  해당 URI를 입력받아 xml의 내용을 InputSource에 입력으로 넣어 DOM파서를 이용하여 파싱한다.
*  @param target java.io.Reader
*  @return Element
*/
    public static Element loadDocument(Reader target) {
        Document doc = null;
        try {
            InputSource xmlInp = new InputSource(target);

            DocumentBuilderFactory docBuilderFactory =
                DocumentBuilderFactory.newInstance();
            DocumentBuilder parser = docBuilderFactory.newDocumentBuilder();
            doc = parser.parse(xmlInp);
            Element root = doc.getDocumentElement();
            root.normalize();
            return root;
        } catch (SAXParseException err) {
            System.err.println ("URLMappingsXmlDAO ** Parsing error" + ", line " +
                        err.getLineNumber () + ", uri " + err.getSystemId ());
            System.err.println("URLMappingsXmlDAO error: " + err.getMessage ());
        } catch (SAXException e) {
            System.err.println("URLMappingsXmlDAO error: " + e);
        } catch (java.net.MalformedURLException mfx) {
            System.err.println("URLMappingsXmlDAO error: " + mfx);
        } catch (java.io.IOException e) {
            System.err.println("URLMappingsXmlDAO error: " + e);
        } catch (Exception pce) {
            System.err.println("URLMappingsXmlDAO error: " + pce);
        }
        return null;
    }

/**
* Element와 찾을 태그를 입력받아 그 태그의 text node를 return
* @param root root element
* @param tagName 찾고자하는 태그명
* @return String
*/
    public static String getTagValue(Element root, String tagName) {
        String returnString = "";
        NodeList list = root.getElementsByTagName(tagName);
        for (int loop = 0; loop < list.getLength(); loop++) {
            Node node = list.item(loop);
            if (node != null) {
                Node child = node.getFirstChild();
                if ((child != null) && child.getNodeValue() != null)
                    return child.getNodeValue();
            }
        }
        return returnString;
    }


/**
* Element와 찾을 태그를 입력받아 그 태그의 text value를 return
* @param root root element
* @param tagName 찾고자하는 태그명
* @param subTagName 3단계 하위태그명
* @return String
*/
    public static String getSubTagValue(
        Element root, String tagName, String subTagName) {
        String returnString = "";
        NodeList list = root.getElementsByTagName(tagName);
        for (int loop = 0; loop < list.getLength(); loop++) {
            Node node = list.item(loop);
            if (node != null) {
                NodeList  children = node.getChildNodes();
                for (int innerLoop =0; innerLoop < children.getLength(); innerLoop++) {
                    Node  child = children.item(innerLoop);
                    if ((child != null) && (child.getNodeName() != null)
                        && child.getNodeName().equals(subTagName) ) {
                        Node grandChild = child.getFirstChild();
                        if (grandChild.getNodeValue() != null) return grandChild.getNodeValue();
                    }
                } // end inner loop
            }
        }
        return returnString;
    }
/**
* Element와 찾을 태그를 입력받아 그 태그의 attribute를 return
* @param root root element
* @param tagName 찾고자하는 태그명
* @param subTagName 3단계 하위태그명
* @param attribute 찾고자하는 애트리뷰트명
* @return String
*/
    public static String getSubTagAttribute(
        Element root, String tagName, String subTagName, String attribute) {
        String returnString = "";
        NodeList list = root.getElementsByTagName(tagName);
        for (int loop = 0; loop < list.getLength(); loop++) {
            Node node = list.item(loop);
            if (node != null) {
                NodeList  children = node.getChildNodes();
                for (int innerLoop =0; innerLoop < children.getLength(); innerLoop++) {
                    Node  child = children.item(innerLoop);
                    if ((child != null) && (child.getNodeName() != null)
                        && child.getNodeName().equals(subTagName) ) {
                        if (child instanceof Element) {
                            return ((Element)child).getAttribute(attribute);
                        }
                    }
                } // end inner loop
            }
        }
        return returnString;
    }

    private static String getSubTagValue(Node node, String subTagName) {
        String returnString = "";
        if (node != null) {
            NodeList  children = node.getChildNodes();
            for (int innerLoop =0; innerLoop < children.getLength(); innerLoop++) {
                Node  child = children.item(innerLoop);
                if ((child != null) && (child.getNodeName() != null)
                    && child.getNodeName().equals(subTagName) ) {
                    Node grandChild = child.getFirstChild();
                    if (grandChild.getNodeValue() != null) return grandChild.getNodeValue();
                }
            } // end inner loop
        }
        return returnString;
    }

/**
* 노드의 위치부분에 해당하는 테이블명과 쿼리타입과 값을 삽입한다.
* @param root root element
* @param table 테이블 xml태그
* @param queryType insert, update, delete등의 쿼리타입
*/

    public static void nodeSubInsert(
        Element root, String table, String queryType, String queryValue){
        Node find =  getTag(root, table);
        Document doc = find.getOwnerDocument();

        Element type = doc.createElement(queryType);

        Text value = doc.createTextNode(queryValue);

        type.appendChild(value);
        find.appendChild(type);

        print(doc);
       
    }
/**
* 노드의 위치부분에 해당하는 테이블명과 쿼리타입과 값을 갱신한다.
* @param root root element
* @param table 테이블 xml태그
* @param queryType insert, update, delete등의 쿼리타입
*/
    public static void nodeSubUpdate(
        Element root, String table, String queryType, String queryValue) {
        Node find =  getTag(root, table);
        Document doc = find.getOwnerDocument();
       
        NodeList nl = find.getChildNodes();
        int numnodes = nl.getLength();

        System.out.println("length : " + numnodes);

        Node replNode = null;

        for(int i = 0 ; i < numnodes ; i++) {
            Node n = nl.item(i);
            String name = n.getNodeName();

            if(name.equals(queryType)) {
                replNode = n;
                System.out.println("Finding Node : " + replNode.getNodeName());

                break;
            }
        }
       
        Element type = doc.createElement(queryType);
        Text value = doc.createTextNode(queryValue);

        type.appendChild(value);

        find.replaceChild(type, replNode);

        print(doc);
    }
/**
* 노드의 위치부분에 해당하는 테이블명과 쿼리타입과 값을 삭제한다.
* @param root root element
* @param table 테이블 xml태그
* @param queryType insert, update, delete등의 쿼리타입
*/
    public static void nodeSubDelete(
        Element root, String table, String queryType) {
        Node find =  getTag(root, table);
        Document doc = find.getOwnerDocument();
       
        NodeList nl = find.getChildNodes();
        int numnodes = nl.getLength();

        System.out.println("length : " + numnodes);

        Node deleteNode = null;

        for(int i = 0 ; i < numnodes ; i++) {
            Node n = nl.item(i);
            String name = n.getNodeName();

            if(name.equals(queryType)) {
                deleteNode = n;
                System.out.println("Finding Node : " + deleteNode.getNodeName());

                break;
            }
        }
       
        find.removeChild(deleteNode);

        print(doc);
    }

    // carouser util append

    public static Node getTag(Element root, String tagName) {
        NodeList list = root.getElementsByTagName(tagName);
        for (int loop = 0; loop < list.getLength(); loop++) {
            Node node = list.item(loop);
            System.out.println("nodeName : " +
                node.getNodeName() + ":::::" + node.getNodeType());
            if (node.getNodeName().equals(tagName)) {
                return node;
            }
        }
        return null;
    }
/**
* 화면상에 XML Document를 출력한다.
*/
    private static void print(Document doc) {
        try{
            XmlDocument xdoc = (XmlDocument) doc;
            xdoc.write(new OutputStreamWriter(System.out));
        }catch(Exception e) {}
    }
/**
* 파일에 XML Document를 출력한다.
*/
    public static void fileSave(Element root, String location) {
        System.out.println("root : " + root.getClass().getName());
        Document doc = (org.w3c.dom.Document) root.getOwnerDocument();
        System.out.println("doc : " + doc.getClass().getName());
        try{
            File f = new File(location);
            FileOutputStream fos = new FileOutputStream(f);
            XmlDocument xdoc = (XmlDocument) doc;
            xdoc.write(new OutputStreamWriter(fos));
        }catch(Exception e) {}
    }

}



위에서는 일반적으로 XML Document를 traverse하며 조작하는 전형적인 utility클래스를 작성했다.
기본적인 API를 여러분들이 잘 알고 있다면 코드를 이해하는데 그리 어려움이 없을 것이다.

범용적인 메소드로는
    loadDocument()
    getTagValue()
    getSubTagValue()
    getSubTagAttribute()
    getTag()
등이 사용될 수 있으며

이번 시나리오에서 독창적으로 사용되는 메소드는 위의 메소드를 제외한 전부이다.

위의 print()메소드와 fileSave()메소드는 crimson패키지가 있어야 가능하지만 다르게 코딩을 한다면
xml transformer를 이용하여 출력하는 방법을 더 권장하도록 하겠다.

xalan패키지를 이용한 transformer는 출력을 아래와같이 할 수 있다.


// xalan.jar
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import javax.xml.transform.dom.*;

public static void write(Element root) {
    try{
        Document doc = root.getOwnerDocument();
        TransformerFactory tFactory = TransformerFactory.newInstance();
        Transformer transformer = tFactory.newTransformer();
        transformer.setOutputProperty("encoding", "euc-kr");
        //transformer.transform(new DOMSource(doc),
        new StreamResult(new OutputStreamWriter(System.out, "euc-kr")));
        transformer.transform(new DOMSource(doc),
        new StreamResult(new FileWriter("oracle.xml")));
    }catch(Exception e) {
        e.printStackTrace();
    }
}


자, 그러면 이제 쿼리를 manage하는 클래스를 작성해보도록 하자.


package com.carouser.spider.manager;


import java.util.Hashtable;
import java.io.FileInputStream;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import java.io.File;

public class QueryManager {
    private static QueryManager manager;
    private static Element queryRoot;
    private Hashtable xmlList;
    private static boolean alive = false;
    private static String location = "oracle.xml";

    static {
        getInstance();
    }

    private QueryManager() {}

    public static String getQuery(String table, String element){
        checkInstance();
        return XmlDAO.getSubTagValue(queryRoot, table, element);
       
    }
    public static void insert(String table, String queryType, String queryValue) {
        checkInstance();
        XmlDAO.nodeSubInsert(queryRoot, table, queryType, queryValue);
    }
    public static void update(String table, String queryType, String queryValue) {
        checkInstance();
        XmlDAO.nodeSubUpdate(queryRoot, table, queryType, queryValue);
    }
    public static void delete(String table, String queryType) {
        checkInstance();
        XmlDAO.nodeSubDelete(queryRoot, table, queryType);
    }
    public static void fileSave() {
        checkInstance();
        XmlDAO.fileSave(queryRoot, location);
    }

    private static void checkInstance() {
        if( !alive ) getInstance();
    }
    private static void getInstance() {
        if( manager == null ) {
            synchronized(QueryManager.class) {
                manager = new QueryManager();
                alive = true;
            }
        }
        queryRoot = XmlDAO.loadDocument(new File(location));
    }
}

위의 코드에서는 테스트하기 위하여 별다른 것이 없다. 실제 메모리상에
xml문서가 로드되었는지를 체크하여 select할것인지 아니면 insert, update, delete할것인지를
결정하여 XmlDAO 유틸리티 클래스의 메소드를 이용하여 실제 xml을 조작하는 call을 할 뿐이다.

간단한 코드라 충분히 이해할 것이라 생각한다.

이제 테스트 클래스를 작성해보도록 한다.


import com.carouser.spider.manager.QueryManager;

public class  ManagerTest{
    public static void main(String[] args) {
        String select = QueryManager.getQuery("sample", "select");
        System.out.println("Select Query : " + select);

        //insert sample
        //QueryManager.insert("sample", "result", "none");

        // update sample
        QueryManager.update("sample", "select", "select * from carouser");

        // delete sample
        //QueryManager.delete("sample", "select");

        // save file
        QueryManager.fileSave();
    }
}

위의 경우에서는 모든 경우에 대하여 테스트하고 있다. 여러분들이 주석을
지워서 처리하면 될것이다.

insert(), update(), delete(), getQuery(), fileSave()등을 테스트해볼 수 있으며,
이러한 방법을 이용한다면 QueryAdmin같은 메뉴를 웹상에서 만들어 테스트해볼 수 있을 것이다.

XML이란게 SAX의 이벤트와 DOM의 Tree를 이해하고 조작할 수 있다면 처리하는 것은
굉장히 쉬운일이며 단지 어렵다는 것이 이런것들을 확장한 웹서비스라든지 프로토콜에 대한
것이 있을 수 있는데 그것은 XML자체가 아니라 specific한 형태의 개념을 사용하기 때문에
개념접근이 어려워서라고 볼 수 있다.

'IT_Programming > XML' 카테고리의 다른 글

자바가 바라보는 XML  (0) 2006.01.31
XMLBeans를 이용한 xml binding  (0) 2006.01.31
DOM 표준 인터페이스  (0) 2006.01.30
XML로 프레임 나누기  (0) 2006.01.05
XML 스크립트 사용하기  (0) 2006.01.05