001    /*
002     * Copyright (c) 2009 The openGion Project.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013     * either express or implied. See the License for the specific language
014     * governing permissions and limitations under the License.
015     */
016    package org.opengion.fukurou.xml;
017    
018    import org.xml.sax.InputSource;
019    import org.xml.sax.SAXException;
020    import org.xml.sax.Attributes;
021    import org.xml.sax.helpers.DefaultHandler;
022    
023    import javax.xml.parsers.SAXParserFactory;
024    import javax.xml.parsers.SAXParser;
025    import javax.xml.parsers.ParserConfigurationException;
026    
027    import java.io.Reader;
028    import java.io.IOException;
029    import java.util.Map;
030    
031    /**
032     * こã?クラスは、拡張オラクル XDK形式ã?XMLファイルをå?ç�?�™るハンドラですã?
033     * オラクルXDK形式ã?XMLとは、下記ã?ような ROWSET をトãƒ??とする ROW の
034     * é›?�¾りで?‘レコードを表しã?各ROWには、カラãƒ?��をキーとするXMLになってã�?�¾すã?
035     *
036     *   <ROWSET>
037     *       <ROW num="1">
038     *           <カラãƒ?>値1</カラãƒ?>
039     *             ?¥?¥?¥
040     *           <カラãƒ?>値n</カラãƒ?>
041     *       </ROW>
042     *        ?¥?¥?¥
043     *       <ROW num="n">
044     *          ?¥?¥?¥
045     *       </ROW>
046     *   <ROWSET>
047     *
048     * こã?形式であれば、XDK(Oracle XML Developer's Kit)を利用すればã€?�ž常に簡単に
049     * ãƒ??タベã?スとXMLファイルとの交換が可能ですã?
050     * <a href="http://otn.oracle.co.jp/software/tech/xml/xdk/index.html" target="_blank" >
051     * XDK(Oracle XML Developer's Kit)</a>
052     *
053     * 拡張XDK形式とは、ROW 以外に、SQL処ç�?”¨タグ(EXEC_SQL)を持つ XML ファイルですã?
054     * またã?登録するãƒ??ブル(table)ã‚?ROWSETタグの属æ?æƒ??として付与することができますã?
055     * (大æ–?­—小文字に注æ„?
056     * これは、オラクルXDKで処ç�?�™るå?合ã?無視されますã?で、同様に扱ã�?�“とがå?来ますã?
057     * こã?、EXEC_SQL は、それそれã? XMLãƒ??タをデータベã?スに登録する際にã€?
058     * SQL処ç�?‚’自動的に流す為の、SQLæ–?‚’記載しますã?
059     * こã?処ç�??、イベント毎に実行される為、その配置é ??重要ですã?
060     * こã?タグは、è¤?•°記述することもå?来ますが、BODY部にはã€?¼‘つのSQLæ–??み記述しますã?
061     *
062     *   &lt;ROWSET tableName="XX" &gt;
063     *       &lt;EXEC_SQL&gt;                    æœ??に記載して、å?期å?ç�?ãƒ??タクリアç­?を実行させるã€?
064     *           delete from GEXX where YYYYY
065     *       &lt;/EXEC_SQL&gt;
066     *       &lt;MERGE_SQL&gt;                   こã?SQLæ–?�§ UPDATEして、結果が0件ならINSERTを行いますã?
067     *           update GEXX set AA=[AA] , BB=[BB] where CC=[CC]
068     *       &lt;/MERGE_SQL&gt;
069     *       &lt;ROW num="1"&gt;
070     *           &lt;カラãƒ?&gt;値1&lt;/カラãƒ?&gt;
071     *             ?¥?¥?¥
072     *           &lt;カラãƒ?&gt;値n&lt;/カラãƒ?&gt;
073     *       &lt;/ROW&gt;
074     *        ?¥?¥?¥
075     *       &lt;ROW num="n"&gt;
076     *          ?¥?¥?¥
077     *       &lt;/ROW&gt;
078     *       &lt;EXEC_SQL&gt;                    æœ?¾Œに記載してã€??目の設å®?整合æ?登録)を行うã€?
079     *           update GEXX set AA='XX' , BB='YY' where CC='ZZ'
080     *       &lt;/EXEC_SQL&gt;
081     *   &lt;ROWSET&gt;
082     *
083     * DefaultHandler クラスを拡張してã�?‚‹為ã€??常の処ç�?�¨同様に、使用できますã?
084     *
085     *      InputSource input = new InputSource( reader );
086     *      HybsXMLHandler hndler = new HybsXMLHandler();
087     *
088     *      SAXParserFactory f = SAXParserFactory.newInstance();
089     *      SAXParser parser = f.newSAXParser();
090     *      parser.parse( input,hndler );
091     *
092     * またã?上記ã?処ç�?��のもã?を簡略化したメソãƒ?ƒ‰:parse( Reader ) を持ってã�?‚‹ためã€?
093     * 通常そã?メソãƒ?ƒ‰を使用しますã?
094     *
095     * HybsXMLHandler には、TagElementListener をセãƒ?ƒˆすることができますã?
096     * これは、ROW 毎に å†?ƒ¨æƒ??ã‚?TagElement オブジェクト化しã?action( TagElement )
097     * が呼び出されますã?こã? Listener を介してã€?¼‘レコードずつ処ç�?�™ることã�?
098     * 可能ですã?
099     *
100     * @version  4.0
101     * @author   Kazuhiko Hasegawa
102     * @since    JDK5.0,
103     */
104    public class HybsXMLHandler extends DefaultHandler {
105            /** シスãƒ?ƒ 依存ã?改行記号をセãƒ?ƒˆしますã?     */
106            private static final String CR = System.getProperty("line.separator");
107    
108            /** こã?ハンドラのトップタグå�?       {@value}        */
109            public static final     String ROWSET           = "ROWSET";
110            /** こã?ハンドラで取り扱える ROWSETタグの属æ?         */
111            public static final     String ROWSET_TABLE = "tableName";
112    
113            /** こã?ハンドラで取り扱えるタグå�?   {@value}        */
114            public static final     String ROW                      = "ROW";
115            /** こã?ハンドラで取り扱える ROWタグの属æ?    {@value}        */
116            public static final     String ROW_NUM          = "num";
117            /** こã?ハンドラで取り扱えるタグå�?   {@value}        */
118            public static final     String EXEC_SQL         = "EXEC_SQL";
119            /** こã?ハンドラで取り扱えるタグå�?   {@value}        */
120            public static final     String MERGE_SQL        = "MERGE_SQL";
121    
122            private Map<String,String>        defaultMap      = null;
123            private TagElementListener listener     = null;
124            private TagElement              element         = null;
125            private String                  key                     = null;
126            private StringBuilder   body            = null;
127            private boolean                 bodyIn          = false;
128            private int                             level           = 0;
129    
130            /**
131             *      パã?ス処ç�?‚’行いますã?
132             *      通常のパã?ス処ç�??簡易メソãƒ?ƒ‰になってã�?�¾すã?
133             *
134             * @param reader パã?ス処ç�?”¨のReaderオブジェクãƒ?
135             */
136            public void parse( final Reader reader ) {
137                    try {
138                            SAXParserFactory fact = SAXParserFactory.newInstance();
139                            SAXParser parser = fact.newSAXParser();
140    
141                            InputSource input = new InputSource( reader );
142    
143                            try {
144                                    parser.parse( input,this );
145                            }
146                            catch( SAXException ex ) {
147                                    if( ! "END".equals( ex.getMessage() ) ) {
148                                            String errMsg = "XMLパã?スエラー key=" + key + CR
149                                                                    + "element=" + element + CR
150                                                                    + ex.getMessage() ;
151                                            if( body != null ) {
152                                                    errMsg = errMsg + CR + body.toString();
153                                            }
154                                            throw new RuntimeException( errMsg,ex );
155                                    }
156                            }
157                    }
158                    catch( ParserConfigurationException ex1 ) {
159                            String errMsg = "SAXParser のコンフィグレーションが構築できませんã€?
160                                                    + "key=" + key + CR + ex1.getMessage();
161                            throw new RuntimeException( errMsg,ex1 );
162                    }
163                    catch( SAXException ex2 ) {
164                            String errMsg = "SAXParser が構築できませんã€?
165                                                    + "key=" + key + CR + ex2.getMessage();
166                            throw new RuntimeException( errMsg,ex2 );
167                    }
168                    catch( IOException ex3 ) {
169                            String errMsg = "InputSource の読み取り時にエラーが発生しましたã€?
170                                                    + "key=" + key + CR + ex3.getMessage();
171                            throw new RuntimeException( errMsg,ex3 );
172                    }
173            }
174    
175            /**
176             * å†?ƒ¨に TagElementListener を登録しますã?
177             * これはã€?lt;ROW&gt; タグの endElement 処ç�?¯Žに呼び出されますã?
178             * つまりã?行データを取得é?度、TagElement オブジェクトを作æ?しã?
179             * こã? TagElementListener の action( TagElement ) メソãƒ?ƒ‰を呼び出しますã?
180             * 何もセãƒ?ƒˆしなã�??またã?、null がセãƒ?ƒˆされたå?合ã?、何もしませんã€?
181             *
182             * @param listener TagElementListenerオブジェクãƒ?
183             */
184            public void setTagElementListener( final TagElementListener listener ) {
185                    this.listener = listener;
186            }
187    
188            /**
189             * TagElement オブジェクトを作æ?する時ã? 初期カラãƒ?値を設定しますã?
190             * TagElements オブジェクトã?、XMLファイルより作æ?する為ã€??目(カラãƒ?ã‚?
191             * XMLファイルのROW属æ?に持ってã�?‚‹é ?›®と値で作æ?されますã?
192             * こã?カラãƒ?��をã?外部から初期設定することが可能ですã?
193             * そã?場合ã?ここで登録したカラãƒ??(Mapに、LinkedHashMap を使用した場å�?
194             * が保持されますã?またã?ROW属æ?に存在しなã�?‚«ラãƒ?�Œあれば、å?とともに
195             * 初期値として設定しておくことが可能ですã?
196             * なおã?ここでのMapは、直接設定してã�?�¾すã?で、ご注意くã�?�•ã�??
197             *
198             * @param       map     初期カラãƒ??ãƒ??
199             */
200            public void setDefaultMap( final Map<String,String> map ) {
201                    defaultMap = map;
202            }
203    
204            /**
205             * 要ç´??のæ–?­—データの通知を受け取りますã?
206             * インタフェース ContentHandler å†?? characters メソãƒ?ƒ‰をオーバã?ライドしてã�?�¾すã?
207             * å�?–‡字データチャンクに対して特殊なアクション (ノã?ドまたã?バッファへのãƒ??タの追åŠ??
208             * ãƒ??タのファイルへの出力など) を実行することができますã?
209             *
210             * @param       buffer  æ–?­—データ配å?
211             * @param       start   配å?å†??開始位置
212             * @param       length  配å?から読み取られるæ–?­—数
213             * @see org.xml.sax.helpers.DefaultHandler#characters(char[] , int , int )
214             */
215            @Override
216            public void characters( final char[] buffer, final int start, final int length ) throws SAXException {
217                    if( ! ROW.equals( key ) && ! ROWSET.equals( key ) && length > 0 ) {
218                            body.append( buffer,start,length );
219                            bodyIn = true;
220                    }
221            }
222    
223            /**
224             * 要ç´??開始é?知を受け取りますã?
225             * インタフェース ContentHandler å†?? startElement メソãƒ?ƒ‰をオーバã?ライドしてã�?�¾すã?
226             * パã?サは XML æ–?›¸å†??å�?¦�ç´??前でこã?メソãƒ?ƒ‰を呼び出しますã?
227             * å�?startElement イベントには対応すã‚?endElement イベントがありますã?
228             * これは、要ç´?�Œ空である場合も変わりません。対応すã‚?endElement イベントã?前にã€?
229             * 要ç´??コンãƒ?ƒ³ãƒ??部がé?番に報告されますã?
230             * ここでは、タグがレベル?“以上ã?場合ã?、上位タグのå†?®¹として取り扱ã�?�¾すã?よってã€?
231             * タグに名前空間が定義されてã�?‚‹場合ã?そã?属æ?は削除しますã?
232             *
233             * @param       namespace       名前空é–??µ?²?©
234             * @param       localName       前置修飾子を含まなã�?ƒ­ーカル名ã?名前空間å?ç�?�Œ行われなã�??合ã?空æ–?­—å?
235             * @param       qname           前置修飾子を持つ修飾名ã?修飾名を使用できなã�??合ã?空æ–?­—å?
236             * @param       attributes      要ç´?�«付加された属æ?。属æ?が存在しなã�??合ã?空の Attributesオブジェクãƒ?
237             * @see org.xml.sax.helpers.DefaultHandler#startElement(String , String , String , Attributes )
238             */
239            @Override
240            public void startElement(final String namespace, final String localName,
241                                                             final String qname, final Attributes attributes) throws SAXException {
242                    if( ROWSET.equals( qname ) ) {
243                            if( listener != null ) {
244                                    element = new TagElement( ROWSET,defaultMap );
245                                    element.put( ROWSET_TABLE,attributes.getValue( ROWSET_TABLE ) );
246                                    listener.actionInit( element );
247                            }
248                            element = null;
249                    }
250                    else if( ROW.equals( qname ) ) {
251                            element = new TagElement( ROW,defaultMap );
252                            String num = attributes.getValue( ROW_NUM );
253                            element.setRowNo( num );
254                    }
255                    else if( EXEC_SQL.equals( qname ) ) {
256                            element = new TagElement( EXEC_SQL );
257                    }
258                    else if( MERGE_SQL.equals( qname ) ) {
259                            element = new TagElement( MERGE_SQL );
260                    }
261    
262                    if( level <= 2 ) {
263                            key = qname;
264                            body = new StringBuilder();
265                    }
266                    else {
267                            // レベル??以上ã?タグは上位タグのå†?®¹として扱ã�?�¾すã?
268                            body.append( "<" ).append( qname );
269                            int len = attributes.getLength();
270                            for( int i=0; i<len; i++ ) {
271                                    // 名前空間ã?宣è¨??、削除しておきますã?あくまでãƒ??タとして取り扱ã�?‚ºですã?
272                                    String attr = attributes.getQName(i);
273                                    if( ! attr.startsWith( "xmlns:" ) ) {
274                                            body.append( " " );
275                                            body.append( attr ).append( "=\"" );
276                                            body.append( attributes.getValue(i) ).append( "\"" );
277                                    }
278                            }
279                            body.append( ">" );
280                    }
281    
282                    bodyIn = false;         // 入れ子状のタグのBODY部の有無
283                    level ++ ;
284            }
285    
286            /**
287             * 要ç´??終äº??知を受け取りますã?
288             * インタフェース ContentHandler å†?? endElement メソãƒ?ƒ‰をオーバã?ライドしてã�?�¾すã?
289             * SAX パã?サは、XML æ–?›¸å†??å�?¦�ç´??終わりにこã?メソãƒ?ƒ‰を呼び出しますã?
290             * å�?endElement イベントには対応すã‚?startElement イベントがありますã?
291             * これは、要ç´?�Œ空である場合も変わりませんã€?
292             *
293             * @param       namespace       名前空é–?URI
294             * @param       localName       前置修飾子を含まなã�?ƒ­ーカル名ã?名前空間å?ç�?�Œ行われなã�??合ã?空æ–?­—å?
295             * @param       qname   前置修飾子を持つ XML 1.0 修飾名ã?修飾名を使用できなã�??合ã?空æ–?­—å?
296             * @see org.xml.sax.helpers.DefaultHandler#endElement(String , String , String )
297             */
298            @Override
299            public void endElement(final String namespace, final String localName, final String qname) throws SAXException {
300                    level -- ;
301                    if( ROW.equals( qname ) ) {
302                            if( listener != null ) {
303                                    listener.actionRow( element );
304                            }
305                            element = null;
306                    }
307                    else if( EXEC_SQL.equals( qname ) ) {
308                            element.setBody( body.toString().trim() );
309                            if( listener != null ) {
310                                    listener.actionExecSQL( element );
311                            }
312                            element = null;
313                    }
314                    else if( MERGE_SQL.equals( qname ) ) {
315                            element.setBody( body.toString().trim() );
316                            if( listener != null ) {
317                                    listener.actionMergeSQL( element );
318                            }
319                            element = null;
320                    }
321                    else if( level <= 2 ) {
322                            if( element != null ) {
323                                    element.put( key , body.toString().trim() );
324                            }
325                    }
326                    else {
327                            if( bodyIn ) {
328                                    body.append( "</" ).append( qname ).append( ">" );
329                            }
330                            else {
331                                    body.insert( body.length()-1, " /" );           // タグのæœ?¾Œを " />" とするã€?
332                            }
333                    }
334            }
335    }