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.process;
017
018import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
019import org.opengion.fukurou.util.Argument;
020
021import org.opengion.fukurou.util.StringUtil;
022import org.opengion.fukurou.util.HybsEntry ;
023import org.opengion.fukurou.system.LogWriter;
024
025import java.util.Hashtable;
026import java.util.List;
027import java.util.ArrayList;
028import java.util.Map ;
029import java.util.LinkedHashMap ;
030
031import javax.naming.Context;
032import javax.naming.NamingEnumeration;
033import javax.naming.NamingException;
034import javax.naming.directory.DirContext;
035import javax.naming.directory.InitialDirContext;
036import javax.naming.directory.SearchControls;
037import javax.naming.directory.SearchResult;
038import javax.naming.directory.Attribute;
039import javax.naming.directory.Attributes;
040
041/**
042 * Process_LDAPReaderは、LDAPから読み取った内容を、LineModel に設定後、
043 * 下流に渡す、FirstProcess インターフェースの実装クラスです。
044 *
045 * LDAPから読み取った内容より、LineModelを作成し、下流(プロセスチェインは、
046 * チェインしているため、データは上流から下流へと渡されます。)に渡します。
047 *
048 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。
049 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に
050 * 繋げてください。
051 *
052 * @og.formSample
053 *  Process_LDAPReader -attrs=uid,cn,officeName,ou,mail,belongOUID -orderBy=uid -filter=(&(objectClass=person)(|(belongOUID=61200)(belongOUID=61100)))
054 *
055 *   [ -initctx=コンテキストファクトリ   ] :初期コンテキストファクトリ (初期値:com.sun.jndi.ldap.LdapCtxFactory)
056 *   [ -providerURL=サービスプロバイダリ ] :サービスプロバイダリ       (初期値:ldap://ldap.opengion.org:389)
057 *   [ -entrydn=取得元の名前             ] :属性の取得元のオブジェクトの名前 (初期値:cn=inquiry-sys,o=opengion,c=JP)
058 *   [ -password=取得元のパスワード      ] :属性の取得元のパスワード   (初期値:******)
059 *   [ -searchbase=コンテキストベース名  ] :検索するコンテキストのベース名 (初期値:soouid=employeeuser,o=opengion,c=JP)
060 *   [ -searchScope=検索範囲             ] :検索範囲。『OBJECT』『ONELEVEL』『SUBTREE』のどれか(初期値:SUBTREE)
061 *   [ -timeLimit=検索制限時間           ] :結果が返されるまでのミリ秒数。0 の場合無制限(初期値:0)
062 *   [ -attrs=属性の識別子               ] :エントリと一緒に返される属性の識別子。null の場合すべての属性
063 *   [ -columns=属性のカラム名           ] :属性の識別子に対する別名。識別子と同じ場合は『,』のみで区切る。
064 *   [ -maxRowCount=最大検索数           ] :最大検索数(初期値:0[無制限])
065 *   [ -match_XXXX=正規表現              ] :指定のカラムと正規表現で一致時のみ処理( -match_LANG=ABC=[a-zA-Z]* など。)
066 *   [ -filter=検索条件                  ] :検索する LDAP に指定する条件
067 *   [ -referral=REFERAL                 ] :ignore/follow/throw
068 *   [ -display=[false/true]             ] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
069 *   [ -debug=[false/true]               ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
070 *
071 * @version  4.0
072 * @author   Kazuhiko Hasegawa
073 * @since    JDK5.0,
074 */
075public class Process_LDAPReader extends AbstractProcess implements FirstProcess {
076        private static final String             INITCTX                 = "com.sun.jndi.ldap.LdapCtxFactory";
077        private static final String             PROVIDER                = "ldap://ldap.opengion.org:389";
078        private static final String             PASSWORD                = "password";
079        private static final String             SEARCH_BASE             = "soouid=employeeuser,o=opengion,c=JP";
080        private static final String             ENTRYDN                 = "cn=inquiry-sys,o=opengion,c=JP";     // 4.2.2.0 (2008/05/10)
081        private static final String             REFERRAL                = ""; // 5.6.7.0 (2013/07/27)
082
083        // 検索範囲。OBJECT_SCOPE、ONELEVEL_SCOPE、SUBTREE_SCOPE のどれか 1 つ
084        private static final String[]   SCOPE_LIST              = { "OBJECT","ONELEVEL","SUBTREE" };
085        private static final String             SEARCH_SCOPE    = "SUBTREE";
086
087        private static final long               COUNT_LIMIT             = 0;                    // 返すエントリの最大数。0 の場合、フィルタを満たすエントリをすべて返す
088        private static final boolean    RETURN_OBJ_FLAG = false;                // true の場合、エントリの名前にバインドされたオブジェクトを返す。false 場合、オブジェクトを返さない
089        private static final boolean    DEREF_LINK_FLAG = false;                // true の場合、検索中にリンクを間接参照する
090
091        private String                  filter                          ;               // "employeeNumber=87019";
092        private int                             timeLimit                       ;               // 結果が返されるまでのミリ秒数。0 の場合、無制限
093        private String[]                attrs                           ;               // エントリと一緒に返される属性の識別子。null の場合、すべての属性を返す。空の場合、属性を返さない
094        private String[]                columns                         ;               // 属性の識別子に対する、別名。識別子と同じ場合は、『,』のみで区切る。
095
096        private int                             executeCount            ;               // 検索/実行件数
097        private int                     maxRowCount                     ;               // 最大検索数(0は無制限)
098
099        // 3.8.0.9 (2005/10/17) 正規表現マッチ
100        private String[]                matchKey                        ;                       // 正規表現
101        private boolean                 display                         ;                       // false:表示しない
102        private boolean                 debug                           ;                       // 5.7.3.0 (2014/02/07) デバッグ情報
103
104        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
105        private static final Map<String,String> MUST_PROPARTY   ;               // [プロパティ]必須チェック用 Map
106        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
107        private static final Map<String,String> USABLE_PROPARTY ;               // [プロパティ]整合性チェック Map
108
109        static {
110                MUST_PROPARTY = new LinkedHashMap<>();
111                MUST_PROPARTY.put( "filter",    "検索条件(必須) 例: (&(objectClass=person)(|(belongOUID=61200)(belongOUID=61100)))" );
112
113                USABLE_PROPARTY = new LinkedHashMap<>();
114                USABLE_PROPARTY.put( "initctx",         "初期コンテキストファクトリ。" +
115                                                                                        CR + " (初期値:com.sun.jndi.ldap.LdapCtxFactory)" );
116                USABLE_PROPARTY.put( "providerURL",     "サービスプロバイダリ (初期値:ldap://ldap.opengion.org:389)" );
117                USABLE_PROPARTY.put( "entrydn",         "属性の取得元のオブジェクトの名前。" +
118                                                                                        CR + " (初期値:cn=inquiry-sys,o=opengion,c=JP)" );
119                USABLE_PROPARTY.put( "password",                "属性の取得元のパスワード(初期値:******)" );
120                USABLE_PROPARTY.put( "searchbase",      "検索するコンテキストのベース名。" +
121                                                                                        CR + " (初期値:soouid=employeeuser,o=opengion,c=JP)" );
122                USABLE_PROPARTY.put( "searchScope",     "検索範囲。『OBJECT』『ONELEVEL』『SUBTREE』のどれか。" +
123                                                                                        CR + " (初期値:SUBTREE)" );
124                USABLE_PROPARTY.put( "timeLimit",       "結果が返されるまでのミリ秒数。0 の場合無制限(初期値:0)" );
125                USABLE_PROPARTY.put( "attrs",           "エントリと一緒に返される属性の識別子。null の場合すべての属性" );
126                USABLE_PROPARTY.put( "columns",         "属性の識別子に対する別名。識別子と同じ場合は『,』のみで区切る。" );
127                USABLE_PROPARTY.put( "maxRowCount",     "最大検索数(0は無制限)  (初期値:0)" );
128                USABLE_PROPARTY.put( "match_",          "指定のカラムと正規表現で一致時のみ処理" +
129                                                                                        CR + " ( -match_LANG=ABC=[a-zA-Z]* など。)" );
130                USABLE_PROPARTY.put( "display",         "結果を標準出力に表示する(true)かしない(false)か" +
131                                                                                        CR + "(初期値:false:表示しない)" );
132                USABLE_PROPARTY.put( "debug",   "デバッグ情報を標準出力に表示する(true)かしない(false)か" +
133                                                                                        CR + "(初期値:false:表示しない)" );             // 5.7.3.0 (2014/02/07) デバッグ情報
134        }
135
136        private NamingEnumeration<SearchResult> nameEnum        ;                       // 4.3.3.6 (2008/11/15) Generics警告対応
137        private LineModel                                               newData         ;
138        private int                                                             count           ;
139
140        /**
141         * デフォルトコンストラクター。
142         * このクラスは、動的作成されます。デフォルトコンストラクターで、
143         * super クラスに対して、必要な初期化を行っておきます。
144         *
145         */
146        public Process_LDAPReader() {
147                super( "org.opengion.fukurou.process.Process_LDAPReader",MUST_PROPARTY,USABLE_PROPARTY );
148        }
149
150        /**
151         * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
152         * 初期処理(ファイルオープン、DBオープン等)に使用します。
153         *
154         * @og.rev 4.2.2.0 (2008/05/10) LDAP パスワード取得対応
155         * @og.rev 5.3.4.0 (2011/04/01) StringUtil.nval ではなく、getProparty の 初期値機能を使う
156         * @og.rev 5.6.7.0 (2013/07/27) REFERRAL対応
157         *
158         * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
159         */
160        public void init( final ParamProcess paramProcess ) {
161                final Argument arg = getArgument();
162
163                timeLimit       = arg.getProparty("timeLimit",timeLimit );                      // 5.3.4.0 (2011/04/01)
164                maxRowCount     = arg.getProparty("maxRowCount",maxRowCount );          // 5.3.4.0 (2011/04/01)
165                display         = arg.getProparty("display",display);
166                debug           = arg.getProparty("debug",debug);                               // 5.7.3.0 (2014/02/07) デバッグ情報
167
168                // 属性配列を取得。なければゼロ配列
169                attrs           = StringUtil.csv2Array( arg.getProparty("attrs") );
170                if( attrs.length == 0 ) { attrs = null; }
171
172                // 別名定義配列を取得。なければ属性配列をセット
173                columns         = StringUtil.csv2Array( arg.getProparty("columns") );
174                if( columns.length == 0 ) { columns = attrs; }
175
176                // 属性配列が存在し、属性定義数と別名配列数が異なればエラー
177                // 以降は、attrs == null か、属性定義数と別名配列数が同じはず。
178                if( attrs != null && attrs.length != columns.length ) {
179                        final String errMsg = "attrs と columns で指定の引数の数が異なります。" +
180                                                " attrs=[" + arg.getProparty("attrs") + "] , columns=[" +
181                                                arg.getProparty("columns") + "]" ;
182                        throw new OgRuntimeException( errMsg );
183                }
184
185                // 3.8.0.9 (2005/10/17) 正規表現マッチ
186                final HybsEntry[] entry = arg.getEntrys( "match_" );
187                final int len = entry.length;
188                matchKey        = new String[columns.length];           // 正規表現
189                for( int clm=0; clm<columns.length; clm++ ) {
190                        matchKey[clm] = null;   // 判定チェック有無の初期化
191                        for( int i=0; i<len; i++ ) {
192                                if( columns[clm].equalsIgnoreCase( entry[i].getKey() ) ) {
193                                        matchKey[clm] = entry[i].getValue();
194                                }
195                        }
196                }
197
198                filter = arg.getProparty( "filter" ,filter );
199
200                final Hashtable<String,String> env = new Hashtable<>();
201                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
202                final String initctx = arg.getProparty( "initctx " ,INITCTX );
203                env.put(Context.INITIAL_CONTEXT_FACTORY, initctx);
204                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
205                final String providerURL = arg.getProparty( "providerURL" ,PROVIDER );
206                env.put(Context.PROVIDER_URL, providerURL);
207                // 3.7.1.1 (2005/05/31)
208                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
209                final String password = arg.getProparty( "password" ,PASSWORD );
210        //      if( password != null && password.length() > 0 ) {
211                        env.put(Context.SECURITY_CREDENTIALS, password);
212        //      }
213
214                // 4.2.2.0 (2008/05/10) entrydn 属性の追加
215                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
216                final String entrydn = arg.getProparty( "entrydn" ,ENTRYDN );   // 4.2.2.0 (2008/05/10)
217        //      if( entrydn != null && entrydn.length() > 0 ) {
218                        env.put(Context.SECURITY_PRINCIPAL  , entrydn);
219        //      }
220
221                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
222                final String referral = arg.getProparty("referral",REFERRAL);  // 5.6.7.0 (2013/07/27)
223                env.put( Context.REFERRAL, referral ); // 5.6.7.0 (2013/07/27)
224
225                try {
226                        final DirContext ctx = new InitialDirContext(env);
227                        // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
228                        final String searchScope = arg.getProparty( "searchScope" ,SEARCH_SCOPE , SCOPE_LIST );
229                        final SearchControls constraints = new SearchControls(
230                                                                        changeScopeString( searchScope ),
231                                                                        COUNT_LIMIT                     ,
232                                                                        timeLimit                       ,
233                                                                        attrs                           ,
234                                                                        RETURN_OBJ_FLAG         ,
235                                                                        DEREF_LINK_FLAG
236                                                                                );
237
238                        // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
239                        final String searchbase = arg.getProparty( "searchbase" ,SEARCH_BASE );
240                        nameEnum = ctx.search( searchbase, filter, constraints );
241                } catch( final NamingException ex ) {
242                        final String errMsg = "NamingException !"
243                                        + ex.getMessage();                              // 5.1.8.0 (2010/07/01) errMsg 修正
244                        throw new OgRuntimeException( errMsg,ex );
245                }
246        }
247
248        /**
249         * プロセスの終了を行います。最後に一度だけ、呼び出されます。
250         * 終了処理(ファイルクローズ、DBクローズ等)に使用します。
251         *
252         * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
253         */
254        public void end( final boolean isOK ) {
255                try {
256                        if( nameEnum  != null ) { nameEnum.close() ;  nameEnum  = null; }
257                }
258                catch( final NamingException ex ) {
259                        final String errMsg = "ディスコネクトすることが出来ません。";
260                        throw new OgRuntimeException( errMsg,ex );
261                }
262        }
263
264        /**
265         * このデータの処理において、次の処理が出来るかどうかを問い合わせます。
266         * この呼び出し1回毎に、次のデータを取得する準備を行います。
267         *
268         * @return      処理できる:true / 処理できない:false
269         */
270        @Override       // FirstProcess
271        public boolean next() {
272                try {
273                        return nameEnum != null && nameEnum.hasMore() ;
274                }
275                catch( final NamingException ex ) {
276                        final String errMsg = "ネクストすることが出来ません。";
277                        throw new OgRuntimeException( errMsg,ex );
278                }
279        }
280
281        /**
282         * 最初に、 行データである LineModel を作成します
283         * FirstProcess は、次々と処理をチェインしていく最初の行データを
284         * 作成して、後続の ChainProcess クラスに処理データを渡します。
285         *
286         * @og.rev 4.2.2.0 (2008/05/10) LDAP パスワード取得対応
287         *
288         * @param       rowNo   処理中の行番号
289         *
290         * @return      処理変換後のLineModel
291         */
292        @Override       // FirstProcess
293        public LineModel makeLineModel( final int rowNo ) {
294                count++ ;
295                try {
296                        if( maxRowCount > 0 && maxRowCount <= executeCount ) { return null ; }
297                        final SearchResult sRslt = nameEnum.next();             // 4.3.3.6 (2008/11/15) Generics警告対応
298                        final Attributes att = sRslt.getAttributes();
299
300                        if( newData == null ) {
301                                newData = createLineModel( att );
302                                if( display ) { println( newData.nameLine() ); }
303                        }
304
305                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );   // 6.1.0.0 (2014/12/26) refactoring
306                        for( int i=0; i<attrs.length; i++ ) {
307                                final Attribute attr = att.get(attrs[i]);
308                                if( attr != null ) {
309                                        final NamingEnumeration<?> vals = attr.getAll();        // 4.3.3.6 (2008/11/15) Generics警告対応
310                                        buf.setLength(0);                                                                       // 6.1.0.0 (2014/12/26) refactoring
311                                        if( vals.hasMore() ) { getDataChange( vals.next(),buf ) ;}      // 4.2.2.0 (2008/05/10)
312                                        while( vals.hasMore() ) {
313                                                buf.append( ',' ) ;                                                             // 6.0.2.5 (2014/10/31) char を append する。
314                                                getDataChange( vals.next(),buf ) ;                              // 4.2.2.0 (2008/05/10)
315                                        }
316                                        // 3.8.0.9 (2005/10/17) 正規表現マッチしなければ、スルーする。
317                                        final String value = buf.toString();
318                                        final String key = matchKey[i];
319                                        if( key != null && value != null && !value.matches( key ) ) {
320                                                return null;
321                                        }
322                                        newData.setValue( i, value );
323                                        executeCount++ ;
324                                }
325                        }
326
327                        newData.setRowNo( rowNo );
328                        if( display ) { println( newData.dataLine() ); }
329                }
330                catch( final NamingException ex ) {
331                        // 6.4.2.1 (2016/02/05) PMD refactoring.
332                        // 6.9.8.0 (2018/05/28) FindBugs:null でないことがわかっている値の冗長な null チェック
333                        // ※ 文字列連結は、内部で、StringBuilder.append しているので、null 判定は不要だが、null という文字列の出力を避けていた。
334//                      final String errMsg = "データを処理できませんでした。[" + rowNo + "]件目" + CR
335//                                      + newData != null ? newData.toString() : "" ;
336                        final String errMsg = "データを処理できませんでした。[" + rowNo + "]件目" + CR
337                                                                        + " newData=[" + newData + "]" + CR ;
338
339                        throw new OgRuntimeException( errMsg,ex );
340                }
341                return newData;
342        }
343
344        /**
345         * LDAPから取得したデータの変換を行います。
346         *
347         * 主に、バイト配列(byte[]) オブジェクトの場合、文字列に戻します。
348         *
349         * @og.rev 4.2.2.0 (2008/05/10) 新規追加
350         *
351         * @param       obj     主にバイト配列オブジェクト
352         * @param       buf     元のStringBuilder
353         *
354         * @return      データを追加した StringBuilder
355         */
356        private StringBuilder getDataChange( final Object obj, final StringBuilder buf ) {
357                if( obj == null ) { return buf; }
358                else if( obj instanceof byte[] ) {
359        //              buf.append( new String( (byte[])obj,"ISO-8859-1" ) );
360                        final byte[] bb = (byte[])obj ;
361                        char[] chs = new char[bb.length];
362                        for( int i=0; i<bb.length; i++ ) {
363                                chs[i] = (char)bb[i];
364                        }
365                        buf.append( chs );
366                }
367                else {
368                        buf.append( obj ) ;
369                }
370
371                return buf ;
372        }
373
374        /**
375         * 内部で使用する LineModel を作成します。
376         * このクラスは、プロセスチェインの基点となりますので、新規 LineModel を返します。
377         * Exception 以外では、必ず LineModel オブジェクトを返します。
378         *
379         * @param   att Attributesオブジェクト
380         *
381         * @return      データベースから取り出して変換した LineModel
382         * @throws RuntimeException カラム名を取得できなかった場合。
383         * @og.rtnNotNull
384         */
385        private LineModel createLineModel( final Attributes att ) {
386                final LineModel model = new LineModel();
387
388                try {
389                        // init() でチェック済み。attrs == null か、属性定義数と別名配列数が同じはず。
390                        // attrs が null の場合は、全キー情報を取得します。
391                        if( attrs == null ) {
392                                final NamingEnumeration<String> nmEnum = att.getIDs();  // 4.3.3.6 (2008/11/15) Generics警告対応
393                                final List<String> lst = new ArrayList<>();
394                                try {
395                                        while( nmEnum.hasMore() ) {
396                                                lst.add( nmEnum.next() );               // 4.3.3.6 (2008/11/15) Generics警告対応
397                                        }
398                                }
399                                finally {
400                                        nmEnum.close();
401                                }
402                                attrs = lst.toArray( new String[lst.size()] );
403                                columns = attrs;
404                        }
405
406                        final int size = columns.length;
407                        model.init( size );
408                        for( int clm=0; clm<size; clm++ ) {
409                                model.setName( clm,StringUtil.nval( columns[clm],attrs[clm] ) );
410                        }
411                }
412                catch( final NamingException ex ) {
413                        final String errMsg = "ResultSetMetaData から、カラム名を取得できませんでした。";
414                        throw new OgRuntimeException( errMsg,ex );
415                }
416                return model;
417        }
418
419        /**
420         * スコープを表す文字列を SearchControls の定数に変換します。
421         * 入力文字列は、OBJECT、ONELEVEL、SUBTREEです。変換する定数は、
422         * SearchControls クラスの static 定数です。
423         *
424         * @param    scope スコープを表す文字列(OBJECT、ONELEVEL、SUBTREE)
425         *
426         * @return   SearchControlsの定数(OBJECT_SCOPE、ONELEVEL_SCOPE、SUBTREE_SCOPE)
427         * @see      javax.naming.directory.SearchControls#OBJECT_SCOPE
428         * @see      javax.naming.directory.SearchControls#ONELEVEL_SCOPE
429         * @see      javax.naming.directory.SearchControls#SUBTREE_SCOPE
430         */
431        private int changeScopeString( final String scope ) {
432                int rtnScope ;
433                if( "OBJECT".equals( scope ) )        { rtnScope = SearchControls.OBJECT_SCOPE ; }
434                else if( "ONELEVEL".equals( scope ) ) { rtnScope = SearchControls.ONELEVEL_SCOPE ; }
435                else if( "SUBTREE".equals( scope ) )  { rtnScope = SearchControls.SUBTREE_SCOPE ; }
436                else {
437                        final String errMsg = "Search Scope in 『OBJECT』『ONELEVEL』『SUBTREE』Selected"
438                                                        + "[" + scope + "]" ;
439                        throw new OgRuntimeException( errMsg );
440                }
441                return rtnScope ;
442        }
443
444        /**
445         * プロセスの処理結果のレポート表現を返します。
446         * 処理プログラム名、入力件数、出力件数などの情報です。
447         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
448         * 形式で出してください。
449         *
450         * @return   処理結果のレポート
451         */
452        public String report() {
453                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
454                return "[" + getClass().getName() + "]" + CR
455//              final String report = "[" + getClass().getName() + "]" + CR
456                                + TAB + "Search Filter : " + filter + CR
457                                + TAB + "Input Count   : " + count ;
458
459//              return report ;
460        }
461
462        /**
463         * このクラスの使用方法を返します。
464         *
465         * @return      このクラスの使用方法
466         * @og.rtnNotNull
467         */
468        public String usage() {
469                final StringBuilder buf = new StringBuilder( BUFFER_LARGE )
470                        .append( "Process_LDAPReaderは、LDAPから読み取った内容を、LineModel に設定後、"           ).append( CR )
471                        .append( "下流に渡す、FirstProcess インターフェースの実装クラスです。"                                 ).append( CR )
472                        .append( CR )
473                        .append( "LDAPから読み取った内容より、LineModelを作成し、下流(プロセスチェインは、"          ).append( CR )
474                        .append( "チェインしているため、データは上流から下流へと渡されます。)に渡します。"         ).append( CR )
475                        .append( CR )
476                        .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"    ).append( CR )
477                        .append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"           ).append( CR )
478                        .append( "繋げてください。"                                                                                                                             ).append( CR )
479                        .append( CR ).append( CR )
480                        .append( getArgument().usage() ).append( CR );
481
482                return buf.toString();
483        }
484
485        /**
486         * このクラスは、main メソッドから実行できません。
487         *
488         * @param       args    コマンド引数配列
489         */
490        public static void main( final String[] args ) {
491                LogWriter.log( new Process_LDAPReader().usage() );
492        }
493}