Seasar Conference 2016 Finalに行ってきた
行ってきました!
あいにくの雨でしたが、久しぶりのConference。
気がつけばはてなも何年放置していたんだろう?
SEASAR PROJECTふりかえり
ひがさんがこれまでのプロジェクトを振り返って、当時どうおもって作っていたのかを話してくれました。
SeasarからS2Daoときて、失敗したといっていたS2JSFとTeeda、、、
そしてS2Strutsを採用しようとしたプロジェクト用にもっと使いやすい仕組みとして作ったSAStrutsとS2JDBC
Slim3の話も聞きたかったな
Teedaは失敗だった?
ひがさん的には失敗だったみたいです。ただJSFって規格に乗っかったのが原因かな?
個人的にはテンプレート部分がすごく好きです。
テンプレートのHTMLがブラウザでそのまま確認できて、組み込みやすいと思うんですよね。
ただ、JSFの影響がいろいろでてきている、、、
DOCTYPEがちょっと変だとエラーになったり、出力されるIDなどがJSF規格なので、JavaScriptと相性が悪かったりといろいろありました。
あとはSeasar全般でチュートリアルプログラムの中をみると使い方がわかるけれど、逆引きとかだとどうデータをいれていいのかわからなかったり、セッションとかの使い方がわかりにくかったりとかして、みんな同じところでハマっていた気がします。
とはいえ、テンプレートエンジンとしては好きです
Mayaaとかはあまり触ったことないですが、やっぱりTeedaの方が直感的かなって思います
ためにしJQueryでYAML形式かJSON形式のデータを読み込んでTeedaテンプレートをブラウザだけでどのように動くのか確認するもの作ってみましたが便利でした。
実際には使っていなくて、テストだけで終わりましたが、今度なにかで使えたらいいな
SEASARのフォークの仕方、LASTAFLUTE編
まだ、フォークしていないです。ごめんなさい
そして、たぶんフォークしません
そういえばSeasarをビルドするのに昔にJava4をいれたなと思い出しました。
最近その環境消した気もします、、、
Java8化したときには結構いろいろ手をいれないといけないみたいですね。
EC-CUBEの納品用画面仕様書作成
ソースファイルから仕様書の自動作成実験です。EC-CUBEのソースからほぼ自動で画面仕様と画面キャプチャーを行なっています。
作成したPDF(Google Doc)
↑ファイル->元のファイルをダウンロードからacrobatでしおり表示して見たほうが見やすいです。
ページクラスの抜き出し
LC_Pageから始まるファイルをすべて検索して処理を行います。中のファイルを見てみると以下のような行がありますので、ここを抜き出します。
<?php $objFormParam->addParam('会社名', 'company_name', STEXT_LEN, 'KVa', array("MAX_LENGTH_CHECK"));
半自動ツールなので、かなり無茶なコードですが、evalを駆使してデータの取得を行っています。。。上記以外にも必要そうなデータを取得しています。
当初はfgetsを利用して、行単位で取り込みを行なっていましたが、複数行に分割されていると取得できないので、token_get_allを利用してなんちゃってPHPソースのパースを行なっています。
画面キャプチャ用のhtml出力
ファイル名から推測されるURLにアクセスして、画面キャプチャを取得するhtmlを記述します。出力するファイルはSelenium IDE用のファイルで、Firefoxで一気にキャプチャします。
一部URLが自動的に変換できないところなどは対応表作って正しいURLを設定してあげてます。
画面キャプチャ
Selenium IDEを利用して一気にキャプチャします。最初にログインを行ってから、各画面にアクセスしてはキャプチャを繰り返します。プラグインの追加やデリート系のページについては画面キャプチャが面倒なのであきらめました。数枚であれば手で取ればいいですからね。
再度ツール実行
キャプチャした場所にファイルがあると項目のほかに画面キャプチャもhtmlに出力しています。印刷した際に一画面に収まるように縦に分割とかもしています。SEO設定画面は縦に長すぎるので表示していません。
htmlの保存
ローカルにファイルないと次のWordに貼り付けができないので、一度保存します。IE8とFirefox9では正常に保存と読み込みができました。
Wordで読み込み
保存したhtmlを読み込みます。h1タグとかをつけて出力してあるので、そのまま見出しで読み込まれます。
雛形Wordにコピーアンドペーストで貼り付ける
見出しなどに書式が設定されているファイルに貼り付けます。表紙や目次などもあらかじめ設定してあるのがよいと思います。
今回は最初の概要までは手で作って、それ以降はツールから貼り付けたものそのままになります。そのままだとリンク設定になっていますので、図を文章に保存するにしてから、リンクを解除します。
目次の更新
意外と大切!
見出しを選択してからF9ですべて更新します。
PDFに出力
そのままでもいいのですが、Word2007以降の人の場合にはしおり付きのPDFが出力できます。
考察
実は一年以上前に取り込み部分まで作っていたのですが、特に自分では利用しないので忙しさにかまけて放置していました。。。まだツールとしては荒いつくりなので、手を入れる必要があるとは思いますがプロトタイプとしては十分かと思います。
OSS使っても全部のページの仕様書頂戴といわれることが多いと思うので、こんなものがあってもいいかなーって思って作りました。カスタマイズしたところは、もう少し詳細仕様を別紙で起こして、共通部分はこれぐらいでいいよね?
ライセンスどうなっているんだーとか、改造してやるぞとか、使い方わからねーとかはコメントかメッセージ等ください。
気がついたら1年ぐらい経過していました。本当は画面遷移まで取ろうかと思いましたが、テンプレートとページクラスの両方からデータを取得しないといけないのでやめました。そのうち画面遷移図も作るかもしれません。
作成物
作成したプログラム
<?php $def_base_url = "http://localhost/eccube/html"; $def_user_id = "admin"; $def_user_pass = "admin"; $def_user_id2 = "test@test.jp"; $def_user_pass2 = "test"; $def_out_dir = "C:\\p\\xampp\\htdocs\\eccube\\capimg"; $def_img_height = 1200; $def_img_width = 640; $def_product_id = 2; $def_urllist = array( $def_base_url."/admin/index/index.php" => $def_base_url."/admin/index.php", $def_base_url."/admin/products/class_category.php" => $def_base_url."/admin/products/classcategory.php", $def_base_url."/admin/products/uploadcsv.php" => $def_base_url."/admin/products/upload_csv.php", $def_base_url."/admin/products/uploadcsvcategory.php" => $def_base_url."/admin/products/upload_csv_category.php", $def_base_url."/admin/system/admin_area.php" => $def_base_url."/admin/system/adminarea.php", $def_base_url."/mypage/down_load.php" => $def_base_url."/mypage/download.php", $def_base_url."/admin/system/admin_area.php" => $def_base_url."/admin/system/adminarea.php", $def_base_url."/products/detail.php" => $def_base_url."/products/detail.php?product_id=".$def_product_id, $def_base_url."/products/review.php" => $def_base_url."/products/review.php?product_id=".$def_product_id, $def_base_url."/input_zip/index.php" => $def_base_url."/input_zip.php?zip1=100&zip2=0001", ); $def_nodisp_urllist = array( $def_base_url."/admin/basis/seo.php", $def_base_url."/admin/plugin/install.php", $def_base_url."/admin/plugin/uninstall.php", ); ini_set('display_errors', 'Off'); if( isset( $_GET['file'] ) ){ dispImg(); exit; } $sfp = fopen( "selenium.html", "w" ); require_once 'html/require.php'; define ("DEFAULT_DSN", DB_TYPE . "://" . DB_USER . ":" . DB_PASSWORD . "@" . DB_SERVER . ":" .DB_PORT . "/" . DB_NAME); define ("CLASS_IMAGE_WIDTH", "CLASS_IMAGE_WIDTH"); define ("CLASS_IMAGE_HEIGHT", "CLASS_IMAGE_HEIGHT"); require_once 'data/mtb_constants_init.php'; $searchEcCube = new searchEcCubeClass; $searchEcCube->checkDir("."); $now_h1type = "none"; seleniumHead($sfp); $pb_flag = false; foreach( $searchEcCube->checkList as $file ){ $h1type = getH1Type($file['file']); if( $h1type != $now_h1type ){ $now_h1type = $h1type; $print_h1type = getH1typename($now_h1type); echo "<h1>$print_h1type</h1>"; $pb_flag = false; } if( $pb_flag == false ){ $pb_flag = true; } else { echo '<div style="page-break-after:always"></div>'."\n"; } $data_file = substr( $file['file'], 1 ); echo <<<__EOF__ <h2>{$file['title']}</h2> __EOF__; $url = seleniumPage($sfp, $file['file'] ); preg_match( '/LC_Page_(.*)\.php/', $file['file'], $match ); $pageName = strtolower( $match[1] ); $img_file = "capimg/{$pageName}.png"; list($width, $height, $type, $attr) = getimagesize($img_file); $img_filesize = filesize($img_file); if( 0 < $height && $height < 3000 ){ $img_cnt = floor( $height / $def_img_height ) + 1; echo '<h3>画面キャプチャ</h3>'; echo '<table border="1" style="width:'.$def_img_width.'px;"><tr><td>'; for( $i = 0 ; $i < $img_cnt ; $i++ ){ $img_file_url = "?file=capimg/{$pageName}.png&ct=$i&h=$def_img_height"; // 200以下の場合 $h = $def_img_height; if( $img_cnt-1 == $i ){ $h = $height % $def_img_height; } $h = floor( $h / ( 1.0 * $width / $def_img_width ) ); echo '<img src="' . $img_file_url . '" width="'.$def_img_width.'" height="'.$h.'">' . "<br />\n"; } echo "</td></tr></table>\n"; } else { // echo '<h3>画面キャプチャ</h3>'; // echo '画面なし'; } echo "<h3>ファイル</h3>\n"; echo $data_file. "<br />\n"; echo "<h3>URL</h3>\n"; echo $url. "<br />\n"; if( count( $file['data2'] ) ){ echo <<<__EOF__ <h3>パラメータ</h3> <table border=1 width="100%"> <tr> <th bgcolor="gray" width="20%">項目名</th> <th bgcolor="gray" width="15%">name</th> <th bgcolor="gray" width="10%">長さ</th> <th bgcolor="gray" width="10%">コンバート</th> <th bgcolor="gray" width="35%">チェック内容</th> <th bgcolor="gray" width="15%">デフォルト</th> <th bgcolor="gray" width="5%">DB入力</th> </tr> __EOF__; foreach( $file['data2'] as $data ){ echo "<tr>\n"; // 項目名 echo "<td>"; echo $data[0]; echo "</td>\n"; // name echo "<td>"; echo $data[1]; echo "</td>\n"; // 長さ echo "<td>"; echo $data[2]; echo "</td>\n"; // コンバート echo "<td>"; echo $data[3]; echo "</td>\n"; // チェック項目 echo "<td>\n"; echo implode(", ", arrayGetName( $data[4] ) ); echo "</td>\n"; // デフォルト echo "<td>"; echo $data[5]; echo "</td>\n"; // DB echo "<td>"; echo ($data[6])?"○":""; echo "</td>\n"; echo "</tr>\n"; } echo <<<__EOF__ </table> <br /> __EOF__; } if( count( $file['data3'] ) ){ echo <<<__EOF__ <h3>ファイルアップロード</h3> <table border=1 width="100%"> <tr> <th bgcolor="gray" width="20%">項目名</th> <th bgcolor="gray" width="15%">name</th> <th bgcolor="gray" width="20%">指定する拡張子</th> <th bgcolor="gray" width="10%">制限サイズ</th> <th bgcolor="gray" width="10%">必須</th> <th bgcolor="gray" width="10%">横幅</th> <th bgcolor="gray" width="10%">縦幅</th> <th bgcolor="gray" width="5%">画像</th> </tr> __EOF__; foreach( $file['data3'] as $data ){ echo "<tr>\n"; // 項目名 echo "<td>"; echo $data[0]; echo "</td>\n"; // name echo "<td>"; echo $data[1]; echo "</td>\n"; // 指定する拡張子 echo "<td>"; echo implode(", ", $data[2] ); echo "</td>\n"; // 制限サイズ echo "<td>"; echo $data[3]; echo "</td>\n"; // 必須 echo "<td>\n"; echo $data[4]; echo "</td>\n"; // 横幅 echo "<td>"; echo $data[5]; echo "</td>\n"; // 縦幅 echo "<td>"; echo $data[6]; echo "</td>\n"; // 画像 echo "<td>"; echo $data[7]; echo "</td>\n"; echo "</tr>\n"; } echo <<<__EOF__ </table> <br /> __EOF__; } if( count( $file['data'] ) ){ echo <<<__EOF__ <h3>入力チェック</h3> <table border=1 width="100%"> <tr> <th bgcolor="gray" width="20%">項目名</th> <th bgcolor="gray" width="10%">必須</th> <th bgcolor="gray" width="20%">入力種別</th> <th bgcolor="gray" width="10%">範囲チェック</th> <th bgcolor="gray" width="40%">チェック内容</th> </tr> __EOF__; foreach( $file['data'] as $data ){ echo "<tr>\n"; // 項目名 echo "<td>"; echo $data[0][0]; echo "</td>\n"; // 必須 $checkTypeList = array(); $checkTypeList[1] = array(); $checkTypeList[2] = array(); $checkTypeList[3] = array(); foreach( $data[1] as $checkName ){ $type = getCheckType($checkName); $checkTypeList[$type][] = $checkName; } echo "<td>\n"; echo implode("<br />", arrayGetName( $checkTypeList[1] ) ); echo "</td>\n"; echo "<td>\n"; echo implode("<br />", arrayGetName( $checkTypeList[2] ) ); echo "</td>\n"; echo "<td>\n"; echo implode("<br />", arrayGetName( $checkTypeList[3] ) ); echo "</td>\n"; // チェック項目 array_shift($data[0]); echo "<td>\n"; echo implode(", ", $data[0] ); echo "</td>\n"; echo "</tr>\n"; } echo <<<__EOF__ </table> __EOF__; } } seleniumFoot($sfp); fclose($sfp); class searchEcCubeClass{ var $arrAllowedTag = "<arrAllowedTag>"; var $arrReviewDenyURL = "<arrReviewDenyURL>"; var $checkList = array(); function lfGetSqlDenyList(){} function checkDir($path){ $list = scandir( $path ); foreach( $list as $file ){ $pathname = $path . "/" . $file; if( $file == '.' || $file == '..'){ continue; } if( is_dir( $pathname ) ){ $this->checkDir( $pathname ); } else if( preg_match( "/^LC_Page.*\.php$/", $file ) ){ $this->checkFile($pathname); } } } function checkFile($path){ $cnt = "#N#"; $no = "#No#"; $classcategory_id1 = "classcategory_id#id#_1"; $classcategory_id2 = "classcategory_id#id#_2"; $quantity = "quantity<id>"; $pageTitle = ""; $i = "#i#"; $checkList = array(); $checkList2 = array(); $checkList3 = array(); $objErr = new objErr; $objWebParam = new objErr; $masterData = new masterData; $this->objDate = new objDateClass; $this->objFormParam = new objErr; $this->objUpFile = new objErr; $this->lfGetSqlDenyList = new objErr; $objUpFile = new objErr; $objFormParam = new objErr; $objCheckCategory = new objErr; $arrList = array( "startyear_m" => "#startyear_m#", "startmonth_m" => "#startmonth_m#", "startyear" => "#startyear#", "startmonth" => "#startmonth#", "startday" => "#startday#", "endyear" => "#endyear#", "endmonth" => "#endmonth#", "endday" => "#endday#", ); $filebody = file_get_contents($path); $lines = file2lines($filebody); foreach( $lines as $line ){ if( preg_match( "/objErr->doFunc(.*);/", $line ) ){ eval( $line ); $checkList[] = array( $objErr->var1, $objErr->var2 ); } else if( preg_match( "/objCheckCategory->doFunc(.*);/", $line ) ){ eval( $line ); $checkList[] = array( $objCheckCategory->var1, $objCheckCategory->var2 ); } else if( preg_match( "/this->objFormParam->addParam(.*);/", $line ) ){ eval( $line ); $checkList2[] = array( $this->objFormParam->disp_name, $this->objFormParam->keyname, $this->objFormParam->length, $this->objFormParam->convert, $this->objFormParam->arrCheck, $this->objFormParam->default, $this->objFormParam->input_db ); } else if( preg_match( "/objFormParam->addParam(.*);/", $line ) ){ eval( $line ); $checkList2[] = array( $objFormParam->disp_name, $objFormParam->keyname, $objFormParam->length, $objFormParam->convert, $objFormParam->arrCheck, $objFormParam->default, $objFormParam->input_db ); } else if( preg_match( "/this->objUpFile->addFile(.*);/", $line ) ){ eval( $line ); $checkList3[] = array( $this->objUpFile->disp_name, $this->objUpFile->keyname, $this->objUpFile->arrExt, $this->objUpFile->size, $this->objUpFile->necessary, $this->objUpFile->width, $this->objUpFile->height, $this->objUpFile->image ); } else if( preg_match( "/objUpFile->addFile(.*);/", $line ) ){ eval( $line ); $checkList3[] = array( $objUpFile->disp_name, $objUpFile->keyname, $objUpFile->arrExt, $objUpFile->size, $objUpFile->necessary, $objUpFile->width, $objUpFile->height, $objUpFile->image ); } else if( preg_match( "/\*(.*)のページクラス/", $line, $match ) ){ $pageTitle = trim( $match[1] ); } } if( count( $checkList ) != 0 || count( $checkList2 ) != 0 || count( $checkList3 ) != 0 ){ $this->checkList[] = array( "title" => $pageTitle, "file" => $path, "data" => $checkList, "data2" => $checkList2, "data3" => $checkList3 ); } } } class objErr{ var $var1; var $var2; var $disp_name; var $keyname; var $length; var $convert; var $arrCheck; var $default; var $input_db; var $arrExt; var $size; var $necessary; var $width; var $height; var $image; function doFunc( $set_var1, $set_var2 ){ $this->var1 = $set_var1; $this->var2 = $set_var2; } function addParam($disp_name, $keyname, $length="", $convert="", $arrCheck=array(), $default="", $input_db="true") { $this->disp_name = $disp_name; $this->keyname = $keyname; $this->length = $length; $this->convert = $convert; $this->arrCheck = $arrCheck; $this->default = $default; $this->input_db = $input_db; } function addFile($disp_name, $keyname, $arrExt, $size, $necessary=false, $width=0, $height=0, $image=true){ $this->disp_name = $disp_name; $this->keyname = $keyname; $this->arrExt = $arrExt; $this->size = $size; $this->necessary = $necessary; $this->width = $width; $this->height = $height; $this->image = $image; } } class masterData{ function getMasterData(){ return "#URL#"; } } class objDateClass{ function getStartYear(){ return "#StartYear#"; } function getEndYear(){ return "#EndYear#"; } } function arrayGetName( $array ){ $ret = array(); foreach( $array as $data ){ $ret[] = getName( $data ); } return $ret; } function getName( $str ){ switch( $str ){ case "EXIST_CHECK": return "必須"; case "ALL_EXIST_CHECK": return "すべて必須"; case "TOP_EXIST_CHECK": return "上位が入力済み"; case "ONE_EXIST_CHECK": return "どれか選択"; case "FILE_EXISTS": return "ファイルの存在チェック"; case "NUM_CHECK": return "数値"; case "GRAPH_CHECK": return "英数記号"; case "KANA_CHECK": return "カナ"; case "EMAIL_CHECK": return "E-Mail形式"; case "MOBILE_EMAIL_CHECK": return "携帯Mail"; case "EMAIL_CHAR_CHECK": return "E-Mail文字種"; case "TEL_CHECK": return "電話形式"; case "ZERO_CHECK": return "0チェック"; case "ALPHA_CHECK": return "半角英字"; case "CHECK_DATE": return "日付形式(ymd)"; case "CHECK_DATE2": return "日付形式(ymd-hm)"; case "KANABLANK_CHECK": return "カナ(スペース許可)"; case "ALNUM_CHECK": return "英数"; case "FILE_NAME_CHECK": return "ファイル名形式"; case "FILE_NAME_CHECK_BY_NOUPLOAD": return "ファイル名形式(アップロード以外)"; case "HTML_TAG_CHECK": return "Htmlタグ"; case "URL_CHECK": return "URL形式"; case "IP_CHECK": return "IPアドレス"; case "PROHIBITED_STR_CHECK": return "禁止文字"; case "SPTAB_CHECK": return "スペース・タブのみ"; case "NO_SPTAB": return "スペース・タブ"; case "ZERO_START": return "先頭が0"; case "MAX_LENGTH_CHECK": return "最大"; case "NUM_COUNT_CHECK": return "桁数"; case "CHECK_SET_TERM": return "期間チェック"; case "CHECK_SET_TERM2": return "期間チェック(秒まで)"; case "NUM_RANGE_CHECK": return "最小・最大"; case "GREATER_CHECK": return "値比較(<)"; case "DIFFERENT_CHECK": return "値比較(!=)"; case "SELECT_CHECK": return "リスト範囲内"; case "EQUAL_CHECK": return "一致"; case "MIN_CHECK": return "最小"; case "MAX_CHECK": return "最大"; case "MIN_LENGTH_CHECK": return "最小長チェック"; } return "####" . $str; } function getCheckType( $str ){ switch( $str ){ case "EXIST_CHECK": // 必須 case "ALL_EXIST_CHECK": // すべて必須 case "TOP_EXIST_CHECK": // 上位が入力済み case "ONE_EXIST_CHECK": // どれか選択 case "FILE_EXISTS": // ファイルの存在チェック return 1; case "NUM_CHECK": // 数値 case "GRAPH_CHECK": // 英数記号 case "KANA_CHECK": // カナ case "EMAIL_CHECK": // E-Mail形式 case "MOBILE_EMAIL_CHECK": // 携帯Mail case "EMAIL_CHAR_CHECK": // E-Mail文字種 case "TEL_CHECK": // 電話形式 case "ZERO_CHECK": // 0チェック case "ALPHA_CHECK": // 半角英字 case "CHECK_DATE": // 日付形式(ymd) case "CHECK_DATE2": // 日付形式(ymd-hm) case "KANABLANK_CHECK": // カナ(スペース許可) case "ALNUM_CHECK": // 英数 case "FILE_NAME_CHECK": // ファイル名形式 case "FILE_NAME_CHECK_BY_NOUPLOAD": // ファイル名形式(アップロード以外) case "HTML_TAG_CHECK": // Htmlタグ case "URL_CHECK": // URL形式 case "IP_CHECK": // IPアドレス case "PROHIBITED_STR_CHECK": // 禁止文字 case "SPTAB_CHECK": // スペース・タブのみ case "NO_SPTAB": // スペース・タブ case "ZERO_START": // 先頭が0 return 2; case "MAX_LENGTH_CHECK": // 最大 case "NUM_COUNT_CHECK": // 桁数 case "CHECK_SET_TERM": // 期間チェック case "CHECK_SET_TERM2": // 期間チェック(秒まで) case "NUM_RANGE_CHECK": // 最小・最大 case "GREATER_CHECK": // 値比較(<) case "DIFFERENT_CHECK": // 値比較(!=) case "SELECT_CHECK": // リスト範囲内 case "EQUAL_CHECK": // 一致 case "MIN_CHECK": // 最小 case "MAX_CHECK": // 最大 case "MIN_LENGTH_CHECK": // 最小長チェック return 3; } return -1; } function getH1Type( $file ){ if( preg_match( "/\/data\/class\/pages\/(.*?)\//", $file, $match ) ){ $ret = trim( $match[1] ); return $ret; } return null; } function getH1typename( $name ){ switch( $name ){ case "": return "全体"; case "admin": return "管理者"; case "regist": return "登録"; case "shopping": return "ショッピング"; case "products": return "商品"; case "campaign": return "キャンペーン"; case "contact": return "お問い合わせ"; case "entry": return "会員登録"; case "inquiry": return "アンケート"; case "mypage": return "MyPage"; case "frontparts": return "フロントパーツ"; case "tb": return "トラックバック"; case "cart": return "カート"; case "forgot": return "パスワード発行"; } return "####" . $name; } function seleniumHead( $fp ){ global $def_base_url; global $def_user_id; global $def_user_pass; global $def_user_id2; global $def_user_pass2; global $def_out_dir; global $def_product_id; $str =<<<__ECHO__ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head profile="http://selenium-ide.openqa.org/profiles/test-case"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>test</title> </head> <body> <table cellpadding="1" cellspacing="1" border="1"> <thead> <tr><td rowspan="1" colspan="3">test</td></tr> </thead><tbody> <tr> <td>open</td> <td>{$def_base_url}/mypage/login.php</td> <td></td> </tr> <tr> <td>type</td> <td>css=dd > input[name="login_email"]</td> <td>$def_user_id2</td> </tr> <tr> <td>type</td> <td>css=dd > input[name="login_pass"]</td> <td>$def_user_pass2</td> </tr> <tr> <td>clickAndWait</td> <td>id=log</td> <td></td> </tr> <tr> <td>open</td> <td>{$def_base_url}/products/detail.php?product_id={$def_product_id}</td> <td></td> </tr> <tr> <td>clickAndWait</td> <td>id=cart</td> <td></td> </tr> <tr> <td>open</td> <td>{$def_base_url}/admin/</td> <td></td> </tr> <tr> <td>type</td> <td>login_id</td> <td>$def_user_id</td> </tr> <tr> <td>type</td> <td>password</td> <td>$def_user_pass</td> </tr> <tr> <td>clickAndWait</td> <td>css=span</td> <td></td> </tr> __ECHO__; fwrite( $fp, $str ); } function seleniumFoot( $fp ){ $str =<<<__ECHO__ </tbody></table> </body> </html> __ECHO__; fwrite( $fp, $str ); } function seleniumPage( $fp, $name ){ global $def_base_url; global $def_out_dir; global $def_urllist; global $def_user_id; global $def_user_pass; global $def_nodisp_urllist; preg_match( '/LC_Page_(.*)\.php/', $name, $match ); $match[1] = preg_replace('/([A-Z][a-z]+)([A-Z][a-z]+)$/', '$1#$2', $match[1]); $pageName = strtolower( $match[1] ); $pageName = str_replace( "#", "", $pageName ); $pagePath = strtolower( $match[1] ); $pagePath = str_replace( "_", "/", $pagePath,$ct ); $pagePath = str_replace( "#", "_", $pagePath ); $url = "{$def_base_url}/$pagePath"; if( strpos($pagePath, "admin/") === 0 && $ct === 1 ){ // index $url .= '/index.php'; } else if( $ct === 0 ) // index $url .= '/index.php'; else { $url .= '.php'; } if( isset( $def_urllist[ $url ] ) ){ $url = $def_urllist[ $url ]; } if( array_search( $url, $def_nodisp_urllist ) === false ){ $str =<<<__ECHO__ <tr> <td>open</td> <td>$url</td> <td></td> </tr> <tr> <td>captureEntirePageScreenshot</td> <td>{$def_out_dir}\\{$pageName}.png</td> <td></td> </tr> __ECHO__; } // ログイン画面 if( $url === $def_base_url . '/admin/index.php' ){ $str .=<<<__ECHO__ <tr> <td>type</td> <td>login_id</td> <td>$def_user_id</td> </tr> <tr> <td>type</td> <td>password</td> <td>$def_user_pass</td> </tr> <tr> <td>clickAndWait</td> <td>css=span</td> <td></td> </tr> __ECHO__; } fwrite( $fp, $str ); return $url; } function dispImg(){ $src_img = imagecreatefrompng($_GET['file']); $img_height = @$_GET['h']; if( $img_height == 0 ){ $img_height = 200; } $top = @$_GET['ct'] * $img_height; $src_width = imagesx($src_img); $src_height = imagesy($src_img); $img_width = $src_width; if( $src_height < $top + $img_height ){ $img_height = $src_height % $img_height; } $dst_img = imagecreatetruecolor($img_width, $img_height); imagecopy($dst_img, $src_img, 0, 0, 0, $top, $img_width, $img_height); header('Content-Type: image/png'); imagepng( $dst_img ); imagedestroy($dst_img); } function file2lines($file){ $token = token_get_all($file); $lines = array(); $line = ''; $case_flag = false; foreach($token as $key=>$data){ if( is_string($data) ){ if( $data === '{' || $data === '}' || ( $case_flag === true && $data === ':' ) || $data === ';' ) { $lines[] = $str = ereg_replace("\r|\n","", trim( $line . $data ) ); $line = ''; } else { $line .= $data; } } else { $skip_token = array( T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG, ); if( $data[0] == T_CASE || $data[0] == T_DEFAULT ){ // case start $case_flag = true; } else if( array_search( $data[0], $skip_token ) !== false){ // reg $lines[] = ereg_replace("\r|\n","", trim( $line . $data[1] ) ); $line = ''; } else { $line .= $data[1]; } $token[$key][99] = token_name($data[0]); } } return $lines; }
Teedaネスト対応 途中経過
コミッタにならさせていただきました!
これから頑張ります
いろいろ考えてはいるのですが、ダイナミックプロパティとかに対応しないとダメですね。完全に想定から抜けていました。
利用するための条件
デフォルトは利用しないにしたいと思っています。やっぱり後方互換性がなくなるのと、処理的に遅くなる可能性があるからです。大抵のプロジェクトは利用しても問題はでないとは思っています。
設定自体は teedaCustomize.dicon を利用したいかなと思っています。
ここはDoltengに入るファイルにも影響でますね
利用方法
まだ悩み中ですが_で区切った名前があったらメンバーのプロパティーを調べるのがいいかなと思っています。-とかの文字がいいんだけれど、それ使うと既存の処理にかむので手を入れる範囲が広がりそうですし、処理的に遅くなりそうです。
Teedaネスト対応
これで実験してから一年半ぐらい経過していますが、組んでみました。
data3Dto = new Data3Dto(); data3Dto.data5 = "DataText5"; data3Dto.data6 = "DataText6"; data3Dto.data2Dto = new Data2Dto(); data3Dto.data2Dto.data3 = "DataText3"; data3Dto.data2Dto.data4 = "DataText4"; data3Dto.data2Dto.dataDto = new DataDto(); data3Dto.data2Dto.dataDto.data1 = "DataText1"; data3Dto.data2Dto.dataDto.data2 = "DataText2";
こんな感じのデータが
<span id="data3Dto_data5">data3Dto_data5</span><br /> <span id="data3Dto_data6">data3Dto_data6</span><br /> <span id="data3Dto_data2Dto_data3">data3Dto_data2Dto_data3</span><br /> <span id="data3Dto_data2Dto_data4">data3Dto_data2Dto_data4</span><br /> <span id="data3Dto_data2Dto_dataDto_data1">data3Dto_data2Dto_dataDto_data1</span><br /> <span id="data3Dto_data2Dto_dataDto_data2">data3Dto_data2Dto_dataDto_data2</span><br /> <span id="data3Dto_data2Dto_dataDto_data3">data3Dto_data2Dto_dataDto_data3</span><br />
こんな感じで利用できます。
最後のdata3Dto_data2Dto_dataDto_data3はプロパティーがないので、データが入らない状態ででます。標準だと例外エラーになるのでここのチェックが一番面倒だと思います。
以下パッチですが、
http://akira.info/labs/NestTeeda/teeda.patch
http://akira.info/labs/NestTeeda/teedaNest20110224.lzh
ここにパッチと、サンプルで動いているプロジェクト起きました。
Eclipseのパッチはなんかおかしい。。。ひとつだけ適応されないところがでてきています(涙)
もう少しテストを書く必要があるけれど、普通にデータ表示するだけだったらこれでいいのかな?
性能実験やっていないので、もう少し速度的な最適化はできるきがします。
Index: teeda-extension/src/test/java/org/seasar/teeda/extension/html/impl/TagProcessorAssembleImplTest.java =================================================================== --- teeda-extension/src/test/java/org/seasar/teeda/extension/html/impl/TagProcessorAssembleImplTest.java (revision 4307) +++ teeda-extension/src/test/java/org/seasar/teeda/extension/html/impl/TagProcessorAssembleImplTest.java (working copy) @@ -450,6 +450,10 @@ return false; } + public boolean hasNestProperty(String name) { + return false; + } + public boolean isModified() { return false; } Index: teeda-extension/src/test/java/org/seasar/teeda/extension/html/impl/page/AaaDto.java =================================================================== --- teeda-extension/src/test/java/org/seasar/teeda/extension/html/impl/page/AaaDto.java (revision 4307) +++ teeda-extension/src/test/java/org/seasar/teeda/extension/html/impl/page/AaaDto.java (working copy) @@ -19,10 +19,11 @@ /** * @author higa - * + * */ public class AaaDto implements Serializable { public static final String COMPONENT = "instance=session"; + public String aaaBbb; } Index: teeda-extension/src/test/java/org/seasar/teeda/extension/html/impl/PageDescImplTest.java =================================================================== --- teeda-extension/src/test/java/org/seasar/teeda/extension/html/impl/PageDescImplTest.java (revision 4307) +++ teeda-extension/src/test/java/org/seasar/teeda/extension/html/impl/PageDescImplTest.java (working copy) @@ -43,6 +43,15 @@ assertFalse(pd.hasProperty(null)); } + public void testHasNestProperty() throws Exception { + PageDesc pd = createPageDesc(FooPage.class, "fooPage"); + assertFalse(pd.hasNestProperty("aaa")); + assertFalse(pd.hasNestProperty("aaaDto")); + assertTrue(pd.hasNestProperty("aaaDto_aaaBbb")); + assertFalse(pd.hasNestProperty(null)); + } + + public void testHasItemsProperty() throws Exception { PageDesc pd = createPageDesc(FooPage.class, "fooPage"); assertTrue(pd.hasItemsProperty("cccItems")); Index: teeda-extension/src/main/java/org/seasar/teeda/extension/html/impl/PageDescImpl.java =================================================================== --- teeda-extension/src/main/java/org/seasar/teeda/extension/html/impl/PageDescImpl.java (revision 4307) +++ teeda-extension/src/main/java/org/seasar/teeda/extension/html/impl/PageDescImpl.java (working copy) @@ -17,6 +17,7 @@ import java.io.File; import java.io.Serializable; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -72,6 +73,8 @@ private static final String[] EMPTY_SCOPES = new String[0]; + private Class pageClass; + public PageDescImpl(Class pageClass, String pageName) { this(pageClass, pageName, null); } @@ -90,6 +93,8 @@ } protected void setup(Class pageClass) { + this.pageClass = pageClass; + BeanDesc beanDesc = BeanDescFactory.getBeanDesc(pageClass); for (int i = 0; i < beanDesc.getPropertyDescSize(); ++i) { PropertyDesc pd = beanDesc.getPropertyDesc(i); @@ -172,6 +177,57 @@ return propertyNames.contains(name); } + public boolean hasNestProperty(String name) { + if( name == null ){ + return false; + } + + BeanDesc beanDesc = BeanDescFactory.getBeanDesc(pageClass); + String[] items = name.split("_"); + + if( items.length < 2 ){ + return false; + } + + for (int i = 0; i < beanDesc.getPropertyDescSize(); ++i) { + PropertyDesc pd = beanDesc.getPropertyDesc(i); + String propertyName = pd.getPropertyName(); + if( items[0].equals(propertyName) ){ + String[] items2 = new String[items.length-1]; + for( int j = 0 ; j < items.length-1; j++ ){ + items2[j] = items[j+1]; + } + return hasNestPropertySub( items2, pd.getPropertyType() ); + } + + } + + return false; + } + + private boolean hasNestPropertySub(String[] items,Class propertyClass) { + Field propertyFields[] = propertyClass.getDeclaredFields(); + + for( int i = 0 ; i < propertyFields.length ; i++ ){ + String fieldsName = propertyFields[i].getName(); + if( items[0].equals(fieldsName) ){ + if( items.length == 1 ){ + return true; + } else { + String[] items2 = new String[items.length-1]; + for( int j = 0 ; j < items.length-1; j++ ){ + items2[j] = items[j+1]; + } + Class fieldsClass = propertyFields[i].getType(); + + return hasNestPropertySub( items2, fieldsClass ); + } + } + } + + return false; + } + public boolean hasItemsProperty(String name) { return itemsPropertyNames.contains(name); } Index: teeda-extension/src/main/java/org/seasar/teeda/extension/html/PageDesc.java =================================================================== --- teeda-extension/src/main/java/org/seasar/teeda/extension/html/PageDesc.java (revision 4307) +++ teeda-extension/src/main/java/org/seasar/teeda/extension/html/PageDesc.java (working copy) @@ -24,6 +24,8 @@ boolean hasProperty(String name); + boolean hasNestProperty(String name); + /** * ForEach繧ТelectOneMenu縺ァ縺ョPage繧ッ繝ゥ繧ケ縺ォ謖√▽Collection縺後≠繧句?蜷医↓true繧定ソ斐☆. * @param name Index: teeda-extension/src/main/java/org/seasar/teeda/extension/html/factory/OutputTextFactory.java =================================================================== --- teeda-extension/src/main/java/org/seasar/teeda/extension/html/factory/OutputTextFactory.java (revision 4307) +++ teeda-extension/src/main/java/org/seasar/teeda/extension/html/factory/OutputTextFactory.java (working copy) @@ -67,6 +67,9 @@ if (isLabel(id, elementNode)) { return true; } + if (isNest(id, pageDesc)) { + return true; + } return pageDesc.hasProperty(id); } @@ -88,6 +91,11 @@ if (pageDesc.hasProperty(id)) { properties.put(JsfConstants.VALUE_ATTR, getBindingExpression( pageDesc.getPageName(), id)); + } else if( isNest(id, pageDesc) ){ + String[] items = id.split("_"); + String itemName = implode( items, "." ); + properties.put(JsfConstants.VALUE_ATTR, getBindingExpression( + pageDesc.getPageName(), itemName)); } else { final String key = toNormalizeId(id); TextNode firstTextNode = elementNode.getFirstTextNode(); @@ -96,6 +104,26 @@ } } + private static String implode(Object[] array, String sep) { + if (array == null) { + return null; + } else if (array.length < 1) { + return ""; + } else if (array.length < 2) { + return (array[0] != null) ? array[0].toString() : ""; + } + + StringBuffer buf = new StringBuffer( + (array[0] != null) ? array[0].toString() : ""); + + for (int i = 1; i < array.length; i++) { + buf.append(sep); + buf.append((array[i] != null) ? array[i].toString() : ""); + } + + return buf.toString(); + } + protected boolean isLabel(final String id, final ElementNode elementNode) { final String key = toNormalizeId(id); if (!TeedaExtensionConfiguration.getInstance().outputTextLabelUnderAnchorOnly) { @@ -115,6 +143,20 @@ } } + protected boolean isNest(final String id, PageDesc pageDesc) { + if( id == null ){ + return false; + } + + String[] items = id.split("_"); + + if( items.length < 2 ){ + return false; + } + + return pageDesc.hasNestProperty(id); + } + protected String toNormalizeId(String id) { final int pos = id.lastIndexOf('-'); if (pos >= 0) {
EthnaのURLを綺麗にする(Ethna_UrlHandlerを使う方法)
やっぱりEthnaのURLって微妙だよね?
てなことで、前々から興味があったけれどやっていなかったのを調べてみました。
似た話題
http://rd.uniba.jp/blog/2010/06/30/ethna-%E3%81%A7%E7%B6%BA%E9%BA%97%E3%81%AA-url-ethna_urlhandler-%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%84%E6%96%B9%E6%B3%95/
Ethna で綺麗な URL (Ethna_UrlHandler を使わない方法)
ここが最初に検索するとひっかかります。
うん、いまちゃんと読んだけれどこっちの方が楽です(笑)
でも。。。
example.org/hogehoge/fugagufa/?id=10
こうじゃなくって
example.org/hogehoge/fugagufa/10
こうしたいときってありますよね?
まあ、私はあまりないのですが(汗)
方針
Ethna_UrlHandlerがあるので、Ethna_UrlHandlerを拡張して再現したいと思います。ただし、Ethna_UrlHandlerは事前にすべてのアクションを登録しておく必要があるので、非常に面倒です。。。
流れ的にはアクセスしてきたURLをみて、動的にアクションとの紐付けを設定してあげます。
コード(pjname\app\Pjname_Testethna_UrlHandler.php)
<?php function &getInstance($class_name = null) { $instance =& parent::getInstance(__CLASS__); // add $path = parent::_normalizePath($_SERVER['PATH_INFO']); $acrion_list = split( "/", $path[0] ); $ctl = &Ethna_Controller::getInstance(); while( count($acrion_list) ){ $action_name = implode( "_", $acrion_list ); if( $ctl->getActionClassName($action_name) ){ break; } array_pop($acrion_list); } $url_path = implode( "/", $acrion_list ); $instance->action_map = array( $_SERVER['URL_HANDLER'] => array( $action_name => array( 'path' => $url_path, 'path_regexp' => '|^'.$url_path.'/(.*)$|', 'path_ext' => array('url_param' => array()), ), ), ); // add-end return $instance; }
上記の部分に無理やり突っ込んでいます。内容はPATH_INFOを取ってきて、整形後にgetActionClassName()を利用して、実在するAction名を探して、それ以降の/はパラメータとしています。
準備
http://ethna.jp/ethna-document-dev_guide-urlhandler.html
ここを参考にして、事前準備が必要です。
$action_map の設定
ここは動的に作っているので必要ありません
_getPath_Index() 関数の定義
ここは動的に作成できないので、{url}を使うことができません。なので設定の必要はありません。
URL_HANDLER 変数の設定
これは必要です。
$config['url'] の設定
なくても動きますが、設定したほうがよいかもしれません。(未検証)
echo_msgアクションを追加
対象アクションは必要です。
値の受け取り方とアクセスURL
example.org/index.php?action_echo_msg=true
通常のURLが上記の場合
example.org/index.php/echo/msg
でアクセスができるようになります。サーバーによっては拡張子を除くことができるので
example.org/index/echo/msg
でも可能です。index.phpの名前を変更することでもっと短い名前にもできますし、mod_rewriteを利用して無くすことも可能ですね。
パラメーターの受け取り方
<?php var $form = array( 'url_param' => array(), );
上記のように設定することで index.php/echo/msg/123/456/789 のアクセスの場合 123/456/789 が取り出せます。このままだと使いにくいので
<?php var $form = array( 'num' => array( 'type' => VAR_TYPE_INT, 'form_type' => FORM_TYPE_TEXT ), 'url_param' => array(), );
上記のように分解して入れる変数を用意し
<?php function prepare() { $_url_param = split( "/", $this->af->get('url_param' ) ); $this->af->set('num', $_url_param[0] ); var_dump($this->af->validate()); return null; }
上記のように分解してから、パラメータを代入してvalidateをかけることで安全に利用することができます。もしくは index.php/echo/msg/0/?num2=1244 とかでもデータは取れます。
総括
本当はパラメータのどこが、どこに対応するのかをまでafの設定の中にいれることで、パラメータの代入まで自動化できると思います。
<?php var $form = array( 'num' => array( 'type' => VAR_TYPE_INT, 'form_type' => FORM_TYPE_TEXT, 'url_param_num' => 0 ), );
上記みたいに紐付けて、url_param_numがついているパラメータを自動代入がきれいかな。。。
まあ、アクション名をecho_msgみたいな形で妥協すればもっと簡単になるんですが、そこはきれいじゃないですよね?
JPUG 2010 年夏セミナーにいってきた
http://www.postgresql.jp/events/soukai2010/
PostgreSQL のこれまで、9.0、そしてこれから
非常に興味深いコマでした。ここが今回の目玉ですね!
年表をみて思ったのですが、7.0がない(笑)
私は6系から使っていたのですが、7.0にバージョンアップして痛い目みています(苦笑)
どうも初期はあまり安定していなかったイメージがあって6に戻しました。。。
7.2からVACUUMがロックしなくなったのかな? 個人的にはここが一番大きな転機な気がします!
でも新しい9はかなりの期待! レプリケーションが標準でついて、エンタープライズでも使われるようになるかな? 外国だと何故かMySQLの方がエンタープライズ用途って感じなんですよねー
レプリケーションは9で組むとして、ロードバランサー的なものは何を使った方がいいんだろう?
現状pgpool-IIが9に対応中みたいです。マスターにしか投げないクエリや非同期の誤差が多い場合にはスレイブに投げないなどかなり充実した機能があります!
ただ更新系と参照系で2つコネクション張って、使い分けるんだったらPgBouncerとかPL/Proxyも単機能だからいいよーって、懇談会で知り合いの理事の人に教えていただきました!
むー、ちょっといろいろ検証する必要があるな!
PostgreSQL on Amazon EC2 の可能性
クラウドでの構成例です!
障害時の自動起動の話ですが、たしかにクラウドだといろいろリアルサーバーとは違う運用になりますよね。。。
今度使うクラウドサーバーがどんな仕様かを調べなければ。。。
EC2の場合、待機系を起動しておくとインスタンスの分お金かかるけれど、月7000円を取るかコールドスタンバイで数分待つのどっちがいいのかって話が良かったです(笑)
まあ、この構成の場合にはそう落ちないよね。。。落ちるときには全体が落ちるだろうし(笑)
SRA OSS による PostgreSQL導入事例とサービス活用事例のご紹介
スライド資料なし。
ここはSRAってより石井さんのいる会社ってイメージが強い。。。
VoIP サービスにおけるPostgreSQLの活用
んー
この辺からどんどんPostgreSQLの成分が減ってきます(笑)
やっぱりインフラ系の会社って急にサービスが立ち上がって、いろいろ大変なんだなーって感じでした。。。
ほとんどDB関係ない実装に関しての話ですが、スライドの最後のページは必見!
ポケットの中の PostgreSQL 〜OpenBlockS での活用事例のご紹介〜
これちょっと使ってみたいと昔に思ったんですよね。その時にはHinemosはまだ入っていなかったと思いますが。。。
Hinemos自体は非常に知名度あるけれど、あまり使っているのは聞かない。。。そして情報もないので検証だけして、導入していない事例がかなりな気がします。
もう少し普及してから有料サポートを厚くしていかないとなかなか広がっていかないかもしれませんね。
懇親会
暑かった。。。
あまり沢山の人とは話していないのですが、結構業界狭いですよね(笑)
あとアンケートの結果を速報していましたが、DBとの関わりがDBエンジニア、PG/SE、ITその他みたいな感じだったので、ITその他が多かったみたいです(笑)
私もその他にしました!
PGだけだったら、PGにしますがSEが入るとアプリケーションエンジニアってよりはメンバーってなるので、その他にしました(笑)
たぶんアーキテクトとかがあればもう少し分布がわかったんじゃないかなと思います
b-mobileSIMについて
徐々に特性についてわかってきました。
普通に使う分にはそんなに不便はないですね。
ただし、何個か制限かけているみたいです。。。
- flaの拡張子はダウンロード速度が制限されている
- Windows Updateはできない
- Skypeのダウンロードも速度制限されている
- iTunesからiPhoneアプリのも速度制限されている
- イーバンク銀行にログインできない
mp4とかのダウンロードは大丈夫なんですが、flaの場合秒間1K以下に制限かかっているようです。毎秒900バイトぐらいしかダウンロードできないので、実質動画を見ることができません。
特定のサイトと、特定のファイルについて制限がかかっているみたいですね。
この回線だけで全部まかなうのはちょっと難しいかもしれません。
たまに無線LANとかで他のネットワークに接続して、ダウンロードとかする必要あります。
特にWindows Updateができないので、ちょっと不安かなー。
この辺はb-mobileSIMで制限をかけている場合と、サーバー側で制限をかけている場合があると思いますので、一概にどこがいけないってなかなかわからないのですが、flaファイルは実際同じサーバーからテストでダウンロードしてみたら明らかに遅くなりました。
イーバンク銀行とかは、通信回線からドコモと認識されて、携帯電話用のサイトからログインしてくださいと怒られました。この手のはサーバー側ですね。
ドメインで制限かかっている場合にはダウンロード時だけWebProxyとか使えば問題ないのですが、拡張子とかはちょっと回避方法分かりません。。。
まあ、外出先でネットするみたいな一般用途には十分かなー?