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