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 org.opengion.fukurou.util.ToString;
022import org.opengion.fukurou.util.XHTMLTag;
023import org.opengion.fukurou.util.Attributes;
024
025import org.opengion.fukurou.util.QrcodeImage;
026import static org.opengion.fukurou.util.QrcodeImage.ErrCrct;
027//import static org.opengion.fukurou.util.QrcodeImage.EncMode;                                  // 8.4.1.0 (2023/02/10) Delete
028import static org.opengion.fukurou.util.StringUtil.nval;
029
030import java.util.Locale;
031
032/**
033 * QRコードに対応したイメージファイルを作成するタグです。
034 *
035 * QRコードで表示できる文字数は、バージョン、エンコードモード、エラー訂正レベル に依存します。
036 * また、イメージの大きさは、文字数と1セル辺りのピクセルに依存します。
037 * fileURLは、通常、システムリソースのFILE_URL(=filetemp)なので、fileURL="{@USER.ID}" と
038 * するのが一般的です。
039 * ファイル名は、初期値:rqcode ですが、拡張子はimageType(初期値:PNG)を小文字化して追加します。
040 * 拡張子が付いている場合は、そのまま使用されます。
041 * また、同一ファイル名の場合、ブラウザキャッシュのため、画像が更新されないことがあるため
042 * imgタグのsrc属性に、キャッシュの無効化のための引数を追加しています。
043 *
044 * @og.formSample
045 * ●形式:<og:qrCode fileURL="{@USER.ID}" >
046 *             エンコードする文字列
047 *         </og:qrCode >
048 * ●body:あり(EVAL_BODY_BUFFERED:BODYを評価し、{@XXXX} を解析します)
049 *
050 * または、
051 *
052 * ●形式:<og:qrCode value="エンコードする文字列" />
053 *
054 * ●Tag定義:
055 *   <og:qrCode
056 *       value              【TAG】エンコードする文字列(または、BODY部に記述)
057 *       version            【TAG】バージョン (2から40の整数)(初期値:5)
058 *  <del>encodeMode         【TAG】エンコードモード('N':数字モード 'A':英数字モード 'B':8bit byteモード)(初期値:B)</del> 8.4.1.0 (2023/02/10) Delete
059 *       errCorrect         【TAG】エラー訂正レベル ('L','M','Q','H')(初期値:M)
060 *       imageType          【TAG】イメージファイル形式(PNG/JPEG)(初期値:PNG)
061 *       pixel              【TAG】1セル辺りの塗りつぶしピクセル(初期値:3)
062 *       fileURL            【TAG】QRイメージファイルを出力するディレクトリ(初期値:FILE_URL)
063 *       filename           【TAG】QRイメージファイル名 (初期値:rqcode)
064 *       textEncode         【TAG】テキスト文字エンコード(初期値:Charset.defaultCharset()) 7.2.3.0 (2020/04/10)
065 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null)
066 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null)
067 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない)
068 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない)
069 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない)
070 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
071 *   &gt;   ... Body ...
072 *   &lt;/og:qrCode&gt;
073 *
074 * @og.rev 7.2.1.0 (2020/03/13) 新規作成
075 * @og.group 画面表示
076 *
077 * @version     7.2
078 * @author       Kazuhiko Hasegawa
079 * @since       JDK11.0,
080 */
081public class QRcodeTag extends CommonTagSupport {
082        /** このプログラムのVERSION文字列を設定します。   {@value} */
083        private static final String VERSION = "8.4.1.0 (2023/02/10)" ;
084        private static final long serialVersionUID = 841020230210L ;
085
086        /** イメージに表示させる alt属性の文字数制限 */
087        private static final int MAX_ALT_SIZE = 50;
088
089        /** エンコードする文字列(または、BODY部に記述) */
090        private String  value           ;
091        /** バージョン (2から40の整数) 初期値:5 */
092        private int             version         = QrcodeImage.DEF_VERSION;
093//      private EncMode encMode         = EncMode.DEF;                                                  // エンコードモード('N':数字モード 'A':英数字モード 'B':8bit byteモード)(初期値:B) 8.4.1.0 (2023/02/10) Delete
094        /** エラー訂正レベル ('L','M','Q','H') 初期値:M */
095        private ErrCrct errCrct         = ErrCrct.DEF;
096        /** イメージファイル形式(PNG/JPEG) 初期値:PNG */
097        private String  imgType         = QrcodeImage.IMAGE_TYPE;
098        /** 1セル辺りの塗りつぶしピクセル 初期値:3 */
099        private int             pixel           = QrcodeImage.PIXEL;
100        /** QRイメージファイルを出力するディレクトリ 初期値:FILE_URL/{&#064;USER.ID} */
101        private String  fileURL         = HybsSystem.sys( "FILE_URL" );
102        /** QRイメージファイル名 (初期値:rqcode) */
103        private String  filename        = "rqcode";
104        /** テキスト文字エンコード 初期値:Charset.defaultCharset() */
105        private String  textEncode      ;                                                                               // 7.2.3.0 (2020/04/10)
106
107        /**
108         * デフォルトコンストラクター
109         *
110         * @og.rev 7.2.1.0 (2020/03/13) 新規作成
111         */
112        public QRcodeTag() { super(); }                 // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
113
114        /**
115         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
116         *
117         * @return      後続処理の指示( EVAL_BODY_BUFFERED )
118         */
119        @Override
120        public int doStartTag() {
121                // caseKey、caseVal 属性対応
122                if( useTag() ) {
123                        if( value == null || value.length() <= 0 ) {
124                                return EVAL_BODY_BUFFERED ;             // Body を評価する
125                        }
126                }
127                return SKIP_BODY ;                                              // Body を評価しない
128        }
129
130        /**
131         * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
132         *
133         * @return      後続処理の指示(SKIP_BODY)
134         */
135        @Override
136        public int doAfterBody() {
137                value = getBodyString();
138
139                return SKIP_BODY ;
140        }
141
142        /**
143         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
144         *
145         * @og.rev 7.2.3.0 (2020/04/10) textEncode byteモード時のテキスト文字エンコード追加
146         * @og.rev 8.4.1.0 (2023/02/10) QRコードを swetake から ZXing への置換(encodeMode廃止)
147         *
148         * @return      後続処理の指示
149         */
150        @Override
151        public int doEndTag() {
152                debugPrint();
153                // caseKey、caseVal 属性対応
154                if( useTag() ) {
155                        if( filename.indexOf( '.' ) < 0 ) {                             // 拡張子が存在しない
156                                filename = filename + "." + imgType.toLowerCase(Locale.JAPAN);
157                        }
158
159                        try {
160                                final String saveFile = HybsSystem.url2dir( fileURL,filename );
161
162                                final QrcodeImage qrcode = new QrcodeImage();
163//                              qrcode.init( value,saveFile,version,encMode,errCrct,imgType,pixel );
164//                              qrcode.init( value,saveFile,version,encMode,errCrct,imgType,pixel,textEncode ); // 7.2.3.0 (2020/04/10)
165                                qrcode.init( value,saveFile,version,errCrct,imgType,pixel,textEncode );                 // 8.4.1.0 (2023/02/10)
166                                qrcode.saveImage();
167                        }
168                        catch( final Throwable th ) {
169                                final String errMsg = "QRコードが作成できませんでした。"                                       + CR
170                                                                + "指定のパラメータが正しいかどうかご確認ください。"            + CR
171                                                                + "  version    = [" + version   + "] バージョン (2から40の整数)"                                         + CR
172//                                                              + "  encodeMode = [" + encMode   + "] エンコードモード('N':数字 'A':英数字'B':byte)" + CR    // 8.4.1.0 (2023/02/10) Delete
173                                                                + "  errCorrect = [" + errCrct   + "] エラー訂正レベル ('L','M','Q','H')"       + CR
174                                                                + "  imageType  = [" + imgType   + "] イメージファイル形式(PNG/JPEG)"             + CR
175                                                                + "  pixel      = [" + pixel     + "] 1セル辺りの塗りつぶしピクセル"          ;
176                                throw new HybsSystemException( errMsg,th );
177                        }
178
179                        filename = filename + "?val=" + System.currentTimeMillis() ;            // 同じファイル名の場合のキャッシュの無効化
180                        final String src = StringUtil.urlAppend( getContextPath() , fileURL , filename );
181                        final String alt = value.substring( 0,Math.min( value.length() , MAX_ALT_SIZE ) );
182
183                        // 作成された QRコードのイメージを表示します。
184                        final String img = XHTMLTag.img(
185                                        new Attributes()
186                                                .set( "src"             , src )
187                                                .set( "alt"             , alt )
188                                                .set( "title"   , alt )
189                        );
190
191                        jspPrint( img );
192                }
193                return EVAL_PAGE ;
194        }
195
196        /**
197         * タグリブオブジェクトをリリースします。
198         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
199         *
200         * @og.rev 7.2.3.0 (2020/04/10) textEncode byteモード時のテキスト文字エンコード追加
201         * @og.rev 8.4.1.0 (2023/02/10) QRコードを swetake から ZXing への置換(encodeMode廃止)
202         */
203        @Override
204        protected void release2() {
205                super.release2();
206                value           = null;                                                                         // エンコードする文字列(または、BODY部に記述)
207                version         = QrcodeImage.DEF_VERSION;                                      // バージョン (2から40の整数)(初期値:5)
208//              encMode         = EncMode.DEF;                                                          // エンコードモード('N':数字モード 'A':英数字モード 'B':8bit byteモード)(初期値:B)  // 8.4.1.0 (2023/02/10) Delete
209                errCrct         = ErrCrct.DEF;                                                          // エラー訂正レベル ('L','M','Q','H')(初期値:M)
210                imgType         = QrcodeImage.IMAGE_TYPE;                                       // イメージファイル形式(PNG/JPEG)(初期値:PNG)
211                pixel           = QrcodeImage.PIXEL;                                            // 1セル辺りの塗りつぶしピクセル(初期値:3)
212                fileURL         = HybsSystem.sys( "FILE_URL" );                         // QRイメージファイルを出力するディレクトリ(初期値:FILE_URL/{@USER.ID})
213                filename        = "rqcode";                                                                     // QRイメージファイル名 (初期値:rqcode)
214                textEncode      = null;                                                                         // 7.2.3.0 (2020/04/10) byteモード時のテキスト文字エンコード
215        }
216
217        /**
218         * 【TAG】エンコードする文字列(または、BODY部に記述)を指定します。
219         *
220         * @og.tag
221         * エンコードする文字列のバイト数は、バージョン、エンコードモード、エラー訂正レベルに依存します。
222         * また、イメージの大きさは、それらプラス1セル辺りのピクセルも影響します。
223         *
224         * @param       val     エンコードする文字列(または、BODY部に記述)
225         */
226        public void setValue( final String val ) {
227                value = nval( getRequestParameter( val ),value );
228        }
229
230        /**
231         * 【TAG】バージョン (2から40の整数)を指定します(初期値:5)。
232         *
233         * @og.tag
234         * エンコードする文字列のバイト数は、エラー訂正レベル、バージョン に依存します。
235         * 文字列のバイト数を増やす場合は、バージョンを適切に設定します。
236         *
237         * @param       ver     バージョン
238         */
239        public void setVersion( final String ver ) {
240                version = nval( getRequestParameter( ver ),version );
241        }
242
243//      /**
244//       * 【TAG】エンコードモード('N':数字モード 'A':英数字モード 'B':8bit byteモード)を指定します(初期値:B)。
245//       *
246//       * @og.tag
247//       * エンコードする文字列の種類に応じて設定します。
248//       * 日本語等を含む場合は、'B':8bit byteモード にしてください。
249//       *
250//       * @og.rev 8.4.1.0 (2023/02/10) QRコードを swetake から ZXing への置換(encodeMode廃止)
251//       *
252//       * @param       mode    エンコードモード
253//       */
254//      public void setEncodeMode( final String mode ) {
255//              final String em = nval( getRequestParameter( mode ),null );
256//              if( em != null ) {
257//                      encMode = EncMode.get( em.charAt(0) );
258//              }
259//      }
260
261        /**
262         * 【TAG】エラー訂正レベル ('L','M','Q','H')を指定します(初期値:M)。
263         *
264         * @og.tag
265         * エンコードする文字列のバイト数は、エラー訂正レベル、バージョン に依存します。
266         * 通常、初期値のままで問題ありません。
267         *
268         * @param       crct    エラー訂正レベル
269         */
270        public void setErrCorrect( final String crct ) {
271                final String ec = nval( getRequestParameter( crct ),null );
272                if( ec != null ) {
273                        errCrct = ErrCrct.get( ec.charAt(0) );
274                }
275        }
276
277        /**
278         * 【TAG】イメージファイル形式(PNG/JPEG)を指定します(初期値:PNG)。
279         *
280         * @og.tag
281         * QRコードのイメージファイルの形式を指定します。
282         * 拡張子は自動的にイメージファイル形式(の小文字)がセットされます。
283         *
284         * @param       type    イメージファイル形式
285         */
286        public void setImageType( final String type ) {
287                imgType = nval( getRequestParameter( type ),imgType );
288        }
289
290        /**
291         * 【TAG】1セル辺りの塗りつぶしピクセルを指定します(初期値:3)。
292         *
293         * @og.tag
294         * QRコードのイメージファイルの形式を指定します。
295         * 拡張子は自動的にイメージファイル形式(の小文字)がセットされます。
296         *
297         * @param       px      ピクセル数
298         */
299        public void setPixel( final String px ) {
300                pixel = nval( getRequestParameter( px ),pixel );
301        }
302
303        /**
304         * 【TAG】QRイメージファイルを出力するディレクトリを指定します(初期値:FILE_URL)。
305         *
306         * @og.tag
307         * この属性で指定されるディレクトリに、QRイメージファイルをセーブします。
308         * 指定方法は、通常の fileURL 属性と同様に、先頭が、'/' (UNIX) または、2文字目が、
309         * ":" (Windows)の場合は、指定のURLそのままのディレクトリに、そうでない場合は、
310         * FILE_URL 属性で指定のフォルダの下に、フォルダを作成します。
311         * 初期値は、FILE_URL になるため、通常は{&#064;USER.ID}を指定する必要があります。
312         *
313         * @param       url     保存先ディレクトリ名
314         * @see         org.opengion.hayabusa.common.SystemData#FILE_URL
315         */
316        public void setFileURL( final String url ) {
317                final String furl = nval( getRequestParameter( url ),null );
318                if( furl != null ) {
319                        fileURL = StringUtil.urlAppend( fileURL,furl );
320                }
321        }
322
323        /**
324         * 【TAG】QRイメージファイル名をセットします(初期値:rqcode)。
325         *
326         * @og.tag
327         * ファイルを作成するときのファイル名をセットします。
328         * ファイル名の拡張子は、imageType属性の小文字を追加します。
329         * 拡張子付きで指定した場合(ファイル名に、ピリオドを含む場合)は、
330         * そのままの値を使用します。
331         *
332         * @param       fname   ファイル名
333         */
334        public void setFilename( final String fname ) {
335                filename = nval( getRequestParameter( fname ),filename );
336        }
337
338        /**
339         * 【TAG】テキスト文字エンコードをセットします(初期値:環境依存)。
340         *
341         * @og.tag
342         * テキストのエンコードの指定がない場合は、プラットフォーム依存のデフォルトの Charset です。
343         * java.nio.charset.Charset#defaultCharset()
344         * QRコードで、機種依存文字(①など)は、Windows-31J を指定しても読み取り側が対応していません。
345         * その場合は、UTF-8 を指定します。(必要なバイト数は当然増えます)
346         *
347         * 通常、何も指定しないか、UTF-8 を指定するかのどちらかになります。
348         *
349         * @og.rev 7.2.3.0 (2020/04/10) textEncode byteモード時のテキスト文字エンコード追加
350         *
351         * @param       txtEnc  テキスト文字エンコード
352         */
353        public void setTextEncode( final String txtEnc ) {
354                textEncode = nval( getRequestParameter( txtEnc ),textEncode );
355        }
356
357        /**
358         * このオブジェクトの文字列表現を返します。
359         * 基本的にデバッグ目的に使用します。
360         *
361         * @return      このクラスの文字列表現
362         * @og.rtnNotNull
363         */
364        @Override
365        public String toString() {
366                return ToString.title( this.getClass().getName() )
367                                .println( "VERSION"                     , VERSION                       )
368                                .println( "value"                       , value                         )
369                                .println( "version"                     , version                       )
370//                              .println( "encMode"                     , encMode                       )       // 8.4.1.0 (2023/02/10) Delete
371                                .println( "errCrct"                     , errCrct                       )
372                                .println( "imgType"                     , imgType                       )
373                                .println( "pixel"                       , pixel                         )
374                                .println( "fileURL"                     , fileURL                       )
375                                .println( "filename"            , filename                      )
376                                .fixForm().toString() ;
377        }
378}