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() ) { getDataChange( vals.next(),buf ) ;}      // 4.2.2.0 (2008/05/10)
165                                                while ( vals.hasMore() ) {
166                                                        buf.append( "," ) ;
167                                                        getDataChange( vals.next(),buf ) ;      // 4.2.2.0 (2008/05/10)
168                                                }
169                                                values[i] = buf.toString();
170                                                flag = true;
171                                        }
172                                }
173                                if( flag ) {
174                                        list.add( values );
175                                        executeCount++ ;
176                                }
177                        }
178                        if( results != null ) { results.close(); }
179                } catch ( NamingException ex ) {
180                        String errMsg = "List オブジェクトの検索に失敗しました。"
181                                                + HybsSystem.CR
182                                                + "searchbase や、entrydn の記述をご確認ください。"
183                                                + HybsSystem.CR
184                                                + "searchbase:" + searchbase
185                                                + " , entrydn:" + entrydn ;
186                        throw new HybsSystemException( errMsg,ex );             // 3.5.5.4 (2004/04/15) 引数の並び順変更
187                }
188                return sort( list,attrs ) ;
189        }
190
191        /**
192         * LDAPから取得したデータの変換を行います。
193         *
194         * 主に、バイト配列(byte[]) オブジェクトの場合、文字列に戻します。
195         *
196         * @og.rev 4.2.2.0 (2008/05/10) 新規追加
197         *
198         * @param       obj     主にバイト配列データ
199         * @param       buf     元のStringBuilder
200         *
201         * @return      データを追加したStringBuilder
202         */
203        private StringBuilder getDataChange( final Object obj, final StringBuilder buf ) {
204                if( obj == null ) { return buf; }
205                else if( obj instanceof byte[] ) {
206                        byte[] bb = (byte[])obj ;
207                        char[] chs = new char[bb.length];
208                        for( int i=0; i<bb.length; i++ ) {
209                                chs[i] = (char)bb[i];
210                        }
211                        buf.append( chs );
212                }
213                else {
214                        buf.append( obj ) ;
215                }
216
217                return buf ;
218        }
219
220        /**
221         * 検索範囲(OBJECT/ONELEVEL/SUBTREE)を設定します(初期値:LDAP_SEARCH_SCOPE)。
222         *
223         * 検索範囲を OBJECT_SCOPE、ONELEVEL_SCOPE、SUBTREE_SCOPE のどれか 1 つです。
224         * 指定文字列は、それぞれ『OBJECT』『ONELEVEL』『SUBTREE』です。
225         *
226         * @param       scope   SearchControlsの検索範囲
227         */
228        public void setSearchScope( final String scope ) {
229                searchScope = StringUtil.nval( scope, searchScope );
230                if( ! "OBJECT".equals( searchScope ) &&
231                        ! "ONELEVEL".equals( searchScope ) &&
232                        ! "SUBTREE".equals( searchScope ) ) {
233                                String errMsg = "検索範囲は、『OBJECT』『ONELEVEL』『SUBTREE』の中から選択して下さい。"
234                                                                + "[" + searchScope + "]" ;
235                                throw new HybsSystemException( errMsg );
236                }
237        }
238
239        /**
240         * 引数の searchScope 文字列(『OBJECT』『ONELEVEL』『SUBTREE』のどれか)を、
241         * SearchControls クラス定数である、OBJECT_SCOPE、ONELEVEL_SCOPE、SUBTREE_SCOPE のどれか
242         *  1 つに設定します。
243         *
244         * @param       scope   searchScope文字列
245         *
246         * @return      SearchControls定数
247         */
248        private int changeScopeString( final String scope ) {
249                final int rtnScope;
250                if( "OBJECT".equals( scope ) )        { rtnScope = SearchControls.OBJECT_SCOPE ; }
251                else if( "ONELEVEL".equals( scope ) ) { rtnScope = SearchControls.ONELEVEL_SCOPE ; }
252                else if( "SUBTREE".equals( scope ) )  { rtnScope = SearchControls.SUBTREE_SCOPE ; }
253                else {
254                        String errMsg = "Search Scope in 『OBJECT』『ONELEVEL』『SUBTREE』Selected"
255                                                        + "[" + searchScope + "]" ;
256                        throw new HybsSystemException( errMsg );
257                }
258                return rtnScope ;
259        }
260
261        /**
262         * これらの SearchControls の時間制限をミリ秒単位で設定します(初期値:0[無制限])。
263         *
264         * 値が 0 の場合、無制限に待つことを意味します。
265         *
266         * @param       limit   ミリ秒単位の時間制限(初期値:無制限)
267         */
268        public void setTimeLimit( final int limit ) {
269                timeLimit = limit;
270        }
271
272        /**
273         * 検索中のリンクへの間接参照を有効または無効[true/false]にします(初期値:false)。
274         *
275         * 検索中のリンクへの間接参照を有効または無効にします。
276         *
277         * @param       deref   リンクを逆参照する場合は true、そうでない場合は false(初期値:false)
278         */
279        public void setDerefLinkFlag( final boolean deref ) {
280                derefLinkFlag = deref;
281        }
282
283        /**
284         * 結果の一部としてオブジェクトを返すことを有効または無効[true/false]にします(初期値:false)。
285         *
286         * 無効にした場合、オブジェクトの名前およびクラスだけが返されます。
287         * 有効にした場合、オブジェクトが返されます。
288         *
289         * @param       pbjflag オブジェクトが返される場合は true、そうでない場合は false(初期値:false)
290         */
291        public void setReturningObjFlag( final boolean pbjflag ) {
292                returningObjFlag = pbjflag;
293        }
294
295        /**
296         * レジストリの最大検索件数をセットします(初期値:0[無制限])。
297         *
298         * DBTableModelのデータとして登録する最大件数をこの値に設定します。
299         * サーバーのメモリ資源と応答時間の確保の為です。
300         * 0 は、無制限です。(初期値は、無制限です。)
301         *
302         * @param       count   レジストリの最大検索件数
303         */
304        public void setMaxRowCount( final int count ) {
305                maxRowCount = count;
306        }
307
308        /**
309         * 検索の一部として返される属性を文字列配列でセットします。
310         *
311         * null は属性が何も返されないことを示します。
312         * このメソッドからは、空の配列をセットすることは出来ません。
313         *
314         * @param       atr     返される属性を識別する属性 ID の配列
315         */
316        public void setAttributes( final String[] atr ) {
317                if( atr != null ) {
318                        attrs = new String[atr.length];
319                        System.arraycopy( atr,0,attrs,0,atr.length );
320                }
321        }
322
323        /**
324         * 検索の一部として返される属性を文字列配列で取得します。
325         *
326         * setAttributes で、設定した文字列配列が返されます。
327         * 属性配列に、 null をセットした場合、全属性が返されます。
328         *
329         * @return      返される属性を識別する属性 ID の配列
330         */
331        public  String[] getAttributes() {
332                return (attrs == null) ? new String[0] : attrs.clone() ;
333        }
334
335        /**
336         * 初期コンテキストファクトリを指定します(初期値:システムパラメータ の INITIAL_CONTEXT_FACTORY)。
337         *
338         * 初期値は、システムパラメータ の INITIAL_CONTEXT_FACTORY 属性です。
339         * 例)com.sun.jndi.ldap.LdapCtxFactory
340         *
341         * @param       ctx INITIAL_CONTEXT_FACTORY属性
342         */
343        public void setInitctx( final String ctx ) {
344                initctx = StringUtil.nval( ctx, initctx );
345        }
346
347        /**
348         * サービスプロバイダの構成情報を指定します(初期値:システムパラメータ の LDAP_PROVIDER_URL)。
349         *
350         * プロトコルとサーバーとポートを指定します。
351         * 例)『ldap://ldap.opengion.org:389』
352         *
353         * @param       url PROVIDER_URL属性
354         */
355        public void setProviderURL( final String url ) {
356                providerURL = StringUtil.nval( url, providerURL );
357        }
358
359        /**
360         * 検索するコンテキストまたはオブジェクトの名前を設定します(初期値:システムパラメータ の LDAP_SEARCH_BASE)。
361         *
362         * 例)『soOUID=employeeuser,o=opengion,c=JP』
363         *
364         * @param       base SEARCHBASE属性
365         */
366        public void setSearchbase( final String base ) {
367                searchbase = StringUtil.nval( base, searchbase );
368        }
369
370        /**
371         * 属性の取得元のオブジェクトの名前を設定します(初期値:システムパラメータ の LDAP_ENTRYDN)。
372         *
373         * 例)『cn=inquiry-sys,o=opengion,c=JP』
374         *
375         * @param       dn 取得元のオブジェクトの名前
376         */
377        public void setEntrydn( final String dn ) {
378                entrydn = StringUtil.nval( dn, entrydn );
379        }
380
381        /**
382         * 属性の取得元のオブジェクトのパスワードを設定します(初期値:システムパラメータ の LDAP_PASSWORD)。
383         *
384         * @og.rev 4.2.2.0 (2008/05/10) LDAP パスワード取得対応
385         *
386         * @param       pwd 取得元のオブジェクトのパスワード
387         */
388        public void setPassword( final String pwd ) {
389                password = StringUtil.nval( pwd, password );
390        }
391
392        /**
393         * 検索した結果を表示する表示順をファイル属性名で指定します。
394         *
395         * attributes 属性で指定するキー、または、LDAPから返されたキーについて
396         * その属性でソートします。逆順を行う場合は、DESC を指定のカラム名の後ろに
397         * 付けて下さい。
398         *
399         * @param       ordr    ソートキーを指定。
400         */
401        public void setOrderBy( final String ordr ) {
402                orderBy = StringUtil.csv2Array( ordr );
403
404                desc = new boolean[orderBy.length];
405                for( int i=0; i<orderBy.length; i++ ) {
406                        String key = orderBy[i].trim();
407                        int ad = key.indexOf( " DESC" ) ;
408                        if( ad > 0 ) {
409                                desc[i] = true;
410                                key = key.substring( 0,ad );
411                        }
412                        else {
413                                desc[i] = false;
414                        }
415                        orderBy[i] = key ;
416                }
417        }
418
419        /**
420         * リストオブジェクトをヘッダーキーに対応させてソートします。
421         *
422         * @og.rev 4.2.2.0 (2008/05/10) ソート条件を増やします。
423         *
424         * @param       in              ソートするリストオブジェクト
425         * @param       headers ソートするキーになる文字列配列
426         *
427         * @return      ソート結果のリストオブジェクト
428         */
429        private List<String[]> sort( final List<String[]> in,final String[] headers ) {
430                // 4.2.2.0 (2008/05/10) ソート条件を増やします。
431                if( orderBy == null || orderBy.length == 0 ||
432                        headers == null || headers.length == 0 ||
433                        in.isEmpty()                                                            ) { return in; }
434
435                int[] no = new int[orderBy.length];
436                for( int i=0; i<orderBy.length; i++ ) {
437                        String key = orderBy[i] ;
438                        no[i] = -1;     // 未存在時のマーカー
439                        for( int j=0; j<headers.length; j++ ) {
440                                if( key.equalsIgnoreCase( headers[j] ) ) {
441                                        no[i] = j ;     break;
442                                }
443                        }
444                        if( no[i] < 0 ) {
445                                String errMsg = "指定の Order BY キーは、ヘッダー列に存在しません。"
446                                                        + "order Key=[" + key + "] , attri=["
447                                                        + StringUtil.array2csv( headers ) + "]" + HybsSystem.CR ;
448                                throw new HybsSystemException( errMsg );
449                        }
450                }
451
452                String[][] data = in.toArray( new String[in.size()][(in.get(0)).length] );
453                Arrays.sort( data, new IdComparator( no,desc ) );
454                List<String[]> rtn = new ArrayList<String[]>();
455                for( int i=0; i<data.length; i++ ) {
456                        rtn.add( data[i] );
457                }
458                return rtn ;
459        }
460
461        /**
462         * LDAPの検索結果を並び替える為の Comparator実装内部クラスです。
463         *
464         * @og.group その他入力
465         *
466         * @version  4.0
467         * @author       Kazuhiko Hasegawa
468         * @since    JDK5.0,
469         */
470        private static class IdComparator implements Comparator<String[]>,Serializable {
471                private static final long serialVersionUID = 400020050131L ;    // 4.0.0.0 (2005/01/31)
472
473                private final int[]             no ;
474                private final boolean[] desc ;
475                private final int               cnt ;
476
477                /**
478                 * コンストラクター
479                 *
480                 * @param       no      int[]  ソートするリストオブジェクト
481                 * @param       desc    boolean[]       ソートするキーになる文字列配列
482                 */
483                public IdComparator( final int[] no , final boolean[] desc ) {
484                        this.no         = no;
485                        this.desc       = desc;
486                        cnt                     = no.length;
487                }
488
489                /**
490                 * Comparator インターフェースのcompareメソッド
491                 *
492                 * 順序付けのために 2 つの引数を比較します。
493                 * 最初の引数が 2 番目の引数より小さい場合は負の整数、
494                 * 両方が等しい場合は 0、最初の引数が 2 番目の引数より
495                 * 大きい場合は正の整数を返します。
496                 *
497                 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応。トリッキーな値の置き換えをやめます。
498                 *
499                 * @param       s1      比較対象の最初のオブジェクト
500                 * @param       s2      比較対象の 2 番目のオブジェクト
501                 * @return      最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
502                 */
503                public int compare( final String[] s1,final String[] s2 ) {
504                        if( s1 == null ) { return -1; }
505
506                        for( int i=0; i<cnt; i++ ) {
507                                if( s1[no[i]] == null ) { return -1; }
508                                if( s2[no[i]] == null ) { return 1; }   // 5.5.2.6 (2012/05/25) 比較を途中で止めないために、nullチェックしておく。
509                                // 5.5.2.6 (2012/05/25) findbugs対応
510                                int rtn = desc[i] ? s2[no[i]].compareTo( s1[no[i]] ) : s1[no[i]].compareTo( s2[no[i]] ) ;
511                                if( rtn != 0 ) { return rtn ;}
512                        }
513                        return 0;
514                }
515
516        //      public boolean equals(Object obj) {
517        //              return ( this == obj );
518        //      }
519        }
520
521        /**
522         * このオブジェクトの文字列表現を返します。
523         * 基本的にデバッグ目的に使用します。
524         *
525         * @return このクラスの文字列表現
526         */
527        @Override
528        public String toString() {
529                StringBuilder buf = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
530                buf.append( "  initctx      [" ).append( initctx      ).append( "]" ).append( HybsSystem.CR );
531                buf.append( "  providerURL  [" ).append( providerURL  ).append( "]" ).append( HybsSystem.CR );
532                buf.append( "  entrydn      [" ).append( entrydn      ).append( "]" ).append( HybsSystem.CR );
533                buf.append( "  searchbase   [" ).append( searchbase   ).append( "]" ).append( HybsSystem.CR );
534                buf.append( "  searchScope  [" ).append( searchScope  ).append( "]" ).append( HybsSystem.CR );
535                buf.append( "  executeCount [" ).append( executeCount ).append( "]" ).append( HybsSystem.CR );
536                buf.append( "  attributes   [" ).append( StringUtil.array2line( attrs,"," ) );
537                buf.append( "]" ).append( HybsSystem.CR );
538
539                return buf.toString();
540        }
541}