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