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.hayabusa.resource;
017
018import org.opengion.hayabusa.common.HybsSystem;
019import org.opengion.hayabusa.common.HybsSystemException;
020import org.opengion.fukurou.util.StringUtil;
021
022import java.util.Hashtable;
023import java.util.List;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Comparator ;
027import java.io.Serializable;
028
029import javax.naming.Context;
030import javax.naming.NamingEnumeration;
031import javax.naming.NamingException;
032import javax.naming.directory.DirContext;
033import javax.naming.directory.InitialDirContext;
034import javax.naming.directory.SearchControls;
035import javax.naming.directory.SearchResult;
036import javax.naming.directory.Attribute;
037import javax.naming.directory.Attributes;
038
039/**
040 * LDAPの内容を検索するための、ldapQueryタグです。
041 *
042 * 検索した結果は、配列で取得します。
043 *
044 * 下記の項目については、src/resource/システムパラメータ に、予め
045 * 設定しておくことで、タグごとに指定する必要がなくなります。
046 * ・LDAP_INITIAL_CONTEXT_FACTORY
047 * ・LDAP_PROVIDER_URL
048 * ・LDAP_ENTRYDN
049 * ・LDAP_PASSWORD
050 * ・LDAP_SEARCH_BASE
051 * ・LDAP_SEARCH_SCOPE
052 * ・LDAP_SEARCH_REFERRAL
053 *
054 * @og.rev 3.7.1.0 (2005/04/15) LDAPにアクセスできる、LDAPSearch.java を新規に作成。
055 * @og.group その他入力
056 *
057 * @version  4.0
058 * @author       Kazuhiko Hasegawa
059 * @since    JDK5.0,
060 */
061public class LDAPSearch {
062
063        private String                  initctx                         = HybsSystem.sys( "LDAP_INITIAL_CONTEXT_FACTORY" );
064        private String                  providerURL             = HybsSystem.sys( "LDAP_PROVIDER_URL" );
065        private String                  entrydn                         = HybsSystem.sys( "LDAP_ENTRYDN" );
066        private String                  password                        = HybsSystem.sys( "LDAP_PASSWORD" );            // 4.2.2.0 (2008/05/10)
067        private String                  searchbase                      = HybsSystem.sys( "LDAP_SEARCH_BASE" );
068        private String                  referral                        = HybsSystem.sys( "LDAP_SEARCH_REFERRAL" ); // 5.6.7.0 (201/07/27)
069
070        // 検索範囲。OBJECT_SCOPE、ONELEVEL_SCOPE、SUBTREE_SCOPE のどれか 1 つ
071        private String                  searchScope                     = HybsSystem.sys( "LDAP_SEARCH_SCOPE" );
072        private static final long       COUNTLIMIT              = 0;                    // 返すエントリの最大数。0 の場合、フィルタを満たすエントリをすべて返す
073        private int                             timeLimit                       = 0;                    // 結果が返されるまでのミリ秒数。0 の場合、無制限
074        private String[]                attrs                           = null;                 // エントリと一緒に返される属性の識別子。null の場合、すべての属性を返す。空の場合、属性を返さない
075        private boolean                 returningObjFlag        = false;                // true の場合、エントリの名前にバインドされたオブジェクトを返す。false 場合、オブジェクトを返さない
076        private boolean                 derefLinkFlag           = false;                // true の場合、検索中にリンクを間接参照する
077
078        private int                             executeCount            = 0;                    // 検索/実行件数
079        private int                     maxRowCount                     = 0;                    // 最大検索数(0は無制限)
080        private SearchControls  constraints                     = null;
081        private DirContext              ctx                                     = null;
082        private String[]                orderBy                         = null;                 // ソート項目(csv)
083        private boolean[]               desc                            = null;                 // 降順フラグ
084
085        /**
086         * LDAPパラメータを利用して、LDAP検索用オブジェクトを構築します。
087         *
088         * @og.rev 4.2.2.0 (2008/05/10) LDAP パスワード取得対応
089         * @og.rev 5.6.7.0 (2013/07/27) LDAPのREFERRAL対応
090         *
091         * 通常、パラメータをセット後、search( String filter ) の実行前に、呼びます。
092         */
093        public void init() {
094                Hashtable<String,String> env = new Hashtable<String,String>();
095                env.put(Context.INITIAL_CONTEXT_FACTORY, initctx);
096                env.put(Context.PROVIDER_URL, providerURL);
097                if( ! StringUtil.isNull( referral ) ) { // 5.6.7.0 (2013/07/27)
098                        env.put( Context.REFERRAL, referral ); 
099                }
100                // 3.7.1.1 (2005/05/31)
101                if( ! StringUtil.isNull( password ) ) {
102                        env.put( Context.SECURITY_CREDENTIALS, password.trim() );
103                }
104                // 4.2.2.0 (2008/05/10) entrydn 属性の追加
105                if( ! StringUtil.isNull( entrydn ) ) {
106                        env.put( Context.SECURITY_PRINCIPAL  , entrydn );
107                }
108
109                try {
110                        ctx = new InitialDirContext(env);
111                        constraints = new SearchControls(
112                                                                        changeScopeString( searchScope ),
113                                                                        COUNTLIMIT                      ,
114                                                                        timeLimit                       ,
115                                                                        attrs                           ,
116                                                                        returningObjFlag        ,
117                                                                        derefLinkFlag
118                                                                                );
119                } catch ( NamingException ex ) {
120                        String errMsg = "LDAP検索用オブジェクトの初期化に失敗しました。" ;
121                        throw new HybsSystemException( errMsg,ex );             // 3.5.5.4 (2004/04/15) 引数の並び順変更
122                }
123        }
124
125        /**
126         * LDPA から、値を取り出し、List オブジェクトを作成します。
127         * 引数の headerAdd をtrueにする事により、1件目に、キー情報の配列を返します。
128         *
129         * @og.rev 4.2.2.0 (2008/05/10) LDAP パスワード取得対応
130         *
131         * @param       filter  フィルター文字列
132         *
133         * @return      検索結果の Listオブジェクト
134         */
135        public List<String[]> search( final String filter ) {
136
137                List<String[]> list = new ArrayList<String[]>();
138                try {
139                        NamingEnumeration<SearchResult> results = ctx.search(searchbase, filter, constraints);    // 4.3.3.6 (2008/11/15) Generics警告対応
140
141                        while (results != null && results.hasMore()) {
142                                if( maxRowCount > 0 && maxRowCount <= executeCount ) { break ; }
143                                SearchResult si = results.next();               // 4.3.3.6 (2008/11/15) Generics警告対応
144                                Attributes at = si.getAttributes();
145                                // attrs が null の場合は、キー情報を取得します。
146                                if( attrs == null ) {
147                                        NamingEnumeration<String> ne = at.getIDs();       // 4.3.3.6 (2008/11/15) Generics警告対応
148                                        List<String> lst = new ArrayList<String>();
149                                        while( ne.hasMore() ) {
150                                         lst.add( ne.next() );  // 4.3.3.6 (2008/11/15) Generics警告対応
151                                        }
152                                        ne.close();
153                                        attrs = lst.toArray( new String[lst.size()] );
154                                }
155
156                                String[] values = new String[attrs.length];
157                                boolean flag = false;           // 属性チェックフラグ
158                                for( int i=0; i<attrs.length; i++ ) {
159                                        if( maxRowCount > 0 && maxRowCount <= executeCount ) { break ; }
160                                        Attribute attr = at.get(attrs[i]);
161                                        if( attr != null ) {
162                                                NamingEnumeration<?> vals = attr.getAll();        // 4.3.3.6 (2008/11/15) Generics警告対応
163                                                StringBuilder buf = new StringBuilder();
164//                                              if( vals.hasMore() ) { buf.append( vals.next() ) ;}
165                                                if( vals.hasMore() ) { getDataChange( vals.next(),buf ) ;}      // 4.2.2.0 (2008/05/10)
166                                                while ( vals.hasMore() ) {
167                                                        buf.append( "," ) ;
168//                                                      buf.append( vals.next() ) ;
169                                                        getDataChange( vals.next(),buf ) ;      // 4.2.2.0 (2008/05/10)
170                                                }
171                                                values[i] = buf.toString();
172                                                flag = true;
173                                        }
174                                }
175                                if( flag ) {
176                                        list.add( values );
177                                        executeCount++ ;
178                                }
179                        }
180                        if( results != null ) { results.close(); }
181                } catch ( NamingException ex ) {
182                        String errMsg = "List オブジェクトの検索に失敗しました。"
183                                                + HybsSystem.CR
184                                                + "searchbase や、entrydn の記述をご確認ください。"
185                                                + HybsSystem.CR
186                                                + "searchbase:" + searchbase
187                                                + " , entrydn:" + entrydn ;
188                        throw new HybsSystemException( errMsg,ex );             // 3.5.5.4 (2004/04/15) 引数の並び順変更
189                }
190                return sort( list,attrs ) ;
191        }
192
193        /**
194         * LDAPから取得したデータの変換を行います。
195         *
196         * 主に、バイト配列(byte[]) オブジェクトの場合、文字列に戻します。
197         *
198         * @og.rev 4.2.2.0 (2008/05/10) 新規追加
199         *
200         * @param       obj     主にバイト配列データ
201         * @param       buf     元のStringBuilder
202         *
203         * @return      データを追加したStringBuilder
204         */
205        private StringBuilder getDataChange( final Object obj, final StringBuilder buf ) {
206                if( obj == null ) { return buf; }
207                else if( obj instanceof byte[] ) {
208                        byte[] bb = (byte[])obj ;
209                        char[] chs = new char[bb.length];
210                        for( int i=0; i<bb.length; i++ ) {
211                                chs[i] = (char)bb[i];
212                        }
213                        buf.append( chs );
214                }
215                else {
216                        buf.append( obj ) ;
217                }
218
219                return buf ;
220        }
221
222        /**
223         * 検索範囲(OBJECT/ONELEVEL/SUBTREE)を設定します(初期値:LDAP_SEARCH_SCOPE)。
224         *
225         * 検索範囲を OBJECT_SCOPE、ONELEVEL_SCOPE、SUBTREE_SCOPE のどれか 1 つです。
226         * 指定文字列は、それぞれ『OBJECT』『ONELEVEL』『SUBTREE』です。
227         *
228         * @param       scope   SearchControlsの検索範囲
229         */
230        public void setSearchScope( final String scope ) {
231                searchScope = StringUtil.nval( scope, searchScope );
232                if( ! "OBJECT".equals( searchScope ) &&
233                        ! "ONELEVEL".equals( searchScope ) &&
234                        ! "SUBTREE".equals( searchScope ) ) {
235                                String errMsg = "検索範囲は、『OBJECT』『ONELEVEL』『SUBTREE』の中から選択して下さい。"
236                                                                + "[" + searchScope + "]" ;
237                                throw new HybsSystemException( errMsg );
238                }
239        }
240
241        /**
242         * 引数の searchScope 文字列(『OBJECT』『ONELEVEL』『SUBTREE』のどれか)を、
243         * SearchControls クラス定数である、OBJECT_SCOPE、ONELEVEL_SCOPE、SUBTREE_SCOPE のどれか
244         *  1 つに設定します。
245         *
246         * @param       scope   searchScope文字列
247         *
248         * @return      SearchControls定数
249         */
250        private int changeScopeString( final String scope ) {
251                final int rtnScope;
252                if( "OBJECT".equals( scope ) )        { rtnScope = SearchControls.OBJECT_SCOPE ; }
253                else if( "ONELEVEL".equals( scope ) ) { rtnScope = SearchControls.ONELEVEL_SCOPE ; }
254                else if( "SUBTREE".equals( scope ) )  { rtnScope = SearchControls.SUBTREE_SCOPE ; }
255                else {
256                        String errMsg = "Search Scope in 『OBJECT』『ONELEVEL』『SUBTREE』Selected"
257                                                        + "[" + searchScope + "]" ;
258                        throw new HybsSystemException( errMsg );
259                }
260                return rtnScope ;
261        }
262
263        /**
264         * これらの SearchControls の時間制限をミリ秒単位で設定します(初期値:0[無制限])。
265         *
266         * 値が 0 の場合、無制限に待つことを意味します。
267         *
268         * @param       limit   ミリ秒単位の時間制限(初期値:無制限)
269         */
270        public void setTimeLimit( final int limit ) {
271                timeLimit = limit;
272        }
273
274        /**
275         * 検索中のリンクへの間接参照を有効または無効[true/false]にします(初期値:false)。
276         *
277         * 検索中のリンクへの間接参照を有効または無効にします。
278         *
279         * @param       deref   リンクを逆参照する場合は true、そうでない場合は false(初期値:false)
280         */
281        public void setDerefLinkFlag( final boolean deref ) {
282                derefLinkFlag = deref;
283        }
284
285        /**
286         * 結果の一部としてオブジェクトを返すことを有効または無効[true/false]にします(初期値:false)。
287         *
288         * 無効にした場合、オブジェクトの名前およびクラスだけが返されます。
289         * 有効にした場合、オブジェクトが返されます。
290         *
291         * @param       pbjflag オブジェクトが返される場合は true、そうでない場合は false(初期値:false)
292         */
293        public void setReturningObjFlag( final boolean pbjflag ) {
294                returningObjFlag = pbjflag;
295        }
296
297        /**
298         * レジストリの最大検索件数をセットします(初期値:0[無制限])。
299         *
300         * DBTableModelのデータとして登録する最大件数をこの値に設定します。
301         * サーバーのメモリ資源と応答時間の確保の為です。
302         * 0 は、無制限です。(初期値は、無制限です。)
303         *
304         * @param       count   レジストリの最大検索件数
305         */
306        public void setMaxRowCount( final int count ) {
307                maxRowCount = count;
308        }
309
310        /**
311         * 検索の一部として返される属性を文字列配列でセットします。
312         *
313         * null は属性が何も返されないことを示します。
314         * このメソッドからは、空の配列をセットすることは出来ません。
315         *
316         * @param       atr     返される属性を識別する属性 ID の配列
317         */
318        public void setAttributes( final String[] atr ) {
319                if( atr != null ) {
320                        attrs = new String[atr.length];
321                        System.arraycopy( atr,0,attrs,0,atr.length );
322                }
323        }
324
325        /**
326         * 検索の一部として返される属性を文字列配列で取得します。
327         *
328         * setAttributes で、設定した文字列配列が返されます。
329         * 属性配列に、 null をセットした場合、全属性が返されます。
330         *
331         * @return      返される属性を識別する属性 ID の配列
332         */
333        public  String[] getAttributes() {
334//              return attrs.clone() ;
335                return (attrs == null) ? new String[0] : attrs.clone() ;
336        }
337
338        /**
339         * 初期コンテキストファクトリを指定します(初期値:システムパラメータ の INITIAL_CONTEXT_FACTORY)。
340         *
341         * 初期値は、システムパラメータ の INITIAL_CONTEXT_FACTORY 属性です。
342         * 例)com.sun.jndi.ldap.LdapCtxFactory
343         *
344         * @param       ctx INITIAL_CONTEXT_FACTORY属性
345         */
346        public void setInitctx( final String ctx ) {
347                initctx = StringUtil.nval( ctx, initctx );
348        }
349
350        /**
351         * サービスプロバイダの構成情報を指定します(初期値:システムパラメータ の LDAP_PROVIDER_URL)。
352         *
353         * プロトコルとサーバーとポートを指定します。
354         * 例)『ldap://ldap.opengion.org:389』
355         *
356         * @param       url PROVIDER_URL属性
357         */
358        public void setProviderURL( final String url ) {
359                providerURL = StringUtil.nval( url, providerURL );
360        }
361
362        /**
363         * 検索するコンテキストまたはオブジェクトの名前を設定します(初期値:システムパラメータ の LDAP_SEARCH_BASE)。
364         *
365         * 例)『soOUID=employeeuser,o=opengion,c=JP』
366         *
367         * @param       base SEARCHBASE属性
368         */
369        public void setSearchbase( final String base ) {
370                searchbase = StringUtil.nval( base, searchbase );
371        }
372
373        /**
374         * 属性の取得元のオブジェクトの名前を設定します(初期値:システムパラメータ の LDAP_ENTRYDN)。
375         *
376         * 例)『cn=inquiry-sys,o=opengion,c=JP』
377         *
378         * @param       dn 取得元のオブジェクトの名前
379         */
380        public void setEntrydn( final String dn ) {
381                entrydn = StringUtil.nval( dn, entrydn );
382        }
383
384        /**
385         * 属性の取得元のオブジェクトのパスワードを設定します(初期値:システムパラメータ の LDAP_PASSWORD)。
386         *
387         * @og.rev 4.2.2.0 (2008/05/10) LDAP パスワード取得対応
388         *
389         * @param       pwd 取得元のオブジェクトのパスワード
390         */
391        public void setPassword( final String pwd ) {
392                password = StringUtil.nval( pwd, password );
393        }
394
395        /**
396         * 検索した結果を表示する表示順をファイル属性名で指定します。
397         *
398         * attributes 属性で指定するキー、または、LDAPから返されたキーについて
399         * その属性でソートします。逆順を行う場合は、DESC を指定のカラム名の後ろに
400         * 付けて下さい。
401         *
402         * @param       ordr    ソートキーを指定。
403         */
404        public void setOrderBy( final String ordr ) {
405                orderBy = StringUtil.csv2Array( ordr );
406
407                desc = new boolean[orderBy.length];
408                for( int i=0; i<orderBy.length; i++ ) {
409                        String key = orderBy[i].trim();
410                        int ad = key.indexOf( " DESC" ) ;
411                        if( ad > 0 ) {
412                                desc[i] = true;
413                                key = key.substring( 0,ad );
414                        }
415                        else {
416                                desc[i] = false;
417                        }
418                        orderBy[i] = key ;
419                }
420        }
421
422        /**
423         * リストオブジェクトをヘッダーキーに対応させてソートします。
424         *
425         * @og.rev 4.2.2.0 (2008/05/10) ソート条件を増やします。
426         *
427         * @param       in              ソートするリストオブジェクト
428         * @param       headers ソートするキーになる文字列配列
429         *
430         * @return      ソート結果のリストオブジェクト
431         */
432        private List<String[]> sort( final List<String[]> in,final String[] headers ) {
433                // 4.2.2.0 (2008/05/10) ソート条件を増やします。
434                if( orderBy == null || orderBy.length == 0 ||
435                        headers == null || headers.length == 0 ||
436//                      in.size() == 0                                                          ) { return in; }
437                        in.isEmpty()                                                            ) { return in; }
438
439                int[] no = new int[orderBy.length];
440                for( int i=0; i<orderBy.length; i++ ) {
441                        String key = orderBy[i] ;
442                        no[i] = -1;     // 未存在時のマーカー
443                        for( int j=0; j<headers.length; j++ ) {
444                                if( key.equalsIgnoreCase( headers[j] ) ) {
445                                        no[i] = j ;     break;
446                                }
447                        }
448                        if( no[i] < 0 ) {
449                                String errMsg = "指定の Order BY キーは、ヘッダー列に存在しません。"
450                                                        + "order Key=[" + key + "] , attri=["
451                                                        + StringUtil.array2csv( headers ) + "]" + HybsSystem.CR ;
452                                throw new HybsSystemException( errMsg );
453                        }
454                }
455
456                String[][] data = in.toArray( new String[in.size()][(in.get(0)).length] );
457                Arrays.sort( data, new IdComparator( no,desc ) );
458                List<String[]> rtn = new ArrayList<String[]>();
459                for( int i=0; i<data.length; i++ ) {
460                        rtn.add( data[i] );
461                }
462                return rtn ;
463        }
464
465        /**
466         * LDAPの検索結果を並び替える為の Comparator実装内部クラスです。
467         *
468         * @og.group その他入力
469         *
470         * @version  4.0
471         * @author       Kazuhiko Hasegawa
472         * @since    JDK5.0,
473         */
474        private static class IdComparator implements Comparator<String[]>,Serializable {
475                private static final long serialVersionUID = 4000 ;     // 4.0.0 (2005/01/31)
476
477                private final int[]             no ;
478                private final boolean[] desc ;
479                private final int               cnt ;
480
481                /**
482                 * コンストラクター
483                 *
484                 * @param       no      int[]  ソートするリストオブジェクト
485                 * @param       desc    boolean[]       ソートするキーになる文字列配列
486                 */
487                public IdComparator( final int[] no , final boolean[] desc ) {
488                        this.no         = no;
489                        this.desc       = desc;
490                        cnt                     = no.length;
491                }
492
493                /**
494                 * Comparator インターフェースのcompareメソッド
495                 *
496                 * 順序付けのために 2 つの引数を比較します。
497                 * 最初の引数が 2 番目の引数より小さい場合は負の整数、
498                 * 両方が等しい場合は 0、最初の引数が 2 番目の引数より
499                 * 大きい場合は正の整数を返します。
500                 *
501                 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応。トリッキーな値の置き換えをやめます。
502                 *
503                 * @param       s1      比較対象の最初のオブジェクト
504                 * @param       s2      比較対象の 2 番目のオブジェクト
505                 * @return      最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
506                 */
507                public int compare( final String[] s1,final String[] s2 ) {
508                        if( s1 == null ) { return -1; }
509
510                        for( int i=0; i<cnt; i++ ) {
511                                if( s1[no[i]] == null ) { return -1; }
512                                if( s2[no[i]] == null ) { return 1; }   // 5.5.2.6 (2012/05/25) 比較を途中で止めないために、nullチェックしておく。
513                                // 5.5.2.6 (2012/05/25) findbugs対応
514//                              int rtn = s1[no[i]].compareTo( s2[no[i]] ) ;
515//                              if( desc[i] ) { rtn = -rtn; }
516                                int rtn = (desc[i]) ? s2[no[i]].compareTo( s1[no[i]] ) : s1[no[i]].compareTo( s2[no[i]] ) ;
517                                if( rtn != 0 ) { return rtn ;}
518                        }
519                        return 0;
520                }
521
522        //      public boolean equals(Object obj) {
523        //              return ( this == obj );
524        //      }
525        }
526
527        /**
528         * このオブジェクトの文字列表現を返します。
529         * 基本的にデバッグ目的に使用します。
530         *
531         * @return このクラスの文字列表現
532         */
533        @Override
534        public String toString() {
535                StringBuilder buf = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
536                buf.append( "  initctx      [" ).append( initctx      ).append( "]" ).append( HybsSystem.CR );
537                buf.append( "  providerURL  [" ).append( providerURL  ).append( "]" ).append( HybsSystem.CR );
538                buf.append( "  entrydn      [" ).append( entrydn      ).append( "]" ).append( HybsSystem.CR );
539                buf.append( "  searchbase   [" ).append( searchbase   ).append( "]" ).append( HybsSystem.CR );
540                buf.append( "  searchScope  [" ).append( searchScope  ).append( "]" ).append( HybsSystem.CR );
541                buf.append( "  executeCount [" ).append( executeCount ).append( "]" ).append( HybsSystem.CR );
542                buf.append( "  attributes   [" ).append( StringUtil.array2line( attrs,"," ) );
543                buf.append( "]" ).append( HybsSystem.CR );
544
545                return buf.toString();
546        }
547}