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 */
016package org.opengion.fukurou.db;
017
018import java.io.File;
019import java.net.URL;
020import java.util.ArrayList;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Locale;
024import java.util.Map;
025import java.util.Collections;
026
027import org.opengion.fukurou.util.StringUtil;
028import org.opengion.fukurou.util.FileUtil;
029import org.opengion.fukurou.util.HybsDateUtil;                          // 6.9.2.1 (2018/03/12)
030import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
031import org.opengion.fukurou.system.LogWriter;
032import org.opengion.fukurou.system.HybsConst;                           // 7.2.3.1 (2020/04/17)
033import org.opengion.fukurou.xml.DomParser;
034import org.w3c.dom.Document;
035import org.w3c.dom.Element;
036import org.w3c.dom.Node;
037import org.w3c.dom.NodeList;
038import org.w3c.dom.NamedNodeMap;                                                        // 6.9.2.0 (2018/03/05)
039
040import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
041
042/**
043 * DB設定XMLの内容をJAXBを利用してロードする
044 * Driverをロードする
045 * 上記2つの機能を備えたクラスです
046 *
047 * 外部からはgetDbidメソッドを利用してDB設定(ExpandedDbid型)を取得します。
048 * DB設定情報が無い場合にXMLを読みにいきます。
049 * このDBIDを決めるキーは、内部取り込み字に、大文字変換されますので、大文字・
050 * 小文字の区別はありません。
051 *
052 * @og.rev 4.0.0.0 (2007/10/25) 新規作成
053 * @og.rev 5.1.7.0 (2010/06/01) org.opengion.fukurou.xml.jaxb.dbid 関係 廃止
054 * @og.group 初期化
055 *
056 * @version  4.0
057 * @author 高橋正和
058 * @since   JDK6.0,
059 */
060// 8.5.5.1 (2024/02/29) spotbugs CT_CONSTRUCTOR_THROW(コンストラクタで、Excweptionを出さない) class を final にすれば、警告は消える。
061// public class DatabaseConfig {
062public final class DatabaseConfig {
063
064        /** fukurou内で完結させるため、HybsDataからは読み込まずにここに書く */
065        private static final String DEFAULT_DRIVER       = "oracle.jdbc.OracleDriver";
066
067        // 6.4.3.3 (2016/03/04) 初期 DBConfig.xml ファイルの設定。
068        /** 初期ファイルの設定 {@value} */
069        public static final String DB_CONFIG_FILE = "../DBConfig.xml" ;
070
071        /** XMLファイル関連 */
072        private final String xmlFilename;                               // 5.7.2.2 (2014/01/24)
073
074        /** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。 */
075        private final Map<String, EDbid> dbidMap = Collections.synchronizedMap( new LinkedHashMap<>() );        // 5.6.7.0 (2013/07/27)
076        /** 6.4.3.1 (2016/02/12) Collections.synchronizedList で同期処理を行います。 */
077        private final List<String>    driverList = Collections.synchronizedList( new ArrayList<>() );
078
079        /** 5.6.7.0 (2013/07/27) プルダウンメニュー用の情報の、キャッシュ用変数。 */
080        private String codeKeyVal ;                                             // 初めて要求されたときに、セットします。
081
082        /** 5.6.6.0 (2013/07/05) 表題(title)属性を追加 */
083        private static final String[] DBID_INFO_KEYS
084                                = { "dbidKey", "title", "url", "user", "password", "readonly"
085                                        , "mincount", "maxcount", "pooltime", "applicationInfo","property" };
086
087        /* DBDRIVERのキーのを管理します。5.1.9.0 (2010/08/01) */
088        private static final String DBDRIVER_CLASS_KEY = "class";
089
090        /**
091         * 初期値を使ってXMLを読み込む
092         * xmlFilenameの初期値は../DBConfig.xml
093         *
094         * @og.rev 4.3.1.1 (2008/08/23) 自分のコンストラクターを呼ぶように修正
095         */
096        public DatabaseConfig() {
097                this( DB_CONFIG_FILE );
098        }
099
100        /**
101         * XMLファイルの名前を指定して読み込む
102         *
103         * @og.rev 5.1.9.0 (2010/08/01) クラスローダー外からでもDBConfig.xmlを取得できるようにする
104         * @og.rev 5.6.7.0 (2013/07/27) オブジェクト作成時に初期化も行っておきます。
105         * @og.rev 5.6.8.2 (2013/09/20) Tomcat8で、クラスローダーが変更されているのでその対応
106         * @og.rev 5.7.2.2 (2014/01/24) WEB-INF/classes フォルダがないと、xmlURL がnull になる対応。
107         * @og.rev 5.7.2.3 (2014/01/31) ファイルの存在チェックを追加します。
108         * @og.rev 6.4.3.3 (2016/03/04) 初期 DBConfig.xml ファイルの設定。
109         * @og.rev 6.6.0.0 (2016/12/01) コンテキストパスから、##バージョン番号を取り去った値を返すようにします。
110         * @og.rev 6.8.5.1 (2018/01/15) ClassLoader変数名を、clsl から、loader に変更(他の変数名と整合性を持たす)。
111         *
112         * @param       infile  XMLファイルの名前
113         */
114        public DatabaseConfig( final String infile ) {
115                final String xmlfile = infile == null || infile.isEmpty() ? DB_CONFIG_FILE : infile ;           // 引数が無い場合の初期設定を行います。
116                String fileName = null;
117
118                // 6.3.9.1 (2015/11/27) In J2EE, getClassLoader() might not work as expected.  Use Thread.currentThread().getContextClassLoader() instead.(PMD)
119                final ClassLoader loader        = Thread.currentThread().getContextClassLoader();
120                URL xmlURL                                      = loader.getResource( xmlfile );
121
122                // 5.6.8.2 (2013/09/20) Tomcat8で、xmlURL が取得できなくなっている・・・ようだ。
123                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
124                if( xmlURL == null ) {
125                        xmlURL = loader.getResource( "/" );             // クラスパスのベースURL
126                        // 5.7.2.2 (2014/01/24) Tomcat7で、WEB-INF/classes フォルダがないと、xmlURL がnull になる。
127                        if( xmlURL != null ) {
128                                final File temp = new File( xmlURL.getPath() , xmlfile );
129                                if( temp.exists() ) { fileName = temp.getAbsolutePath(); }
130                        }
131                }
132                else {
133                        fileName = xmlURL.getFile().replaceAll( "%23%23","##" );                // 6.6.0.0 (2016/12/01)
134                        // 5.7.2.3 (2014/01/31) ファイルの存在チェックを追加します。
135                        if( ! new File( fileName ).exists() ) { fileName = null; }
136                }
137
138                // 5.1.9.0 (2010/08/01)  クラスローダー外からでもDBConfig.xmlを取得できるようにする
139                if( fileName == null && new File( xmlfile ).exists() ) {
140                        fileName = xmlfile;
141                }
142
143                if( fileName == null ) {
144                        // 5.5.7.2 (2012/10/09) コメント追加
145                        final String errMsg = "DBConfig.xmlが見つかりません。File=[" + xmlfile + "]\n"
146                                                                + " WEB-INF/classes フォルダがないと、相対パスで見つけることができません。" ;
147                        throw new OgRuntimeException( errMsg );
148                }
149
150                xmlFilename = fileName;
151
152                init();                 // 5.6.7.0 (2013/07/27)
153        }
154
155        /**
156         * dbidKeyをキーにしてExpandedDbid型でマップの内容を返す。
157         * 存在しない場合はNULLを返します。
158         * キーが無い場合に初期化を行う。
159         *
160         * @og.rev 4.0.0.1 (2007/12/04) EDbid#clone() 廃止
161         * @og.rev 5.6.7.0 (2013/07/27) synchronized メソッドにします。
162         * @og.rev 6.0.0.1 (2014/04/25) Collections.synchronizedMap を使用します。
163         *
164         * @param key XMLで登録したdbidKey
165         *
166         * @return EDbid型オブジェクト
167         */
168        public EDbid getDbid( final String key ) {
169                return dbidMap.get( key.toUpperCase( Locale.JAPAN ) ) ;
170        }
171
172        /**
173         * マップをクリアします。
174         * XMLファイルを再読み込みする場合に使用します。
175         *
176         * @og.rev 5.1.9.0 (2010/08/01) ドライバーのリストもクリアする。
177         * @og.rev 5.6.7.0 (2013/07/27) synchronized メソッドにします。
178         * @og.rev 6.0.0.1 (2014/04/25) Collections.synchronizedMap を使用します。
179         */
180        public void reload() {
181                dbidMap.clear();
182                driverList.clear();
183                init();
184        }
185
186        /**
187         * 初期化処理
188         *
189         * DB設定XMLファイル(DBConfig.xml)を読み込みます。
190         * このファイルから、ドライバーリストの取得、DBIDのオブジェクトマップの作成を
191         * 行います。
192         * EDbidオブジェクト は、環境変数で、共通の初期値を定義しておくことが可能です。
193         * 項目として、REALM_URL、REALM_NAME、REALM_PASSWORD が設定可能です。
194         *
195         * ドライバーリストの取得後、Class.forName で、ドライバの登録も行います。
196         *
197         * @og.rev 5.1.7.0 (2010/06/01) org.opengion.fukurou.xml.jaxb.dbid 関係 廃止
198         * @og.rev 5.6.7.0 (2013/07/27) dbidMap,driverList を書き込むのではなく、作成します。
199         * @og.rev 7.2.3.1 (2020/04/17) System.getenv → HybsConst.getenv 変更(サービス化対応)
200         */
201        private void init() {
202                final Document doc = DomParser.read( new File(xmlFilename) ) ;
203                final Element firstRoot = doc.getDocumentElement();
204
205                makeDriverList( firstRoot );                            // 5.6.7.0 (2013/07/27)
206
207                // 5.6.7.0 (2013/07/27) を、かけておきます。
208                synchronized ( this ) {
209                        for( final String dr : driverList ) {
210                                try {
211                                        Class.forName( dr );
212//                              } catch( final ClassNotFoundException ex ) {            // 7.0.0.1 (2018/10/09) LinkageErrorが発生したので、いっそすべてをcatchします。
213                                } catch( final Throwable th ) {
214                                        final String errMsg = "ドライバクラスが見つかりません。[" + dr + "]" ;
215                                        LogWriter.log( errMsg );
216                                        LogWriter.log( th );
217                                }
218                        }
219                }
220
221                final EDbid defDdbid = new EDbid();             // 初期値
222//              defDdbid.setUrl(                System.getenv( "REALM_URL" ) );
223//              defDdbid.setUser(               System.getenv( "REALM_NAME" ) );
224//              defDdbid.setPassword(   System.getenv( "REALM_PASSWORD" ) );
225                defDdbid.setUrl(                HybsConst.getenv( "REALM_URL"      ) ); // 7.2.3.1 (2020/04/17)
226                defDdbid.setUser(               HybsConst.getenv( "REALM_NAME"     ) ); // 7.2.3.1 (2020/04/17)
227                defDdbid.setPassword(   HybsConst.getenv( "REALM_PASSWORD" ) ); // 7.2.3.1 (2020/04/17)
228
229                makeDbidMap( firstRoot,defDdbid );                              // 5.6.7.0 (2013/07/27)
230        }
231
232        /**
233         * ドライバーリストを取得します。
234         *
235         * DB設定XMLファイル(DBConfig.xml)の、class タグを取り込みます。
236         * このファイルから、ドライバーリストを取得します。
237         *
238         * 内部的に3段階の処理が実行されます。
239         * 第1Step:DBConfig.xml から、ドライバーリストを取得
240         * 第2Step:ドライバーリストが存在しない場合、環境変数の REALM_DRIVER からドライバーを取得
241         * 第3Step:それでも存在しない場合、このクラスの DEFAULT_DRIVER 定数 からドライバーを取得
242         *
243         * @og.rev 5.1.7.0 (2010/06/01) org.opengion.fukurou.xml.jaxb.dbid 関係 廃止
244         * @og.rev 5.1.9.0 (2010/08/01) ドライバ一覧のListをオブジェクト変数化
245         * @og.rev 5.6.7.0 (2013/07/27) driverList を書き込むのではなく、作成します。
246         * @og.rev 5.6.7.0 (2013/07/27) synchronized メソッドにします。
247         * @og.rev 7.2.3.1 (2020/04/17) System.getenv → HybsConst.getenv 変更(サービス化対応)
248         *
249         * @param       element DB設定XMLファイルのElementオブジェクト
250         */
251        private void makeDriverList( final Element element ) {
252
253                final NodeList list = element.getElementsByTagName( "class" ) ;
254                final int num = list.getLength();
255                for( int i=0; i<num; i++ ) {
256                        final Element cls = (Element)list.item(i);
257                        driverList.add( cls.getTextContent() );
258                }
259
260                if( driverList.isEmpty() ) {
261//                      final String realmDriver = System.getenv( "REALM_DRIVER" );
262                        final String realmDriver = HybsConst.getenv( "REALM_DRIVER" );  // 7.2.3.1 (2020/04/17)
263                        if( realmDriver != null && realmDriver.length() > 0 ) {
264                                driverList.add( realmDriver );
265                        }
266                }
267
268                if( driverList.isEmpty() ) { driverList.add( DEFAULT_DRIVER ); }
269        }
270
271        /**
272         * EDbidオブジェクトのマップを取得します。
273         *
274         * DB設定XMLファイル(DBConfig.xml)の、dbid タグを取り込みます。
275         * このファイルから、EDbidオブジェクトの属性情報を取得し、オブジェクトを構築します。
276         *
277         * EDbidオブジェクト は、初期値をコピーして、作成していきます。
278         * EDbidオブジェクトをマップから取り出すキーとなる、dbidKey は、大文字化して設定します。
279         *
280         * 7.0.5.0 (2019/09/09)
281         *  begin,end,step,var,items 属性に対して、stepを年月指定できるように、type属性を追加しました。
282         *  type="month" で、begin,end を、年月として処理します。
283         *
284         * @og.rev 5.1.7.0 (2010/06/01) org.opengion.fukurou.xml.jaxb.dbid 関係 廃止
285         * @og.rev 5.1.9.0 (2010/08/01) Mapを返すように変更
286         * @og.rev 5.5.2.0 (2012/05/01) property追加
287         * @og.rev 5.6.6.0 (2013/07/05) 表題(title)属性の取得
288         * @og.rev 5.6.7.0 (2013/07/27) 内部MapをDBConfig.xmlの読み込み順に変更。
289         * @og.rev 5.6.7.0 (2013/07/27) dbidMap を書き込むのではなく、作成します。
290         * @og.rev 5.6.7.0 (2013/07/27) synchronized メソッドにします。
291         * @og.rev 5.6.7.1 (2013/08/09) DEFAULT と、RESOURCE の DBIDキーがなければ、内部的に作成します。
292         * @og.rev 5.6.8.0 (2013/09/06) RESOURCE の DBIDキーを内部作成時に、title も設定します。
293         * @og.rev 6.4.3.4 (2016/03/11) computeIfAbsent と、新しい clone(String) メソッドを使用する。
294         * @og.rev 6.8.2.4 (2017/11/20) 環境変数( ${env.XXXX} ) を処理できるようにします。
295         * @og.rev 6.9.2.0 (2018/03/05) dbidに、begin,end,step,var,items 属性を追加します(影響は、ForEachObject内)。
296         * @og.rev 7.0.5.0 (2019/09/09) dbidに、type 属性を追加します(影響は、ForEachObject内)。
297         *
298         * @param  element DB設定XMLファイルのElementオブジェクト
299         * @param  defDdbid 初期情報の設定された、EDbidオブジェクト
300         */
301        private void makeDbidMap( final Element element , final EDbid defDdbid ) {
302                final NodeList list = element.getElementsByTagName( "dbid" ) ;
303                final int num = list.getLength();
304                for( int i=0; i<num; i++ ) {
305                        final Element ele = (Element)list.item(i);
306                        final ForEachObject forEach = new ForEachObject( ele );
307
308                        while( forEach.hasNext() ) {
309                                final NodeList childs   = ele.getChildNodes();
310                                final int      numChild = childs.getLength();
311                                final EDbid    dbid     = defDdbid.clone();                             // 初期値をコピーして、作成
312                                for( int j=0; j<numChild; j++ ) {
313                                        final Node nd = childs.item(j);
314                                        if( nd.getNodeType() == Node.ELEMENT_NODE ) {
315                                                final Element el = (Element)nd;
316                                                final String tag = el.getTagName();
317//                                              // 6.8.2.4 (2017/11/20) 環境変数( ${env.XXXX} ) を処理できるようにします。
318//                                              final String txt = envText( el.getTextContent() );
319                                                // 6.9.2.0 (2018/03/05) 変数の置き換え( ${var} )と、環境変数( ${env.XXXX} ) を処理します。
320                                                final String txt = forEach.getText( el.getTextContent() );
321
322                                                // dbidKey は、toUpperCase して、大文字のみとする。
323                                                // 6.8.2.4 (2017/11/20) 環境変数( ${env.XXXX} ) を処理できるようにします。
324                                                if( "dbidKey".equals( tag ) )   {
325                                                        if( txt != null && txt.length() > 0 ) {
326                                                                dbid.setDbidKey( txt.toUpperCase( Locale.JAPAN ) );
327                                                        }
328                                                }
329                                                else if( "title".equals( tag ) )        { dbid.setTitle(        txt ); }                // 5.6.6.0 (2013/07/05) 表題(title)属性の取得
330                                                else if( "url".equals( tag ) )          { dbid.setUrl(          txt ); }
331                                                else if( "user".equals( tag ) )         { dbid.setUser(         txt ); }
332                                                else if( "password".equals( tag ) ) { dbid.setPassword( txt ); }
333                                                else if( "readonly".equals( tag ) ) { dbid.setReadonly( txt ); }
334                                                else if( "mincount".equals( tag ) ) { dbid.setMincount( txt ); }
335                                                else if( "maxcount".equals( tag ) ) { dbid.setMaxcount( txt ); }
336                                                else if( "pooltime".equals( tag ) ) { dbid.setPooltime( txt ); }
337                                                else if( "applicationInfo".equals( tag ) ) { dbid.setApplicationInfo( txt ); }
338                                                else if( "property".equals( tag ) ) { dbid.addProp(             txt ); } // 5.5.2.0 (2012/05/01)
339                                                else {
340                                                        System.err.println( "警告:dbid に新しい属性が、追加されています。" );
341                                                }
342                                        }
343                                }
344                                dbidMap.put( dbid.getDbidKey(), dbid );         // 5.6.7.0 (2013/07/27) 復活
345                        }
346                }
347
348                // 5.6.7.1 (2013/08/09) DEFAULT と、RESOURCE の DBIDキーがなければ、内部的に作成します。
349                // 6.4.3.4 (2016/03/11) computeIfAbsent と、新しい clone(String) メソッドを使用する。
350                // Map#computeIfAbsent : 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
351                dbidMap.computeIfAbsent( "DEFAULT", k -> defDdbid.clone( "DEFAULT" ) ); // DEFAULT が存在するか確認する。
352
353                // 6.4.3.4 (2016/03/11) computeIfAbsent と、新しい clone(String) メソッドを使用する。
354                // Map#computeIfAbsent : 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
355                dbidMap.computeIfAbsent( "RESOURCE", k -> defDdbid.clone( "RESOURCE" ) );       // RESOURCE が存在するか確認する。
356        }
357
358//      /**
359//       * 環境変数( ${env.XXX} )を含むテキストを処理します。
360//       *
361//       * 環境変数を含まない場合は、引数の orgTxt を返します。
362//       *
363//       * @og.rev 6.8.2.4 (2017/11/20) 環境変数( ${env.XXXX} ) を処理できるようにします。
364//       *
365//       * @param  orgTxt 環境変数( ${env.XXX} )を含む可能性のあるテキスト
366//       * @return  環境変数を処理したテキスト
367//       */
368//      private String envText( final String orgTxt ) {
369//              final int st = orgTxt.indexOf( "${env." );
370//              if( st >= 0 ) {
371//                      final int ed = orgTxt.indexOf( '}' , st );
372//                      if( ed >= 0 ) {
373//                              final String envKey = orgTxt.substring( st + "${env.".length() , ed );
374//                              final String envTxt = orgTxt.substring( 0,st ) + System.getenv( envKey ) + orgTxt.substring( ed+1 );
375//                              return envText( envTxt );                       // ループすべきだが、再帰処理で対応します。
376//                      }
377//                      else {
378//                              final String errMsg = xmlFilename + " の環境変数( ${env.XXX} )の整合性が取れていません。" + orgTxt ;
379//                              throw new OgRuntimeException( errMsg );
380//                      }
381//              }
382//
383//              return orgTxt;
384//      }
385
386        /**
387         * dbidの属性処理が、煩雑になってきたので、内部クラスにまとめます。
388         *
389         * begin,end,step,var,items 属性で、ループ処理を行うための設定を取得します。
390         * items が未指定の場合は、begin,end,step による、数字(文字列)の配列を返します。
391         * itemsが、定義されている場合は、カンマで分割後、文字列の配列を返します。
392         * それぞれ、初期値があり、begin=0 , end=0 or items要素数,step=1,
393         *
394         * @og.rev 7.0.5.0 (2019/09/09) type属性を追加。
395         *  begin,end,step,var,items 属性に対して、stepを年月指定できるように、type属性を追加しました。
396         *  type="month" で、begin,end を、年月として処理します。
397         *
398         * @og.rev 6.9.2.1 (2018/03/12) dbidに、begin,end,step,var,items 属性を追加します。
399         * @og.rev 7.0.5.0 (2019/09/09) dbidに、type 属性を追加します。
400         */
401        private static final class ForEachObject {
402                private final String   attVar ;                         // var 属性の値を、${xxxx} に置き換えた文字列。未定義は、null
403                private final String[] items ;                          // items 属性のCSV分割か、begin,end,step の数字文字列の配列
404
405                private int   idx      = -1;                            // hasNext() で、先にアップするので、一つ引いておきます。
406
407                /**
408                 * Elementオブジェクトを指定して、インスタンスを生成するコンストラクタです。
409                 *
410                 * @og.rev 6.9.2.1 (2018/03/12) dbidに、begin,end,step,var,items 属性を追加します。
411                 * @og.rev 7.0.5.0 (2019/09/09) dbidに、type 属性を追加します。
412                 *
413                 * @param  ele DB設定XMLファイルのElementオブジェクト
414                 */
415                public ForEachObject( final Element ele ) {
416                        // 6.9.2.0 (2018/03/05) dbidに、begin,end,step,var 属性を追加します。
417                        if( ele.hasAttributes() ) {
418                                final NamedNodeMap attNode = ele.getAttributes();
419//                              final int attBgn = StringUtil.nval( getAttri( attNode,"begin" ),0 );    // 7.0.5.0 (2019/09/09) 削除
420//                              final int attStp = StringUtil.nval( getAttri( attNode,"step"  ),1 );    // 7.0.5.0 (2019/09/09) (ifの中へ)移動
421                                final String tmpVar = getAttri( attNode,"var" );
422                                attVar = StringUtil.isNull( tmpVar ) ? null : ( "${" + tmpVar + "}" ) ; // 置換元定数を作成します。
423
424//                              final String endStr = getAttri( attNode,"end" );                                                // 7.0.5.0 (2019/09/09) (ifの中へ)移動
425                                final String itms   = getAttri( attNode,"items" );
426                                if( StringUtil.isNull( itms ) ) {
427                                        final String bgiStr = getAttri( attNode,"begin" );                                      // 7.0.5.0 (2019/09/09) 追加
428                                        final String endStr = getAttri( attNode,"end" );                                        // 7.0.5.0 (2019/09/09) (ifの中へ)移動
429                                        final int attStp = StringUtil.nval( getAttri( attNode,"step"  ),1 );
430
431                                        final String typStr = getAttri( attNode,"type" );                                       // 7.0.5.0 (2019/09/09) 新規追加
432                                        if( "month".equalsIgnoreCase( typStr ) ) {                                                      // 7.0.5.0 (2019/09/09) 新規追加
433                                                items = HybsDateUtil.stepYM( bgiStr,endStr,attStp );
434                                        }
435                                        else {
436                                                final int attBgn = StringUtil.nval( bgiStr,0 );                                 // 7.0.5.0 (2019/09/09) 文字列化をint化
437
438                                                final int attEnd = StringUtil.nval( endStr,attBgn+1 );                  // とりあえず 1回は回すため。
439                                                final List<String> list = new ArrayList<>();
440                                                for( int i=attBgn; i<attEnd; i+=attStp ) {
441                                                        list.add( String.valueOf( i ) );
442                                                }
443
444//                                              items = list.toArray( new String[list.size()] ) ;
445                                                items = list.toArray( new String[0] ) ; // 8.5.4.2 (2024/01/12) PMD 7.0.0 OptimizableToArrayCall 対応
446                                        }
447                                }
448                                else {
449                                        items = itms.split( "," );
450                                }
451                        }
452                        else {
453                                attVar = null;
454                                items  = new String[] { "0" };                  // 最低1回は回せるように。
455                        }
456                }
457
458                /**
459                 * 反復処理でさらに要素がある場合にtrueを返します。
460                 *
461                 * この判定で、内部カウンタを、プラスしていますので、
462                 * ループ判定時にのみ、呼び出してください。
463                 *
464                 * @og.rev 6.9.2.1 (2018/03/12) 新規追加
465                 *
466                 * @return 反復処理でさらに要素がある場合はtrue
467                 */
468                public boolean hasNext() { return ++idx < items.length; }
469
470                /**
471                 * 引数の文字列に対して、var 属性と item要素の置換を行います。
472                 *
473                 * さらに、${env.XXXX} 環境変数の置換と、日付文字列{&#064;DATE.XXXX}の置換も
474                 * 行います。
475                 * この処理では、内部カウンタはアップしませんので、inTxt を替えて、何度でも
476                 * 呼び出すことが可能です。
477                 *
478                 * @og.rev 6.9.2.1 (2018/03/12) 新規追加
479                 * @og.rev 7.2.5.0 (2020/06/01) ${env.XXXX} 環境変数 は、{&#064;ENV.XXXX} も対応します。
480                 *
481                 * @param  inTxt 置換元の文字列
482                 * @return items属性に対応して置換された、文字列
483                 */
484                public String getText( final String inTxt ) {
485                        String rtnTxt = StringUtil.replace( inTxt , attVar , items[idx] );              // attVar が null の場合、inTxt が戻される。
486                        rtnTxt = StringUtil.replaceText( rtnTxt , "${env."  , "}" , System::getenv );                           // 環境変数置換
487                        rtnTxt = StringUtil.replaceText( rtnTxt , "{@ENV."  , "}" , System::getenv );                           // 7.2.5.0 (2020/06/01) 環境変数置換
488                        rtnTxt = StringUtil.replaceText( rtnTxt , "{@DATE." , "}" , HybsDateUtil::getDateFormat );      // 日付文字列置換
489
490                        return rtnTxt;
491                }
492
493                /**
494                 * 属性を取得する簡易メソッドです。
495                 *
496                 * 引数に、ノードのコレクションと、属性のキーワードを指定することで、属性値を返します。
497                 *
498                 * @og.rev 6.9.2.0 (2018/03/05) 新規追加
499                 * @og.rev 7.2.5.0 (2020/06/01) ${env.XXXX} 環境変数 は、{&#064;ENV.XXXX} も対応します。
500                 *
501                 * @param  nodeMap 名前を指定してアクセスできるノードのコレクション
502                 * @param  key    属性のキーワード
503                 * @return 属性値(キーに対応する属性が無い場合は、null)
504                 */
505                private String getAttri( final NamedNodeMap nodeMap,final String key ) {
506                        final Node attNode = nodeMap.getNamedItem( key );
507
508//                      return attNode == null ? null : attNode.getNodeValue() ;
509
510                        String rtnTxt = attNode == null ? null : attNode.getNodeValue() ;
511
512                        rtnTxt = StringUtil.replaceText( rtnTxt , "${env."  , "}" , System::getenv );                           // 環境変数置換
513                        rtnTxt = StringUtil.replaceText( rtnTxt , "{@ENV."  , "}" , System::getenv );                           // 7.2.5.0 (2020/06/01) 環境変数置換
514                        rtnTxt = StringUtil.replaceText( rtnTxt , "{@DATE." , "}" , HybsDateUtil::getDateFormat );      // 日付文字列置換
515
516                        return rtnTxt;
517                }
518        }
519
520        /* ------------------------------------------------------------------------------------
521         *
522         * 以下は、DBConfig.xml編集用のメソッドです。
523         * 編集用のメソッドでは、オブジェクト化されたDBID及びDBDRIVERの情報は使用せずに、
524         * DBConfig.xmlからその情報を再度読み出して、SET/GETしています。
525         * (オブジェクトとして依存しているのは、DBConfig.xmlのファイル名のみです)
526         *
527         * -------------------------------------------------------------------------------------
528         */
529        /**
530         * DBIDとして管理している項目のキーの一覧を配列形式で返します。
531         *
532         * @og.rev 5.1.9.0 (2010/08/01) 新規作成
533         *
534         * @return 項目のキー一覧
535         * @og.rtnNotNull
536         */
537        public static String[] getDbidInfoKeys() {
538                return DBID_INFO_KEYS.clone();
539        }
540
541        /**
542         * 全てのDBIDの属性情報のリスト(配列)で返します。
543         *
544         * 値の順番については、{@link #getDbidInfoKeys()}で返されるキーの一覧と同じです。
545         *
546         * @og.rev 5.1.9.0 (2010/08/01) 新規作成
547         * @og.rev 5.5.2.1 (2012/05/07) propertiesを出力
548         * @og.rev 5.6.6.0 (2013/07/05) 表題(title)属性を追加
549         * @og.rev 5.6.7.0 (2013/07/27) 内部MapをDBConfig.xmlの読み込み順に変更。
550         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
551         *
552         * @return 全てのDBIDの属性情報のリスト(配列)
553         * @og.rtnNotNull
554         * @see #getDbidInfoKeys()
555         */
556        public String[][] getDbidInfo() {
557                // 6.3.9.0 (2015/11/06) 色々やりたいが、今はsynchronizedブロックにするだけにします。
558                synchronized( dbidMap ) {
559                        final String[][] dbidInfo = new String[dbidMap.size()][DBID_INFO_KEYS.length];          // 8.5.4.2 (2024/01/12) PMD 7.0.0 LocalVariableCouldBeFinal
560                        int idx = 0;
561                        for( final EDbid dbid : dbidMap.values() ) {
562                                dbidInfo[idx][0] = dbid.getDbidKey();
563                                dbidInfo[idx][1] = dbid.getTitle();                             // 5.6.6.0 (2013/07/05) 表題(title)属性を追加
564                                dbidInfo[idx][2] = dbid.getUrl();
565                                dbidInfo[idx][3] = dbid.getUser();
566                                dbidInfo[idx][4] = dbid.getPassword();
567                                dbidInfo[idx][5] = String.valueOf( dbid.isReadonly() );
568                                dbidInfo[idx][6] = String.valueOf( dbid.getMincount() );
569                                dbidInfo[idx][7] = String.valueOf( dbid.getMaxcount() );
570                                dbidInfo[idx][8] = String.valueOf( dbid.getPooltime() );
571                                dbidInfo[idx][9] = String.valueOf( dbid.isApplicationInfo() );
572                                dbidInfo[idx][10]= String.valueOf( dbid.getProps().toString() ); // 5.5.2.1 (2012/05/07)
573                                idx++;
574                        }
575
576                        return dbidInfo;
577                }
578        }
579
580        /**
581         * 全てのDBIDの属性情報のリスト(配列)をセットします。
582         *
583         * このメソッドを呼び出すと、DBConfig.xmlで定義されているDBID情報一覧を"一旦削除し"、
584         * その上で、引数のDBID情報一覧をDBConfig.xmlに書き込みます。
585         *
586         * 値の順番については、{@link #getDbidInfoKeys()}で返されるキーの一覧と同じです。
587         *
588         * 書き込みの直前に、同じフォルダにタイムスタンプを付加したバックアップファイルを作成します。
589         *
590         * @og.rev 5.1.9.0 (2010/08/01) 新規作成
591         * @og.rev 5.6.7.0 (2013/07/27) 内部MapをDBConfig.xmlの読み込み順に変更。
592         *
593         * @param dbidVals 全てのDBIDの属性情報の配列の配列
594         * @see #getDbidInfoKeys()
595         */
596        public void setDbidInfo( final String[][] dbidVals ) {
597                FileUtil.copy( xmlFilename, xmlFilename + "_" + System.currentTimeMillis() );
598
599                final Document doc = DomParser.read( new File(xmlFilename) ) ;
600                final Element firstRoot = doc.getDocumentElement();
601                deleteChildElements( firstRoot, "dbid" );
602
603                if( dbidVals != null && dbidVals.length > 0 ) {
604                        // 5.6.7.0 (2013/07/27) 内部MapをDBConfig.xmlの読み込み順に変更(なので、廃止)。
605                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
606//                      for( int i=0; i<dbidVals.length; i++ ) {
607                        for( final String[] vals : dbidVals ) {
608                                final Element newEle = doc.createElement( "dbid" );
609//                              for( int j=0; j<dbidVals[i].length; j++ ) {
610                                for( int j=0; j<vals.length; j++ ) {
611                                        final Element newChEle = doc.createElement( DBID_INFO_KEYS[j] );
612//                                      newChEle.setTextContent( dbidVals[i][j] );
613                                        newChEle.setTextContent( vals[j] );
614                                        newEle.appendChild( newChEle );
615                                }
616                                firstRoot.appendChild( newEle );
617                                firstRoot.appendChild( doc.createTextNode( "\n\n" ) );
618                        }
619                }
620
621                DomParser.write( new File(xmlFilename), doc );
622
623                reload();               // 5.6.7.0 (2013/07/27) DBIDの属性情報のリストを更新後、初期化します。
624        }
625
626        /**
627         * DBドライバーの属性キーを返します。
628         *
629         * @og.rev 5.1.9.0 (2010/08/01) 新規作成
630         *
631         * @return      DBドライバーの属性キー
632         * @og.rtnNotNull
633         */
634        public static String getDriverKey() {
635                return DBDRIVER_CLASS_KEY;
636        }
637
638        /**
639         * DBドライバーのリスト(配列)を返します。
640         *
641         * @og.rev 5.1.9.0 (2010/08/01) 新規作成
642         * @og.rev 5.6.7.0 (2013/07/27) driverList を書き込むのではなく、作成します。
643         *
644         * @return      DBドライバーリスト(配列)
645         * @og.rtnNotNull
646         */
647        public String[] getDriverList() {
648//              return driverList.toArray( new String[driverList.size()] );
649                return driverList.toArray( new String[0] );     // 8.5.4.2 (2024/01/12) PMD 7.0.0 OptimizableToArrayCall 対応
650        }
651
652        /**
653         * DBドライバーのリスト(配列)をセットします。
654         *
655         * このメソッドを呼び出すと、DBConfig.xmlで定義されているclass一覧を"一旦削除し"、
656         * その上で、引数のDBドライバー一覧をDBConfig.xmlに書き込みます。
657         *
658         * 書き込みの直前に、同じフォルダにタイムスタンプを付加したバックアップファイルを作成します。
659         *
660         * @og.rev 5.1.9.0 (2010/08/01) 新規作成
661         * @og.rev 5.6.7.0 (2013/07/27) DBドライバーのリストを更新後、初期化します。
662         *
663         * @param drivers DBドライバーの配列(可変長引数)
664         */
665        public void setDriverList( final String... drivers ) {
666                FileUtil.copy( xmlFilename, xmlFilename + "_" + System.currentTimeMillis() );
667
668                final Document doc = DomParser.read( new File(xmlFilename) );
669                final Element firstRoot = doc.getDocumentElement();
670
671                final Element parent = (Element)firstRoot.getElementsByTagName( "dbDriver" ).item( 0 );
672                deleteChildElements( parent, "class" );
673
674                // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
675                if( drivers != null && drivers.length > 0 ) {
676                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
677//                      for( int i=0; i<drivers.length; i++ ) {
678                        for( final String driver : drivers ) {
679                                final Element newEle = doc.createElement( "class" );
680//                              newEle.setTextContent( drivers[i] );
681                                newEle.setTextContent( driver );
682                                parent.appendChild( newEle );
683                        }
684                }
685
686                DomParser.write( new File(xmlFilename), doc );
687
688                reload();               // 5.6.7.0 (2013/07/27) DBドライバーのリストを更新後、初期化します。
689        }
690
691        /**
692         * DBID情報のキーとタイトルから、プルダウンメニューを作成するための情報を取得します。
693         *
694         * このメソッドを呼び出すと、DBConfig.xmlで定義されている dbidKey と、title 属性から、
695         * 「key1:val1 key2:val2 ・・・」という文字列を作成します。
696         * これを利用すれば、プルダウンメニューが簡単に作成できます。
697         *
698         * @og.rev 5.6.7.0 (2013/07/27) プルダウンメニュー用の情報を作成します。
699         * @og.rev 5.6.7.1 (2013/08/09) 表題(title)属性のスペース対策
700         * @og.rev 6.2.6.0 (2015/06/19) 表題(title)属性のスペース対策(KEY:LBL をダブルクオートで囲う)
701         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
702         * @og.rev 7.1.0.0 (2020/01/20) DB接続できないDBIDはプルダウンメニューに含めない。
703         * @og.rev 7.2.3.1 (2020/04/17) 超特殊処理:Derby の場合は、エラーメッセージを出さない。
704         *
705         * @return プルダウンメニューを作成するための情報
706         */
707        public String getCodeKeyVal() {
708                if( codeKeyVal == null ) {
709                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
710                        // 6.3.9.0 (2015/11/06) 色々やりたいが、今はsynchronizedブロックにするだけにします。
711                        synchronized( dbidMap ) {
712                                for( final EDbid dbid : dbidMap.values() ) {
713                                        final String key = dbid.getDbidKey();
714                                        final String dbName = dbid.getDbProductName();          // 7.2.3.1 (2020/04/17)
715                                        try {                                                                                           // 7.1.0.0 (2020/01/20)
716//                                              if( dbid.getDbProductName() == null ) {
717                                                if( dbName == null ) {
718                                                        ConnectionFactory.getDBName( key );                     // 7.1.0.0 (2020/01/20) DB接続テスト
719                                                }
720
721                                                final String lbl = StringUtil.nval( dbid.getTitle() , key );
722                                                // 6.0.2.5 (2014/10/31) char を append する。
723                                                if( lbl.indexOf( ' ' ) >= 0 ) {                                 // 5.6.7.1 (2013/08/09) スペース対策
724                                                        buf.append( '"' ).append( key ).append( ':' ).append( lbl ).append( '"' );
725                                                }
726                                                else {
727                                                        buf.append( key ).append( ':' ).append( lbl );
728                                                }
729                                                buf.append( ' ' );
730                                        }
731        //                              catch( final OgRuntimeException ex ) {                          // 7.1.0.0 (2020/01/20) エラー時
732                                        catch( final RuntimeException ex ) {                            // 7.1.0.0 (2020/01/20) エラー時
733                                                // 7.2.3.1 (2020/04/17) 超特殊処理:Derby の場合は、エラーメッセージを出さない。
734                                                // ProductやdbNameはnullなので、URLから判定します。
735                                                final String dbUrl = dbid.getUrl();
736                                                // 7.2.9.5 (2020/11/28)
737                                                if( dbUrl == null || !dbUrl.contains( "derby" ) ) {
738                                                        System.err.println( "警告:" + dbUrl + "\t\n" +  ex.getMessage() );
739                                                }
740
741//                                              if( dbUrl != null && dbUrl.contains( "derby" ) ) {
742//              //                                      final String errMsg = "DBID=[" + key + "] 接続できません" ;            // エラーメッセージの簡素化
743//              //                                      System.err.println( errMsg );
744//                                              }
745//                                              else {
746//                                                      System.err.println( "警告:" + dbUrl + "\t\n" +  ex.getMessage() );
747//                                              }
748                                        }
749                                }
750                        }
751
752                        buf.setLength( buf.length()-1 );                // 最後のスペースを削除
753                        codeKeyVal = buf.toString();
754                }
755
756                return codeKeyVal;
757        }
758
759        /**
760         * 親要素を基点として、引数で指定されたタグ名を持つ子要素を削除します。
761         *
762         * @og.rev 5.6.7.0 (2013/07/27) staticメソッド を イスタンスメソッドに変更
763         *
764         * @param parent 親要素
765         * @param childTagName 削除する子要素のタグ名
766         */
767        private void deleteChildElements( final Element parent, final String childTagName ) {
768                Node child = parent.getFirstChild();
769                boolean isDel = false;
770                while( child != null ) {
771                        // エレメント間の改行Cも削除するため、次の異なる要素が来るまでは削除し続けます。
772                        if( child.getNodeType() == Node.ELEMENT_NODE ) {
773                                // 6.4.4.1 (2016/03/18)
774                                isDel = ((Element)child).getTagName().equalsIgnoreCase( childTagName );
775                        }
776
777                        final Node next = child.getNextSibling();
778                        if( isDel ) {
779                                parent.removeChild( child );
780                        }
781                        child = next;
782                }
783        }
784}