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.taglib; 017 018import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 019import org.opengion.hayabusa.common.HybsSystem; 020import org.opengion.hayabusa.common.HybsSystemException; 021import org.opengion.fukurou.util.ToString; // 6.1.1.0 (2015/01/17) 022import org.opengion.fukurou.util.ArraySet; // 6.4.3.4 (2016/03/11) 023import static org.opengion.fukurou.util.StringUtil.nval ; 024 025import org.apache.commons.codec.binary.Base64 ; 026import java.nio.charset.Charset; // 5.5.2.6 (2012/05/25) 027import java.util.Set; // 6.4.3.4 (2016/03/11) 028 029/** 030 * Cookie を読み書きするタグです。 031 * 032 * Cookie は少量の情報を Servlet から Web ブラウザに送り、 ブラウザにそれを 033 * 維持しもらい、以降のアクセスでサーバに送り返してもらう仕組です。 034 * Cookie の値はクライアントを一意に識別できるようになっているので一般に 035 * セッション管理に用いられています。 036 * 037 * Cookie には名前と値が一つありますが、他にコメントやパス、ドメイン、 038 * 最長存続期間、バージョンといったオプショナルな属性もあります。 039 * Web ブラウザの中にはオプショナルな属性の扱いにバグがあるものがあります。 040 * このため、Servlet の相互運用性を高めるためにはあまり使わないほうがいいでしょう。 041 * 042 * 標準の JavaScript で登録機能はサポートしていましたが、メモリのみで、かつ 043 * 画面単位の書き込みのみでした。 044 * 今回の cookie タグでは、永続化(maxAge)の設定や、システム内(CONTEXT_NAME以下) 045 * での共有(デフォルト)や、その変更、ドメインを指定しての共有(domain)などの 046 * 機能を持っています。 047 * また、漢字コードでの読み書き(useBase64)にも対応しています。 048 * 読み込みに関しては、漢字を指定しなければ、{@SYS.COOKIE.カラム名}で、使用可能です。 049 * 複数の読み込み、また、漢字コードを含むクッキーの場合は、読み込み(action="LOAD") 050 * してください。指定のキー以外に、別名に読み込む(aliasNames)事も可能です。 051 * 052 * @og.formSample 053 * ●形式: 054 * <og:cookie 055 * action = "SAVE" Cookie に対するアクションを指定します。(SAVE|LOAD|DELETE) 056 * keys = "AAA,BBB" キーをCSV形式で複数指定できます。 057 * vals = "VAL1,VAL2" 値をCSV形式で複数指定できます。 058 * path = "/ge" クライアントがこの Cookie を返さなくてはいけないパスを指定します。 059 * domain = ".foo.com" この Cookie がどこで生成されたかを表すドメインを指定します。 060 * maxAge = "3600" Cookie の最長存続期間を秒単位で設定します。 061 * useBase64 = "false" 漢字等の2Byte文字を使用する場合に、BASE64で処理します。[true/false] 062 * > 063 * ●body:なし 064 * 065 * ●Tag定義: 066 * <og:cookie 067 * action ○【TAG】アクション(SAVE,LOAD,DELETE)をセットします(必須)。 068 * keys ○【TAG】クッキーのキーをCSV形式で複数指定します(必須)。 069 * vals 【TAG】keys属性に対応する値をCSV形式で複数指定します 070 * aliasNames 【TAG】クッキーのキーの別名をCSV形式で複数指定します 071 * path 【TAG】クライアントがこの Cookie を返さなくてはいけないパスを指定します(初期値:/+CONTEXT_NAME) 072 * domain 【TAG】この Cookie がどこで生成されたかを表すドメインを指定します(初期値:付与したサーバ) 073 * maxAge 【TAG】Cookie の最長存続期間を秒単位で設定します(初期値: -1 ) 074 * useBase64 【TAG】漢字等の文字を扱う場合に、BASE64で処理を行うかどうか[true/false]を設定します(初期値:false ) 075 * debug 【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false) 076 * /> 077 * 078 * ●使用例 079 * 例1)設定:複数キーを同時に書き込むことが可能です。 080 * <og:cookie 081 * action="SAVE" keys="CDJ,FG_NAME" vals="{@CDJ},{@NAME}" 082 * /> 083 * 084 * 例2)取得:cookieタグで取得すると、それ以降では {@CDJ} や {@NAME} で扱えます。 085 * aliasNames 属性を使わない場合は、keys に指定した変数にセットされます。 086 * <og:cookie 087 * action="LOAD" keys="CDJ,FG_NAME" aliasNames="CDJ,NAME" 088 * /> 089 * 090 * 例3)取得:SYS パラメータでの取得も可能です。 091 * {@SYS.COOKIE.CDJ} 092 * 093 * 例4) QUERY画面では値の表示(LOAD)を行い、RESULT画面で値の設定(SAVE)を行うケース 094 * 095 * QUERY画面 096 * <og:cookie action="LOAD" useBase64="true" 097 * keys="CLM,NAME" aliasNames="CLM,LABEL_NAME" /> 098 * 099 * <og:column name="CLM" defaultVal="{@CLM}" /> 100 * <og:column name="LABEL_NAME" defaultVal="{@LABEL_NAME}"/> 101 * 102 * RESULT画面 103 * <og:cookie action="SAVE" maxAge="360000" useBase64="true" 104 * keys="CLM,NAME" vals="{@CLM},{@LABEL_NAME}" /> 105 * 106 * 例5) QUERY画面では、{@SYS.COOKIE.カラム名} で取得。 107 * RESULT画面では、ムラタ内全システム共通に使える値をセット。 108 * 109 * QUERY画面 110 * <og:column name="SYSTEM_ID" defaultVal="{@SYS.COOKIE.SYSTEM_ID}" /> 111 * 112 * RESULT画面 113 * <og:cookie action="SAVE" maxAge="360000" domain=".opengion.org" 114 * keys="SYSTEM_ID" vals="{@SYSTEM_ID}" /> 115 * 116 * @og.rev 3.8.0.2 (2005/06/30) 新規作成 117 * @og.group 画面制御 118 * 119 * @version 0.9.0 2000/10/17 120 * @author Kazuhiko Hasegawa 121 * @since JDK5.0, 122 */ 123public class CookieTag extends CommonTagSupport { 124 /** このプログラムのVERSION文字列を設定します。 {@value} */ 125 private static final String VERSION = "6.4.3.4 (2016/03/11)" ; 126 private static final long serialVersionUID = 643420160311L ; 127 128 /** 129 * プラットフォーム依存のデフォルトの Charset です。 130 * プラットフォーム依存性を考慮する場合、エンコード指定で作成しておく事をお勧めします。 131 * 132 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応 133 */ 134 private static final Charset DEFAULT_CHARSET = Charset.defaultCharset() ; 135 136 /** action 引数に渡す事の出来る アクション 設定 {@value} */ 137 public static final String ACT_SAVE = "SAVE" ; 138 /** action 引数に渡す事の出来る アクション 取得 {@value} */ 139 public static final String ACT_LOAD = "LOAD" ; 140 /** action 引数に渡す事の出来る アクション 削除 {@value} */ 141 public static final String ACT_DELETE = "DELETE" ; 142 143 // 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。 144 private static final Set<String> ACTION_SET = new ArraySet<>( ACT_SAVE , ACT_LOAD , ACT_DELETE ); 145 146 private String action ; 147 private String[] keys ; 148 private String[] vals ; 149 private String[] aliasNames ; 150 private String path = "/" + HybsSystem.sys( "CONTEXT_NAME" ); 151 private String domain ; 152 private int maxAge = -1; 153 private boolean useBase64 ; 154 155 /** 156 * デフォルトコンストラクター 157 * 158 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 159 */ 160 public CookieTag() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 161 162 /** 163 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。 164 * 165 * @return 後続処理の指示(EVAL_PAGE) 166 */ 167 @Override 168 public int doEndTag() { 169 debugPrint(); // 4.0.0 (2005/02/28) 170 actionExec( action ); 171 172 return EVAL_PAGE ; 173 } 174 175 /** 176 * タグリブオブジェクトをリリースします。 177 * キャッシュされて再利用されるので、フィールドの初期設定を行います。 178 * 179 */ 180 @Override 181 protected void release2() { 182 super.release2(); 183 action = null; 184 keys = null; 185 vals = null; 186 aliasNames = null; 187 path = "/" + HybsSystem.sys( "CONTEXT_NAME" ); 188 domain = null; 189 maxAge = -1; 190 useBase64 = false; 191 } 192 193 /** 194 * アクションを実行します。 195 * 196 * アクションは action 属性で指定します。 197 * 198 * @og.rev 6.3.4.0 (2015/08/01) Arrays.toString から String.join に置き換え。 199 * @og.rev 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。 200 * 201 * @param action アクション (public static final 宣言されている文字列) 202 */ 203 private void actionExec( final String action ) { 204 205 if( ACT_SAVE.equals( action ) ) { 206 saveCookies( maxAge ); 207 } 208 else if( ACT_LOAD.equals( action ) ) { 209 loadCookies(); 210 } 211 else if( ACT_DELETE.equals( action ) ) { 212 saveCookies( 0 ); // maxAge にゼロをセットすると削除されます。 213 } 214 else { 215 final String errMsg = "指定のアクションは実行できません。アクションエラー" + CR 216 + "action=[" + action + "] " + CR 217 + "actionList=" + String.join( ", " , ACTION_SET ) ; 218 throw new HybsSystemException( errMsg ); 219 } 220 } 221 222 /** 223 * SAVE アクションを実行します。 224 * 225 * クッキーに書き込みを行います。 226 * 227 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 228 * 229 * @param maxAge 最長存続期間 ( 0 なら削除 ) 230 */ 231 private void saveCookies( final int maxAge ) { 232 // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 233 if( keys == null || vals == null || keys.length != vals.length ) { 234 final String errMsg = "キーが未登録か、値が未登録か、キーと値の個数が異なります。" ; 235 throw new OgRuntimeException( errMsg ); 236 } 237 238 // BASE64Encoder encoder = null; 239 for( int i=0; i<keys.length; i++ ) { 240 String value = nval (vals[i],"" ); 241 if( useBase64 && value.length() > 0 ) { 242 // if( encoder == null ) { encoder = new BASE64Encoder(); } 243 // value = encoder.encode( value.getBytes() ) ; 244 245 final byte[] encoded = Base64.encodeBase64( value.getBytes( DEFAULT_CHARSET ) ); // 5.5.2.6 (2012/05/25) findbugs対応 246 value = new String( encoded,DEFAULT_CHARSET ); // 5.5.2.6 (2012/05/25) findbugs対応 247 } 248 setCookie( keys[i], value, maxAge ); 249 } 250 } 251 252 /** 253 * LOAD アクションを実行します。 254 * 255 * クッキーから読み込みを行います。 256 * 257 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 258 * 259 */ 260 private void loadCookies() { 261 // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 262 if( keys == null ) { 263 final String errMsg = "キーが未登録です。" ; 264 throw new OgRuntimeException( errMsg ); 265 } 266 267 // BASE64Decoder decoder = null; 268 if( aliasNames == null ) { aliasNames = keys; } 269 270 // try { 271 for( int i=0; i<keys.length; i++ ) { 272 String value = getCookie( keys[i] ); 273 if( useBase64 && value != null && value.length() > 0 ) { 274 // if( decoder == null ) { decoder = new BASE64Decoder(); } 275 // byte[] decoded = decoder.decodeBuffer( value ); 276 final byte[] decoded = Base64.decodeBase64( value.getBytes( DEFAULT_CHARSET ) ); // 5.5.2.6 (2012/05/25) findbugs対応 277 value = new String( decoded,DEFAULT_CHARSET ); // 5.5.2.6 (2012/05/25) findbugs対応 278 } 279 if( value != null ) { 280 setRequestAttribute( aliasNames[i],value ); 281 } 282 } 283 // } 284 // catch( final IOException ex ) { 285 // final String errMsg = "BASE64Decoder エラー " + ex.toString(); 286 // throw new HybsSystemException( errMsg ); 287 // } 288 } 289 290 /** 291 * 【TAG】アクション(SAVE,LOAD,DELETE)をセットします。 292 * 293 * @og.tag 294 * アクションは,HTMLから(get/post)指定されますので,ACT_xxx で設定される 295 * フィールド定数値のいづれかを、指定できます。 296 * 無指定の場合は、なにもしません。 297 * 298 * <table class="plain"> 299 * <caption>アクションの説明</caption> 300 * <tr><th>action </th><th>名称</th><th>機能</th></tr> 301 * <tr><td>SAVE </td><td>登録</td><td>指定の keys のキーに vals の値をセットします。</td></tr> 302 * <tr><td>LOAD </td><td>取得</td><td>指定の keys のクッキーを(リクエスト中に)取得します。</td></tr> 303 * <tr><td>DELETE </td><td>削除</td><td>指定の keys のクッキーを削除します。</td></tr> 304 * </table> 305 * 306 * @og.rev 6.3.4.0 (2015/08/01) Arrays.toString から String.join に置き換え。 307 * @og.rev 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。 308 * 309 * @param act アクション (public static final 宣言されている文字列) 310 * @see <a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.CookieTag.ACT_DELETE">アクション定数</a> 311 */ 312 public void setAction( final String act ) { 313 action = nval( getRequestParameter( act ),action ); 314 315 if( action != null && !check( action, ACTION_SET ) ) { 316 final String errMsg = "指定のアクションは実行できません。アクションエラー" + CR 317 + "action=[" + action + "] " + CR 318 + "actionList=" + String.join( ", " , ACTION_SET ) ; 319 throw new HybsSystemException( errMsg ); 320 } 321 } 322 323 /** 324 * 【TAG】クッキーのキーをCSV形式で複数指定します。 325 * 326 * @og.tag 327 * クッキーにセットするときのキーを指定します。CSV形式で複数指定できます。 328 * vals 属性には、キーに対応する値を、設定してください。 329 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。 330 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。 331 * 332 * @param key クッキーのキー 333 */ 334 public void setKeys( final String key ) { 335 keys = getCSVParameter( key ); 336 } 337 338 /** 339 * 【TAG】クッキーのキーの別名をCSV形式で複数指定します。 340 * 341 * @og.tag 342 * クッキーから値を取得する(action="LOAD")場合に、読み込みキー(keys)に対応する 343 * 別名を指定することで、別名の変数に読み込んだ値を登録することが出来ます。 344 * 別名を指定しない場合は、keys に指定された名前が、使用されます。 345 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。 346 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。 347 * 348 * @param names クッキーの別名 349 */ 350 public void setAliasNames( final String names ) { 351 aliasNames = getCSVParameter( names ); 352 } 353 354 /** 355 * 【TAG】keys属性に対応する値をCSV形式で複数指定します。 356 * 357 * @og.tag 358 * キーに設定した値を、CSV形式で複数して出来ます。 359 * 指定順序は、キーと同じにしておいて下さい。 360 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。 361 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。 362 * 363 * @param val keys属性に対応する値 364 */ 365 public void setVals( final String val ) { 366 vals = getCSVParameter( val ); 367 } 368 369 /** 370 * 【TAG】クライアントがこの Cookie を返さなくてはいけないパスを指定します(初期値:/+CONTEXT_NAME)。 371 * 372 * @og.tag 373 * この値を指定すると Cookie が該当するディレクトリ内、さらに、 374 * サブディレクトリに存在する全てのページから参照できるようになります。 375 * Cookie のパスには Cookie をセットした Servlet が含まれていなければなりません。 376 * 例えば、/catalog を指定したとします。このとき、 サーバの /catalog 以下の全ての 377 * ディレクトリから Cookie が見えるようになります。 378 * 初期値は、"/" + CONTEXT_NAME です。 379 * 380 * Cookie のパス名指定についての詳細は RFC2109 を参照してください。 381 * 382 * @param uri パスを表すURL 383 */ 384 public void setPath( final String uri ) { 385 path = nval( getRequestParameter( uri ),path ); 386 } 387 388 /** 389 * 【TAG】この Cookie がどこで生成されたかを表すドメインを指定します(初期値:付与したサーバ)。 390 * 391 * @og.tag 392 * ドメイン名の形式は RFC2109 で指定されています。 393 * ドメイン名は (.foo.com のように) ドットで始まります。 このように設定すると、 394 * Cookie は指定された Domain Name System (DNS) のゾーン内のサーバから見える 395 * ようになります(例えば、www.foo.com) からは見えるけれど、a.b.foo.com からは 396 * 見えないというようにです)。 デフォルトでは Cookie を付与したサーバにしか送り返しません。 397 * 398 * @param pattern 生成ドメイン名 399 */ 400 public void setDomain( final String pattern ) { 401 domain = nval( getRequestParameter( pattern ),domain ); 402 } 403 404 /** 405 * 【TAG】Cookie の最長存続期間を秒単位で設定します(初期値: -1 )。 406 * 407 * @og.tag 408 * 正の値が指定されると Cookie はある秒数が過ぎた後、削除されます。 409 * この値は、Cookie の有効期限が切れる 最長 存続期間であることに注意してください。 410 * Cookie の現在までの存続期間ではありません。 411 * 412 * 負の値は Cookie が永続的に保存されないことを意味しています。 この場合、 413 * Web ブラウザが終了すると Cookie も削除されます。 0 という値を指定すると 414 * Cookie が削除されることになります。 415 * 初期値は、-1(永続的に保存されない)です。 416 * 417 * @param expiry 最長存続期間(秒) (負の値は Cookie を保存しない、 0 なら Cookie を削除する意味となる) 418 */ 419 public void setMaxAge( final String expiry ) { 420 maxAge = nval( getRequestParameter( expiry ),maxAge ); 421 } 422 423 /** 424 * 【TAG】漢字等の文字を扱う場合に、BASE64で処理を行うかどうか[true/false]を設定します(初期値:false )。 425 * 426 * @og.tag 427 * クッキーへの読み書きは、ASCII に限られます。漢字等のコードを書き込む場合は、 428 * BASE64でエンコードして書き込む必要があります。読み込む場合も同様です。 429 * ただし、一般のASCIIは、BASE64 ではエンコードしないため、外部で指定する必要があります。 430 * BASE64 で書き込んだ場合ば、{@SYS.COOKIE.CDJ} での取得はできませんので、 431 * action="LOAD" で、取得してください。 432 * 初期値は、false(使用しない)です。 433 * 434 * @param flag BASE64処理可否 [true:する/false:しない] 435 */ 436 public void setUseBase64( final String flag ) { 437 useBase64 = nval( getRequestParameter( flag ),useBase64 ); 438 } 439 440 /** 441 * このオブジェクトの文字列表現を返します。 442 * 基本的にデバッグ目的に使用します。 443 * 444 * @return このクラスの文字列表現 445 * @og.rtnNotNull 446 */ 447 @Override 448 public String toString() { 449 return ToString.title( this.getClass().getName() ) 450 .println( "VERSION" ,VERSION ) 451 .println( "action" ,action ) 452 .println( "keys" ,keys ) 453 .println( "vals" ,vals ) 454 .println( "aliasNames" ,aliasNames ) 455 .println( "path" ,path ) 456 .println( "domain" ,domain ) 457 .println( "maxAge" ,maxAge ) 458 .println( "useBase64" ,useBase64 ) 459 .println( "Other..." ,getAttributes().getAttribute() ) 460 .fixForm().toString() ; 461 } 462}