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