2012年11月26日月曜日

写真投稿テスト

写真投稿テスト


2012年11月23日金曜日

FB連携テスト

テストPOST

2012年4月14日土曜日

HerokuでRuby1.9.3 + Rails3.2環境構築


HerokuでRuby1.9.3 + Rails3.2環境構築
参考)http://railsapps.github.com/rails-heroku-tutorial.html


■0. はじめに

HerokuはRuntime環境を"stack"という管理概念で保持している。
・Stackの一覧
=> https://devcenter.heroku.com/articles/stack

デフォルトのstackはBamboo。
Ruby1.9系はCedarを使う。
※cedarは2012/04/14現在、public beta。


■1. herokuプロジェクト作成

・プロジェクト作成

# gitが居なければ初期化
# git init
$ heroku create {myapp} --stack cedar

# stackの確認
$ heroku stack
  aspen-mri-1.8.6
  bamboo-mri-1.9.2
  bamboo-ree-1.8.7
* cedar (beta)

$ heroku ps
Process  State           Command
-------  --------------  -------
web.1    created for 4m

・Rubyバージョンの確認

$ heroku run console
Running console attached to terminal... up, run.1
sh: console: not found
# cedarではタスクの実行は"heroku run hoge"
# rakeの実行は"heroku run rake db:reset"など
# まだrailsプロジェクトをデプロイしてないのでconsoleは動かない
# ここでconsoleが起動するようだと、後々ちょっと迷う。(後述)

・環境確認

$ heroku info
=== {myapp}
Git URL:       git@heroku.com:{myapp}.git
Owner:         myapp@example.com
Stack:         cedar
Web URL:       http://{myapp}.herokuapp.com/

$heroku config
# レスポンスなし


■2. ruby1.9.3導入
http://railsapps.github.com/rails-heroku-tutorial.html

$ heroku labs:enable user_env_compile -a {myapp}
----> Enabling user_env_compile for {myapp}... done
WARNING: This feature is experimental and may change or be removed without notice.

$ heroku config:add RUBY_VERSION=ruby-1.9.3-p125
Adding config vars and restarting app... done, v2
  RUBY_VERSION => ruby-1.9.3-p125

$ heroku config:add PATH=bin:vendor/bundle/ruby/1.9.1/bin:/usr/local/bin:/usr/bin:/bin
Adding config vars and restarting app... done, v3
  PATH => bin:vendor/bundl...in:/usr/bin:/bin

$heroku config
PATH         => bin:vendor/bundle/ruby/1.9.1/bin:/usr/local/bin:/usr/bin:/bin
RUBY_VERSION => ruby-1.9.3-p125


■3. railsデプロイ

cedarはデフォルトでwebrickが動く。
thisサーバに変更しておく。

# RAILS_ROOT/Gemfile
+ group :production do
+   gem 'thin'
+ end

・デプロイ実行

# git add .
# git commit -m "foo"
$ git push heroku master

・環境確認

$ heroku ps
Process  State       Command
-------  ----------  ------------------------------------
web.1    up for 20s  bundle exec thin start -R config.r..
# Thisサーバで実行されている。

$ heroku config
DATABASE_URL        => foobar
GEM_PATH            => vendor/bundle/ruby/1.9.1
LANG                => en_US.UTF-8
PATH                => bin:vendor/bundle/ruby/1.9.1/bin:/usr/local/bin:/usr/bin:
/bin
RACK_ENV            => production
RAILS_ENV           => production
RUBY_VERSION        => ruby-1.9.3-p125
SHARED_DATABASE_URL => foobar

$heroku run console
Loading production environment (Rails 3.2.1)
irb> RUBY_VERSION
RUBY_VERSION
=> "1.9.3

ここで「2. ruby1.9.3導入」のステップを踏んでいるにも関わらず1.9.2が返ってくる場合は、恐らく1.9.3を導入してからアプリのデプロイを行っていない。
もう一度git push heroku masterするとrubyが1.9.3になる。
コマンドベースで反映できるのかどうかは分からない。
※heroku restartしてもダメだった。


■4. 困ったら見るサイト

http://d.hatena.ne.jp/language_and_engineering/20110914/p1
http://d.hatena.ne.jp/xyk/20101102
http://railsapps.github.com/rails-heroku-tutorial.html
https://devcenter.heroku.com/articles/rails3
https://devcenter.heroku.com/articles/rails3x-asset-pipeline-cedar

2012年2月26日日曜日

Zend Framework + WURFLでPC、携帯、スマホのUA判定


いつまで経ってもUA判定面倒だぜ。。

って、やっぱりみんな思ってる。
WURFL(Wireless Universal Resource FiLe)というモバイル判定を行うデータベースが整備されています。

ZendのUserAgent判定にWURFLを組み込む方法。

参考)
http://framework.zend.com/manual/ja/zend.http.user-agent.html
http://framework.zend.com/manual/en/zend.http.user-agent-features-wurfl.html


0. 環境

PHP 5.3.10
Zend 1.11.10
wurfl-php-1.3.1


1. WURFLのダウンロードと配置

WURFL PHPをダウンロード。
http://sourceforge.net/projects/wurfl/files/
http://sourceforge.net/projects/wurfl/files/WURFL%20PHP/

任意のディレクトリに展開してOKだけど、Zendのドキュメントに書いてある通り、application/libraryに展開します。

library
 └wurfl-php-1.3.1
  ├docs
  ├examples
  ├tests
  ├tools
  ├WURFL
  ├COPYING
  └README


2. キャッシュディレクトリの設定

WURFLデータベースのストレージとしてFile、Memcache、Memory、Mysqlの4つが使えるっぽいです。

以下、簡単そうなFileストレージを採用します。
WURFLのXMLファイルをリクエスト毎にパースしていたら大変なのでFileストレージは最初のアクセス時にキャッシュを作っておきます。

/wurfl-php-1.3.1/WURFL/Storage/File.php
を見てみると、デフォルトのキャッシュパスが/var/tmpになっています。

これをZendのドキュメントに倣って
APPLICATION_ROOT/data/wurfl/cache
とします。

mkdir -p data/wurfl/cache
chmod -R o+rwX data/wurfl/cache


3. データファイルコピー

wurfl-php-1.3.1/tests/resources/
配下のWURFLデータベースを先ほど作ったdataディレクトリにコピーします。

cp library/wurfl-php-1.3.1/tests/resources/wurfl-2.0.27.zip data/wurfl/
cp library/wurfl-php-1.3.1/tests/resources/web_browsers_patch.xml data/wurfl/


4. 設定

Zend先生は
application/config/wurfl-config.php
を作って、
application.ini
からwurfl-config.phpをロードしなさい、と言われていますが、キャッシュディレクトリの設定が上手くいきません。

参考)
<?php
$resourcesDir            = dirname(__FILE__) . '/../../data/wurfl/';
 
// $wurfl['main-file']      = $resourcesDir  . 'wurfl-latest.zip';
$wurfl['main-file']      = $resourcesDir  . 'wurfl-2.0.27.zip';
$wurfl['patches']        = array($resourcesDir . 'web_browsers_patch.xml');
 
$persistence['provider'] = 'file';
$persistence['dir']      = $resourcesDir . '/cache/';
 
$cache['provider']       = null;
 
$configuration['wurfl']       = $wurfl;
$configuration['persistence'] = $persistence;
$configuration['cache']       = $cache;

この辺りのドキュメントのwurflapi.wurfl_config_array.*パラメタをwurfl-config.phpで設定しておきなさい、ということのようですが、
wurflapi.wurfl_config_array.persistence.dir
が効かないっぽいですし、
wurflapi.wurfl_config_array.cache.*
なんてドキュメントには無い。

よく分かんないですが、wurfl-config.phpを作らずにapplication.iniから設定します。

; application/configs/application.ini
; WURFL API
;
resources.useragent.wurflapi.wurfl_lib_dir = APPLICATION_PATH "/../library/wurfl-php-1.3.1/WURFL/"
resources.useragent.wurflapi.wurfl_api_version = "1.1"
resources.useragent.wurflapi.wurfl_config_array.wurfl.main-file = APPLICATION_PATH "/../data/wurfl/wurfl-2.0.27.zip"
resources.useragent.wurflapi.wurfl_config_array.wurfl.patches = APPLICATION_PATH "/../data/wurfl/web_browsers_patch.xml"
resources.useragent.wurflapi.wurfl_config_array.persistence.provider = "file"
resources.useragent.wurflapi.wurfl_config_array.persistence.dir.dir = APPLICATION_PATH "/../data/wurfl/cache"

; resources.useragent.wurflapi.wurfl_config_array.persistence.dir = FOO
; キャッシュディレクトリは恐らくこう↑設定するのが正しいのだと思いますが、これだと動作しません。
; /wurfl-php-1.3.1/WURFL/Storage/File.phpのコンストラクタにdirパラメタが渡ってきません。
; こう↓すれば渡ってきます。
; resources.useragent.wurflapi.wurfl_config_array.persistence.dir.dir = FOO
; Zendの不具合??仕様??。


5. トラブルシュート1

これで設定はお終いでパスが正しく設定されていれば動作する、とのことですが、上手く行きません。

キャッシュディレクトリとして作成した
data/wurfl/cache
がWritableじゃないよ、と言われます。

chmod -R o+rwX data/wurfl/cache
してるのにな、と/wurfl-php-1.3.1/WURFL/Storage/File.phpを見てみると、

if(!is_writeable(dirname($this->root))){
    throw new WURFL_Storage_Exception("The file cache directory is not writeable: ".$this->root);
}

となっており、一つ上のdata/wurflディレクトリがWritableでないとエラーが出ます。

if(!is_writeable($this-&gt;root)){
に修正しても良さそうに見えますが、data/wurflのパーミッションを777にして対応しました。


6. トラブルシュート2

Fatal error: Class 'XMLReader' not found

となった場合は

$ yum install php-xml
$ service httpd restart
# それでもダメならコンパイルオプションの問題かも。


7. 実行

適当なコントローラで

$bootstrap = $this->getInvokeArg('bootstrap');
$userAgent = $bootstrap->getResource('useragent');
$device = $userAgent->getDevice();
var_dump($device);

してみると色々取れてて面白い。

// モバイル、PC、タブレット判定
$device->getFeature('is_mobile');
$device->getFeature('is_desktop');
$device->getFeature('is_tablet');
// モバイル、スマホ判定
$device->getFeature('device_os')
// SSLサポート
$device->httpsSupport();
// 最大画像サイズ(?)
$device->getMaxImageWidth();
$device->getMaxImageHeight();
// 画面解像度(?)
$device->getPhysicalScreenWidth();
// Flashサポート
$device->hasFlashSupport()
// audioサポート
if ($userAgent->hasFeature('mp3') && $userAgent->getFeature('mp3')) {
    // embed HTML5 audio tag...
}


8. 付録

エミュレータを使ったスマホ判定実験結果一覧。

端末名 OS device_os device_os_token brand_name
DC GALAXY NEXUS(SC-04D) Android 4.0 Android Android 4.0.1 Android
SB iPad iOS5 iPhone OS iPhone OS Apple
SB iPhone4 iOS5 iPhone OS iPhone OS Apple
DC HT1100 Windows Mobile 6 Windows Mobile OS Windows CE Generic
DC BlackBerry Bold 9700 BlackBerry OS5.0 RIM OS NULL RIM
DC NM850iG Symbian OS 8.0 NULL NULL DoCoMo

Symbianだけガラケー判定しそうだけど、基本的にはdevice_osの有り無しで見てしまっていいんではないだろうか。。

2012年2月16日木曜日

CentOS6.2 KVM環境構築

CentOS5.4で運用してきましたがそろそろ6.xにアップデート。
Xenで管理してきた仮想化環境を6系からCentOS標準になったKVMに移行。

以下、その記録。

http://www.oss-d.net/virt/kvm
こちらを参考に。

■環境

$ cat /etc/redhat-release
CentOS release 6.2 (Final)

$ cat /proc/version
Linux version 2.6.32-220.4.2.el6.x86_64 (mockbuild@c6b18n3.bsys.dev.centos.org) (gcc version 4.4.6 20110731 (Red Hat 4.4.6-3) (GCC) ) #1 SMP Tue Feb 14 04:00:16 GMT 2012

■KVMインストール

$ yum groupinfo kvm
Loaded plugins: fastestmirror, refresh-packagekit
Setting up Group Process
Loading mirror speeds from cached hostfile
 * base: rsync.atworks.co.jp
 * extras: rsync.atworks.co.jp
 * updates: rsync.atworks.co.jp
Warning: Group kvm does not exist.

おっと、いきなりダメじゃん。

ここまで辿り着くまでにRAID組んだりX入れたりVNC入れたり色々用意してたんですが、CentOS6.2で管理されてるパッケージがこれまでと地味に色々変わってる。

$ yum groupinstall Virtualization "Virtualization Client" "Virtualization Platform" "Virtualization Tools"

が正解。
参考 http://unix.stackexchange.com/questions/18802/how-to-install-kvm-on-centos-6

後は基本的に書かれてある通り。

■ブリッジ設定

$ cp /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-br0
$ vi /etc/sysconfig/network-scripts/ifcfg-br0

DEVICE="br0"
BOOTPROTO="static"
BROADCAST="192.168.12.255"
DNS1="192.168.12.1"
GATEWAY="192.168.12.1"
#HWADDR="00:22:19:0B:6F:F0"
IPADDR="192.168.12.51"
NETMASK="255.255.255.0"
#NM_CONTROLLED="yes"
ONBOOT="yes"
TYPE="Bridge"

$ vi /etc/sysconfig/network-scripts/ifcfg-eth0

DEVICE="eth0"
#BOOTPROTO="static"
#BROADCAST="192.168.12.255"
#DNS1="192.168.12.1"
#GATEWAY="192.168.12.1"
HWADDR="00:22:19:0B:6F:F0"
#IPADDR="192.168.12.51"
#NETMASK="255.255.255.0"
#NM_CONTROLLED="yes"
ONBOOT="yes"
TYPE="Ethernet"
BRIDGE="br0"

$ service network restart

$ vi /etc/sysctl.conf
# 恐らく変更の必要ない

ここまででブリッジを見るように設定できた。
ただ、rebootしたりするとWANが見えなくなる。
何でかよく分からないけどservice network restartすると見える。

■仮想マシンマネージャー起動

X上で
$ virt-manager

Error polling connection 'qemu:///system': internal error Cannot find suitable
emulator for x86_64

となれば、恐らくBIOSのVirtualizationが無効になっている。
BIOS設定からVirtualizationをenableに変更すればOKだった。

■ゲストOSインストール(失敗)

virt-managerからGUIで仮想マシンをインストールしようとすると、ネットワーク越しのインストールが出来ない。
URIを指定してインストールを試みると
The location must be the root directory of an install tree.
とかって言われる。

検索してみてもバグ?とか何とか、よく分からない。
しょうがないのでローカルメディアからインストーラを起動してネットワークインストール。

■仮想マシンインストール(続き)

CDROMにネットワークインストールのメディアを焼いてそこからインストールを試みた。
途中までは良かったけれど、インストール中にUSB接続キーボードのアンダースコアが表示できない。

keymapとか色々あるんだろう。

ググってみてもVmwareの場合はいくつか出てくるけど、KVMはよく分からない。

仕方がないのでUSのキーボードレイアウトでインストール実行。
「ほ」の辺りのキーをShift押しながらでアンダースコア。

無理やり感漂うけれど、まぁ、インストールはできた。

インストール後日本語レイアウトに変更。

$ vi /etc/sysconfig/keyboard

KEYTABLE="us"
MODEL="pc105+inet"
LAYOUT="us"
KEYBOARDTYPE="pc"

↓

KEYTABLE="jp106"
MODEL="jp106"
LAYOUT="jp"
KEYBOARDTYPE="pc"

アンダースコアも大丈夫。

・ゲストOS環境

$ cat /etc/redhat-release
CentOS release 6.2 (Final)

$ cat /proc/version
Linux version 2.6.32-220.el6.x86_64 (mockbuild@c6b18n3.bsys.dev.centos.org) (gcc version 4.4.6 20110731 (Red Hat 4.4.6-3) (GCC) ) #1 SMP Tue Dec 6 19:48:22 GMT 2011

2012年2月1日水曜日

Android, Viewにstyleを動的に設定する方法

AndroidアプリのButtonやViewに動的にstyleを設定したい。
例えばActivityからnew Button()したようなオブジェクトに共通で使っているstyleを設定したい。

かなり探したけど全然見つからなかった。
唯一ここに発見。
ソースコード内で生成したViewインスタンスにStyleを適用

はじめは書いてある内容が理解できなかったので、備忘録として以下メモ。
#リンク先と同じこと書いてるだけ。

・アウトライン

Viewのコンストラクタを見てると
View(Context context)
View(Context context, AttributeSet attrs)
View(Context context, AttributeSet attrs, int defStyle)
こんな風に定義されている。

Contextはクラスのオブジェクトを指定。この辺りを参照。
AttributeSetはXMLで指定した場合のプロパティが渡ってくる。
style指定は3つ目のdefStyle。

なので、Activityから生成したViewオブジェクトにstyleを指定した場合は、3つ目のパラメタにR.foo.barのリソースIDを渡せばよい。
っていうのは比較的すぐ分かるんですが、具体的にどうやるのかは一苦労。

以下。

・styleの指定

R.styleというのがあるので、こいつを指定すればいいんだろうと格闘してましたが、どうやら違ってR.attrでないとダメな模様。
このstyleリソースIDを直接指定できれば楽なんだけどなー、というのが最初のリンク先で触れられていた内容。

・R.attrについて

R.attrのドキュメント

ドキュメントを見る限り、R.attrはwidthやcheckedのようなAndroidのXMLエレメントのアトリビュートを定義してるものらしい。
R.attrの中にはdialogLayoutやbuttonStyleのようなレイアウト周りのリソースIDを指定していると思われるものも存在しますが、本来styleとは直接は関係ない(はず)。
なのでR.attrの指定からしかstyleを適用できない、というのは勘違いかAndroidのバグだと思う。

でも他にやり方が分からないので以下進めます。
#詳しい方、教えて。。

・R.attrを指定する

XMLはres/values/style.xmlで指定する。
#ここじゃないとダメなのかはよく分からない。

1. 適用させるstyleを作成
<!-- <item name="android:layout_*">の要素はR.attrでスタイル適用出来ないっぽい -->
<style name="MyButtonStyle" parent="android:style/Widget.Button">
    <item name="android:layout_width">match_parent</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:layout_marginBottom">2dp</item>
    <item name="android:textColor">@color/foo</item>
    <item name="android:textSize">16dp</item>
    <item name="android:background">@drawable/bar</item>
    <item name="android:textStyle">bold</item>
</style>

作ったstyleをR.attrで参照可能なように設定。

2. テーマの適用

theme属性としてstyleを設定するので、先にテーマをアプリケーションに適用させておく。
AndroidManifest.xml
  <application
      android:icon="@drawable/ic_launcher"
      android:label="@string/app_name"
+     android:theme="@style/MyApplicationStyle" >


3. テーマの設定

<style name="MyApplicationStyle" parent="android:Theme">
</style>


4. styleのテーマへの組み込み

+ <attr name="myButtonStyle" format="integer|reference" />
  <style name="MyApplicationStyle" parent="android:Theme">
+      <item name="myButtonStyle">@style/MyButtonStyle</item>
  </style>

これでR.attr.myButtonStyleが参照可能になった。

5. Activityでの適用

Button btn = new Button(this, null, R.attr.myButtonStyle);

でstyle定義済みのViewオブジェクトが生成可能。
後は如何様にも。

btn.setText("foo");
LinearLayout layout = new LinearLayout(this);
layout.addView(
    btn,
    new LinearLayout.LayoutParams(
        LinearLayout.LayoutParams.WRAP_CONTENT,
        LinearLayout.LayoutParams.WRAP_CONTENT));
setContentView(layout);

とか。

2012年1月26日木曜日

Zend HeadLink ヘルパーでコンディショナルCSS

ZendViewヘルパーを使って<head>タグや<meta>タグを管理すると嬉しいことが一杯。

・layoutファイルがすっきりする(場合によっては全てのmoduleに同じlayoutファイルを適用できるようになる)
・ベタ書きしてれば微妙に大変な<title>の変更なんかも一発
・コントローラで設定すれば、継承してサブクラスでどんどんappendできる

CSSのlink設定でのTipsをメモ。

標準的には以下のような感じで設定。

Controllerで設定
{
public function preDispatch()
{
    parent::preDispatch();
    $this->view->headTitle('MyWEB', 'SET');
    $this->view->headLink()->appendStylesheet($this->view->baseUrl('path_to_css/css1.css'))
                           ->appendStylesheet($this->view->baseUrl('path_to_css/css2.css'));
    $this->view->headMeta()->setCharset('UTF-8'); //こんなのはbootstrapとかどこか固定で設定してもいい
}
Viewで出力
<?php echo $this->doctype(); ?>
<html>
<head>
<?php echo $this->headTitle() ?>
<?php echo $this->headMeta() ?>
<?php echo $this->headLink() ?>
<?php echo $this->headScript() ?>
</head>
<body>

コンディショナルな指定をする場合。
例えばIE7だけで適用するCSSの設定。

こんな感じ。
public function preDispatch()
{
    parent::preDispatch();
    $this->view->headLink()->appendStylesheet($this->view->baseUrl('path_to_css/common.css'))
                           ->appendStylesheet($this->view->baseUrl('path_to_css/ltIE7.css'), 'screen', 'lt IE 7');
    // <!--[if lt IE 7]> <link href="/path_to_css/ltIE7.css" media="screen" rel="stylesheet" type="text/css" ><![endif]-->
}

参考)
Zend Viewヘルパー

2011年12月25日日曜日

DELL Studio17をSSDに換装(を試みて失敗)

Studio17/1747をSSDに換装しようとしてコールド負け。

http://urakaze.com/post-379.html
ここなどを参考にSSDに換装しようとして、結果、敗北。。

SSDが安くなってて256GBで2万円ちょっと。
ここいらでタイミングだろうと思い立ち、換装を試みましたがリカバリが上手くいきませんでした。

結論から言うと、大容量512GBのSSDにするべき。

以下、敗北の記録。

1. リカバリできない

Dellはリカバリ用のOSを添付してくれなくなりました。
購入直後にリカバリ用ディスク作成を推奨され、そこからリカバリするのですが、OSのインストーラではなく、ディスクイメージのダンプのようなものっぽいです。

元のHDDが512GBだったのですが、SSD付けてリカバリしようとすると「ドライブの容量が小さいのでリカバリできません」のようなエラーが表示され、工場出荷時の状態に戻せません。
リカバリディスクは10GBも無いくせに、元のHDDと同容量程度はないとリカバリできないっぽいです。

 2. Windowsのシステム修復ができない

Dellのリカバリが効かないのでWindows7のシステムイメージ作成とシステム修復からSSDへのインストールを試みました。

システム修復ディスクでブートするも、「erro 0X4001100200001012」というエラーが表示され、システム修復のメニューが表示されません。

http://answers.microsoft.com/ja-jp/windows/forum/windows_7-performance/%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E4%BF%AE/2f804e74-e6ed-4059-a0fd-8328823e7e11
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1452430939

このあたりに言及もありますが、DellかWindowsかどちらかのバグのようです。

3. システム修復ディスクのISOを焼いて再挑戦

上記の問題を回避するために、 システム修復ディスクのISOを焼け、という情報があったので試してみました。

Windows-7-64-bit-Repair-Disc.iso
こんな名前のシステム修復ディスクイメージが出回っています。

なんか怪しいサイトにあるので、ウイルス怖え~、とか思いながらも挑戦してみましたが、これもダメでした。

リカバリのメニューは表示され、外付けUSBに落としておいたWindowsのバックアップイメージも認識して選択できるのですが、いざ、リカバリを実行しようとすると原因不明のエラーになります。

エラーのdetailを見る限り「USBが認識できてないかもしれないよ」といったインフォメーションが表示されてるのですが、そもそもバックアップイメージ選択できてます。

ただ、USBストレージを表していると思われるドライブ文字が「\\?\」と文字化けしてるのが気になります。よく分かりませんが、マルチバイト環境とかドライバとか色々あるんでしょう、きっと。。

4. 諦めた

後のやりようとしては、別のバックアップソフトからシステムイメージ丸ごとコピーする、とかでしょうけど、SATAでSSD繋げる箱が無かったり、Studio17の2ndベイの形状が特殊で繋げなかったりして、もう面倒なので諦めました。

これ以上時間使うより、Windows単体で新しく買った方がよい。

Windows新たに買う費用考えたら512GBのSSD買えた、というコールド負け。

2011年10月19日水曜日

Poeditで登録されていない拡張子ファイルでカタログ作成

Poedit : http://www.poedit.net/
使い方
http://wp.mmrt-jp.net/plugin-japanization-project/poedit/
http://ideasilo.wordpress.com/2006/08/14/edit-language-files-with-poedit/
http://www.koikikukan.com/archives/2007/03/29-032523.php

Poedit Version 1.4.6
登録されていない拡張子のファイルでカタログ生成したい場合。

例えば、PHPファイルだけど、「.php」拡張子ではなくて、「.phtml」や「.ctp」や「.php5」だったりした場合、デフォルトではカタログ生成ができない。(無視される)

それらもPHPファイルですよ、と教えてあげて、パースしてもらう設定。

1. ファイル > 設定 > パーサ
2. [ソースコードのパーサ] > PHP > 編集
3. パーサ設定
・対象拡張子
- *.php
+ *.php;*.phtml;*.ctp;*.php5
・パーサのコマンド
- xgettext --force-po -o %o %C %K %F
+ xgettext --force-po -o %o %C %K %F --language=PHP

これで出来るハズ(PHPなら)。

2011年8月24日水曜日

Zend Framework default module prefix

default moduleは標準的には
class IndexController extends Zend_Controller_Action
のようなクラス名でモジュール名プレフィックスが付かない。
application
 - modules
   - foo
    - controllers
    - views
    - ...
   - bar
    - controllers
    - views
    - ...
のような構成を取っていて、fooモジュールをデフォルトモジュールとしたい場合、
#application/configs/application.ini
resources.frontController.defaultModule = "foo"
のような設定を行うが、そのままだと、
class Foo_IndexController extends Zend_Controller_Action
のクラス名でディスパッチできない。

デフォルトモジュールにもプレフィックスを付けたい場合、
#application/configs/application.ini
resources.frontController.defaultModule = "foo"
resources.frontController.prefixDefaultModule = TRUE
とするとOK。

プレフィックス付いていた方が管理しやすいし、動的なデフォルトモジュール切り替えとかもやりやすい。

http://framework.zend.com/manual/ja/zend.controller.dispatcher.html
#Zend_Application_Resource_Frontcontroller
http://framework.zend.com/manual/ja/zend.application.available-resources.html
#概要

メモメモ。

2011年7月6日水曜日

お名前.comVPSのマルチドメイン環境でメール受信が出来なくなる

yum updateかけようとすると、clamv周りでエラーが出て更新できなくなったので、関係パッケージ全て削除しました。

・・・すると、マルチドメイン環境でメール受信が出来なくなる。

clamd再インストールしようとしても登録されてるリポジトリが古いのか何なのか、とにかくパッケージ入りません。

$ yum install clamd
Loaded plugins: fastestmirror, priorities
Loading mirror speeds from cached hostfile
 * base: www.ftp.ne.jp
 * epel: ftp.kddilabs.jp
 * extras: www.ftp.ne.jp
 * updates: www.ftp.ne.jp
base                                                               | 2.1 kB     00:00
epel                                                               | 3.7 kB     00:00
epel/primary_db                                                    | 3.7 MB     00:00
extras                                                             | 2.1 kB     00:00
lg-ce5x-main-arch                                                  | 1.9 kB     00:00
lg-ce5x-main-noarch                                                | 1.9 kB     00:00
updates                                                            | 1.9 kB     00:00
updates/primary_db                                                 | 664 kB     00:00
128 packages excluded due to repository priority protections
Setting up Install Process
Resolving Dependencies
--> Running transaction check
---> Package clamd.x86_64 0:0.97-13.el5 set to be updated
--> Processing Dependency: clamav = 0.97-13.el5 for package: clamd
--> Processing Dependency: libclamav.so.6(CLAMAV_PRIVATE)(64bit) for package: clamd
--> Processing Dependency: libclamav.so.6(CLAMAV_PUBLIC)(64bit) for package: clamd
--> Processing Dependency: libclamav.so.6()(64bit) for package: clamd
--> Running transaction check
---> Package clamav-lib.x86_64 0:0.97-1.r1.lg.ce5 set to be updated
--> Processing Dependency: data(clamav) for package: clamav-lib
---> Package clamd.x86_64 0:0.97-13.el5 set to be updated
--> Processing Dependency: clamav = 0.97-13.el5 for package: clamd
--> Running transaction check
---> Package clamav-data.x86_64 0:0.97-1.r1.lg.ce5 set to be updated
--> Processing Dependency: clamav-filesystem = 0.97-1.r1.lg.ce5 for package: clamav-data
---> Package clamd.x86_64 0:0.97-13.el5 set to be updated
--> Processing Dependency: clamav = 0.97-13.el5 for package: clamd
--> Running transaction check
---> Package clamav-filesystem.x86_64 0:0.97-1.r1.lg.ce5 set to be updated
---> Package clamd.x86_64 0:0.97-13.el5 set to be updated
--> Processing Dependency: clamav = 0.97-13.el5 for package: clamd
--> Finished Dependency Resolution
clamd-0.97-13.el5.x86_64 from epel has depsolving problems
  --> Missing Dependency: clamav = 0.97-13.el5 is needed by package clamd-0.97-13.el5.x86_64 (epel)
Error: Missing Dependency: clamav = 0.97-13.el5 is needed by package clamd-0.97-13.el5.x86_64 (epel)
 You could try using --skip-broken to work around the problem
 You could try running: package-cleanup --problems
                        package-cleanup --dupes
                        rpm -Va --nofiles --nodigest

マルチドメイン環境にすると、自動的にamavisdというウイルスチェックソフトにメールのスプールの中身が転送される設定になるようですが、メールチェックはクライアントですればいいので、設定を外す。

こちらを参考にclamdへのスプール転送を外す。
http://d.hatena.ne.jp/pasela/20100420/postfix

が、メール受信できず。

postsuper -r ALL
すれば、メール配信されてくるのですが、
/etc/postfix/main.cf
の
content_filter
設定をコメントアウトしても、
/var/log/maillog
には
connect to 127.0.0.1[127.0.0.1]:10024: Connection refused
みたいなClamAVが見つかりません的なエラーが出続ける。

どうやら、
/etc/postfix/main.cf
以外にもamavisd関係の設定が残っているらしい。

ということで
grep 'content_filter' ./*

結果、
/etc/postfix/master.cf
/etc/postfix/postfix-pkg-main.cf
という2つのファイルにも
-o content_filter=smtp-amavis:[127.0.0.1]:10024
のような設定がされているので、コメントアウトしてpostfix再起動。

無事にメール受信できるようになりました。

===============
2011-07-11追記

上記だけだとメールの転送が効かなくなります。

/etc/postfix/main.cf
の
receive_override_options = no_address_mappings
設定をコメントアウトすれば、メール転送も上手くいきます。

が、どうしてそれで上手くいくのかはよく分かりません。
何か弊害が出るようであればお知らせ下さい。

仔細はコメント欄参照。
===============

2011年6月30日木曜日

Redmine - undefined method `name' for "abstract":String

gem update --systemすると、Redmineが動かなくなる。
「undefined method `name' for "abstract":String」エラー。

Ruby : 1.8.7 (2011-02-18 patchlevel 334) [x86_64-linux], MBARI 0x6770, Ruby Enterprise Edition 2011.03
Rails : Redmine vendor - 2.2.2?
Redmine: 1.2.0

gemのバージョンを差し戻すと動作するようになったのでメモ。

http://www.redmine.org/issues/8325
一ヶ月程前からチケット上がってるけど、今日(2011-06-30)時点ではクローズされてない。

「yum -yで色々インストールすればいけますよー」みたいな報告がされてResolvedされたチケットが、「どう解決したってんだよ、バカ!結局原因は何なんだよ!」的なツッコミと共にNewに差し戻されたのは余談。

最新のgem1.8.5に更新してしまうとRedmineが動かなくらしいです。
1.6.2なら動作する、という報告もありますが、1.7.2でも動きました。

$ gem update --system 1.7.2
$ gem -v
1.7.2

参考までにgem list。

$ gem list

*** LOCAL GEMS ***

abstract (1.0.0)
actionmailer (3.0.9, 3.0.7)
actionpack (3.0.9, 3.0.7)
activemodel (3.0.9, 3.0.7)
activerecord (3.0.9, 3.0.7)
activeresource (3.0.9, 3.0.7)
activesupport (3.0.9, 3.0.7)
arel (2.0.10, 2.0.9)
builder (3.0.0, 2.1.2)
bundler (1.0.15, 1.0.13)
daemon_controller (0.2.6)
erubis (2.7.0, 2.6.6)
fastthread (1.0.7)
i18n (0.6.0, 0.5.0, 0.4.2)
mail (2.3.0, 2.2.19)
mime-types (1.16)
mysql (2.8.1)
passenger (3.0.7)
polyglot (0.3.1)
rack (1.3.0, 1.2.2, 1.1.2, 1.0.1)
rack-mount (0.8.1, 0.6.14)
rack-test (0.6.0, 0.5.7)
rails (3.0.9, 3.0.7)
railties (3.0.9, 3.0.7)
rake (0.9.2, 0.8.7)
rdoc (3.6.1)
rubygems-update (1.8.5, 1.8.4, 1.8.1, 1.8.0, 1.7.2, 1.6.2, 1.4.2)
thor (0.14.6)
treetop (1.4.9)
tzinfo (0.3.28, 0.3.27)

原因はよく分かりませんが、Redmineが動かないのは仕事に支障が出るので、取り合えずgemを巻き戻して対応することに。

2011年4月16日土曜日

EclipseからJavadocを生成する時のエラー

ま、エンコーディングの話しだけなのですが、備忘録として。

Project > Generate Javadoc...
からJavadoc生成。

で、エラー。

java.nio.BufferOverflowException
 at java.nio.Buffer.nextPutIndex(Buffer.java:495)
 at java.nio.HeapCharBuffer.put(HeapCharBuffer.java:145)
 at com.sun.tools.javac.util.DefaultFileManager.decode(DefaultFileManager.java:830)
 at com.sun.tools.javac.util.DefaultFileManager.access$300(DefaultFileManager.java:76)
 at com.sun.tools.javac.util.DefaultFileManager$RegularFileObject.getCharContent(DefaultFileManager.java:1353)
 at com.sun.tools.javac.util.DefaultFileManager$RegularFileObject.getCharContent(DefaultFileManager.java:1252)
 at com.sun.tools.javac.main.JavaCompiler.readSource(JavaCompiler.java:483)
 at com.sun.tools.javac.main.JavaCompiler.parse(JavaCompiler.java:550)
 at com.sun.tools.javac.main.JavaCompiler.parse(JavaCompiler.java:541)
 at com.sun.tools.javadoc.JavadocTool.parsePackageClasses(JavadocTool.java:203)
 at com.sun.tools.javadoc.JavadocTool.getRootDocImpl(JavadocTool.java:145)
 at com.sun.tools.javadoc.Start.parseAndExecute(Start.java:330)
 at com.sun.tools.javadoc.Start.begin(Start.java:128)
 at com.sun.tools.javadoc.Main.execute(Main.java:41)
 at com.sun.tools.javadoc.Main.main(Main.java:31)

Google先生に聞いてみる。

BufferOverflowException when generating Javadoc?
Eclipse 上で UTF-8 のソースから javadoc を生成する方法

EclipseからJavadocを生成する際にVM実行オプションを指定

-encoding "utf-8" -charset "utf-8"

以上。

2011年1月19日水曜日

JAXB xs:intに文字列を入れてみる

JAXBでJava - XMLを相互変換したい。

・JAXB
Java Architecture for XML Binding(Wikipedia)

ということで実装していたのですが、xs:intなXMLのエレメントに文字列や空を渡した場合の挙動が不安定であることが判明。

サーバによって同じ文字列でもパース出来るときと出来ないときがある。
文字列からintへの変換の挙動が環境に依存する。

例えば、こんなXSDを用意。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="hoge">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="fuga" type="xs:int"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

こいつをxjcコマンドでコンパイルしてObjectFactoryや以下のようなXMLエンティティを表現するObjectを生成。

@XmlRootElement(name = "hoge")
public class Hoge {

    @XmlElement(name = "fuga", required = true)
    protected int fuga;

これにXMLを噛ましてパースさせる。

<?xml version="1.0" encoding="utf-8" ?>
<hoge>
 <fuga>123</fuga>
</hoge>

Hoge hoge = (Hoge)unmarshaller.unmarshal(reader);

これはまぁ、普通にパースできて、Hoge#fugaに123が入ってくる。

問題は、以下のようなXML

<?xml version="1.0" encoding="utf-8" ?>
<hoge>
 <fuga></fuga>
</hoge>

<?xml version="1.0" encoding="utf-8" ?>
<hoge>
 <fuga>abc</fuga>
</hoge>

これらをunmarshalするとJAXBExceptionが出る場合と0に変換されてパースされる状況がある。

ローカル(Windows7)で実行するとJAXBException、リモートのWindows Server上で実行すると0に変換。

全く同じソースをデプロイして、さらにはアプリ付きのサーバ(Jetty)ごとリモートに移しても挙動が一致しない。

差異はjavaのバージョンか、Windowsのバージョンぐらいしか思いつかない。。

・java -version
ローカル:1.6.0_21
リモート:1.6.0_23

・OS
ローカル:Windows7
リモート:Win2003Server R2 64bit、Win2008 Server 64bit、32bit

いずれにしてもこれぐらいの差異で環境によって異なる結果を返されると困る。。

Google先生に聞いてみたり、色々ダンプしてみたりと結構調べたのですが、原因が分からず、xs:intな項目を使わない方針に変更。

こんな感じでxs:intからxs:integerに変更

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="hoge">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="fuga" type="xs:integer"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

@XmlRootElement(name = "hoge")
public class Hoge {

    @XmlElement(name = "fuga", required = true)
    protected BigInteger fuga;

こうしておくと、文字列などパースできないXMLエレメントは環境に依存せずにnullにパースされてるっぽい。今のところ、、。

2010年11月28日日曜日

Spring SecurityでDB接続してユーザ認証してみる

Springの認証機構Spring SecurityでのDBと繋いだログイン処理。

・環境
Spring 3.0.5
Roo 1.1.0

基本となる各種設定をSpring Rooでセットアップします。

roo> security setup

Managed ROOT\pom.xml [Adding property 'spring-security.version' to '3.0.2.RELEASE']
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-core:${spring-security.version}]
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-config:${spring-security.version}]
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-web:${spring-security.version}]
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-taglibs:${spring-security.version}]
Created SRC_MAIN_RESOURCES\META-INF\spring\applicationContext-security.xml
Created SRC_MAIN_WEBAPP\WEB-INF\views\login.jspx
Managed SRC_MAIN_WEBAPP\WEB-INF\views\views.xml
Managed SRC_MAIN_WEBAPP\WEB-INF\web.xml
Managed SRC_MAIN_WEBAPP\WEB-INF\spring\webmvc-config.xml

Spring RooによってSpring Securityアドオンがインストールされ、web.xmlにプロキシが登録されたり、各種viewが作成されます。
(実行時のSpring Securityバージョンは3.0.2)

認証機構の定義ファイルとして
SRC_MAIN_RESOURCES\META-INF\spring\applicationContext-security.xml
が生成されます。

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

 <!-- HTTP security configurations -->
    <http auto-config="true" use-expressions="true">
     <form-login login-processing-url="/resources/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"/>
        <logout logout-url="/resources/j_spring_security_logout"/>
        
        <!-- Configure these elements to secure URIs in your application -->
        <intercept-url pattern="/choices/**" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="/member/**" access="isAuthenticated()" />
        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/**" access="permitAll" />
    </http>

 <!-- Configure Authentication mechanism -->
    <authentication-manager alias="authenticationManager">
     <!-- SHA-256 values can be produced using 'echo -n your_desired_password | sha256sum' (using normal *nix environments) -->
     <authentication-provider>
      <password-encoder hash="sha-256"/>
         <user-service>
             <user name="admin" password="8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918" authorities="ROLE_ADMIN"/>
          <user name="user" password="04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb" authorities="ROLE_USER"/>
      </user-service>
     </authentication-provider>
 </authentication-manager>

</beans:beans>

デフォルトのこの設定ファイルで、
"/choices/**"のURLは'ROLE_ADMIN'なロールを持っていること、
"/member/**"のURLは認証されていること、
その他のアクセスは無制限に許可、
のインターセプトがかかります。

また、User/Passがadmin/adminの場合はROLE_ADMIN、user/userの場合はROLE_USERな権限設定がXMLに直接記述されています。

これをDBに問い合わせ、ユーザ認証を行うように変更します。

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

 <!-- HTTP security configurations -->
    <http auto-config="true" use-expressions="true">
     <form-login login-processing-url="/resources/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"/>
        <logout logout-url="/resources/j_spring_security_logout"/>
        
        <!-- Configure these elements to secure URIs in your application -->
        <!-- cssやimgなどWEBリソースは無制限 -->
        <intercept-url pattern="/resources/**" access="permitAll" />
        <!-- ログイン処理は無制限 -->
        <intercept-url pattern="/login/**" access="permitAll" />
        <!-- それ以外はadmin権限 -->
        <intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />
    </http>

 <!-- Configure Authentication mechanism -->
    <authentication-manager alias="authenticationManager">
     <!-- SHA-256 values can be produced using 'echo -n your_desired_password | sha256sum' (using normal *nix environments) -->
     <authentication-provider>
      <!-- sha-256でパスワードをエンコーディングする -->
      <!-- "admin"文字列のハッシュ => "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918" -->
      <!-- "user"文字列のハッシュ => "04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb" -->
      <password-encoder hash="sha-256"/>
      <!-- データソース指定。SQL指定。 -->
      <jdbc-user-service data-source-ref="dataSource"
      users-by-username-query="select users_id as username, password as password, true as enabled from users where users_id=?"
      authorities-by-username-query="select users_id as username, 'ROLE_ADMIN' as authority from users where users_id=?"/>
     </authentication-provider>
 </authentication-manager>
</beans:beans>

以下で記述されているスキーマを定義することが好ましいですが、上記の設定ではusersテーブル一つで処理を行っています。
http://static.springsource.org/spring-security/site/docs/2.0.x/reference/appendix-schema.html

usersテーブルにはusers_id、passwordの2つのフィールドが定義されているものとします。

users-by-username-query設定で以下のクエリを発行し、認証を許可するユーザを取得します。

select
    users_id as username,
    password as password,
    true     as enabled
from
    users
where
    users_id=?

authorities-by-username-query設定で実際の認証設定を取得する以下のクエリを発行します。
ユーザが存在し、users-by-username-queryでのユーザ取得が行えれば全てのリソースに接続可能とするため、'ROLE_ADMIN'な固定値をauthorityとして返却します。

select
    users_id     as username,
    'ROLE_ADMIN' as authority
from
    users
where
    users_id=?

usersテーブルに以下のデータを登録すればhoge/adminでログイン可能となります。
users_id:hoge
password:8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918

参考資料)

・Spring Securityの機能について
http://ameblo.jp/spring-beginner/entry-10234230466.html

・実際に認証と認可をWEBにつけるには?
http://ameblo.jp/spring-beginner/entry-10236817063.html

・Spring by Example - Simple Spring Security Webapp
http://www.springbyexample.org/examples/simple-spring-security-webapp-spring-config.html

・5 Minute Guide to Spring Security
http://www.mularien.com/blog/2008/07/07/5-minute-guide-to-spring-security/

・Using Spring-Security Database in Spring-Roo
http://roosbertl.blogspot.com/2010/06/using-spring-security-database-in.html

・Security Database Schema
http://static.springsource.org/spring-security/site/docs/2.0.x/reference/appendix-schema.html

・Security Namespace Configuration
http://static.springsource.org/spring-security/site/docs/3.1.x/reference/ns-config.html#ns-auth-manager

・概説 Springプロダクト(3) - Spring Securityでユーザ認証/アクセス制御
http://journal.mycom.co.jp/articles/2010/03/25/spring3/index.html

・Spring Roo日本語リファレンス - 2.10 セキュアなアプリケーション
http://www.spring-roo.com/home/210

2010年11月10日水曜日

EclipseプラグインでJetty連携

開発用のAPコンテナをJettyにしよう作業してたところ、EclipseからJettyを起動させるプラグインを発見。

http://docs.codehaus.org/display/JETTY/Web+Tooling+Support


これはいいや、と書かれている通りに構築していたのですが、以下のエラーが出て起動できず。

java.lang.NoClassDefFoundError: org/mortbay/start/Main
Caused by: java.lang.ClassNotFoundException: org.mortbay.start.Main
 at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Exception in thread "main" 

別途アプリをデプロイすればいいだけなのですが、開発資材を毎度毎度操作するのは面倒過ぎる。。

何だかんだで5時間近く色々と試してみたところ、当該プラグインがJetty7には対応していない、というオチ。

こことか、ここにその旨ありました。

Jetty6系だとちゃんと動きます。

2010年11月9日火曜日

Eclipseで比較エディタが開けなくなった。

Eclipse Version: 3.6.1

Updateかけたら比較エディタが開けなくなった。

こんなエラーが出る。

Could not open the editor: An unexpected exception was thrown.

java.lang.NullPointerException
 at org.eclipse.php.internal.core.format.FormatPreferencesSupport.verifyValidity(FormatPreferencesSupport.java:100)
 at org.eclipse.php.internal.core.format.FormatPreferencesSupport.getIndentationChar(FormatPreferencesSupport.java:89)
 at org.eclipse.php.internal.ui.editor.configuration.PHPStructuredTextViewerConfiguration.getIndentPrefixes(PHPStructuredTextViewerConfiguration.java:448)
 at org.eclipse.wst.sse.ui.internal.StructuredTextViewer.configure(StructuredTextViewer.java:311)
 at org.eclipse.php.internal.ui.compare.MergeSourceViewer.configure(MergeSourceViewer.java:542)
 at org.eclipse.php.internal.ui.compare.TextMergeViewer$ContributorInfo.internalSetDocument(TextMergeViewer.java:546)
 at org.eclipse.php.internal.ui.compare.TextMergeViewer$ContributorInfo.setDocument(TextMergeViewer.java:434)
 at org.eclipse.php.internal.ui.compare.TextMergeViewer.updateContent(TextMergeViewer.java:2569)
 at org.eclipse.php.internal.ui.compare.ContentMergeViewer.internalRefresh(ContentMergeViewer.java:814)
 at org.eclipse.php.internal.ui.compare.ContentMergeViewer.inputChanged(ContentMergeViewer.java:704)
 at org.eclipse.jface.viewers.ContentViewer.setInput(ContentViewer.java:274)
 at org.eclipse.compare.CompareViewerSwitchingPane.setInput(CompareViewerSwitchingPane.java:276)
 at org.eclipse.compare.internal.CompareContentViewerSwitchingPane.setInput(CompareContentViewerSwitchingPane.java:158)
 at org.eclipse.compare.CompareEditorInput.internalSetContentPaneInput(CompareEditorInput.java:844)
 at org.eclipse.compare.CompareEditorInput.access$8(CompareEditorInput.java:842)
 at org.eclipse.compare.CompareEditorInput$11.run(CompareEditorInput.java:778)
 at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
 at org.eclipse.compare.CompareEditorInput.feed1(CompareEditorInput.java:772)
 at org.eclipse.compare.CompareEditorInput.feedInput(CompareEditorInput.java:750)
 at org.eclipse.compare.CompareEditorInput.createContents(CompareEditorInput.java:554)
 at org.eclipse.compare.internal.CompareEditor.createCompareControl(CompareEditor.java:456)
 at org.eclipse.compare.internal.CompareEditor.createPartControl(CompareEditor.java:414)
 at org.eclipse.ui.internal.EditorReference.createPartHelper(EditorReference.java:670)
 at org.eclipse.ui.internal.EditorReference.createPart(EditorReference.java:465)

数日無視してたんですが、、地味に困るぜ、、ということで調べてみた。

何でも知ってるGoogle先生に聞けばすぐに判明。
https://bugs.eclipse.org/bugs/show_bug.cgi?id=326227
PDTのバグらしい。

Updateサイトを登録してHelp>Check for Updates
http://download.eclipse.org/tools/pdt/updates/2.2/milestones

PDT2.2が落ちてくるので更新すればOK。

2010年10月20日水曜日

PHPExcel1.7.4の不具合?について

以前PHPExcelのメモリ馬鹿食いについてで触れていたコードがPHPExcel1.7.4では正常に動作しないことが判明しました。

ワークシートオブジェクトのclone処理で、セルへの参照が1.7.4では上手くハンドリングされていないようで、書式が設定されたセルへ値を書き込むと、別のシートの同じセルにも値が書き込まれることがあります。

再現する条件を完全には洗い出せていませんが、書式が何も設定されていないプレーンなセルに対する書き込みは問題ありませんが、以下のような条件で別のシートの同じセルにも値が書き込まれることがあります。

・書き込むセルに既に値が入力されている
・書き込むセルがセル結合されている

他にもワークシート名の変更もちょっと動作が怪しいことがあります。

PHPExcelの1.7.4には不具合があるようです。
というよりも根本的な不具合があるのは1.7.3系で、PHPExcelのメモリ馬鹿食いについてで触れていたメモリリークっぽい現象を修正しようとして失敗しているようです。

セルのデータをキャッシュ(デフォルトではメモリ上に展開?)し、それを利用してセルのコピーやclone処理を行っているようですが、1.7.4系ではデータキャッシュのライブラリが格納されているClasses/PHPExcel/CachedObjectStorage配下が大幅に修正されています。

1.7.4は使用せずに1.7.3cに巻き戻した方が良さそうです。

1.7.3cではPHPExcel_CachedObjectStorage_Memcache.phpに明らかなバグ(単純ミス)があって気持ち悪いので、その箇所のみパッチ当てて使ってます。
ただし、このクラスはPHPExcel_Settingsを通して明示的にmemcachedをキャッシュオブジェクトとして指定しないと使われないようなので、デフォルト設定では実害はないようです。

以下、パッチ。

189行目
- if (!$this->_memcache->addServer($memcacheServer, $memcachePort, false, 50, 5, 5, true, array($this, 'failureCallback')) {
+ if (!$this->_memcache->addServer($memcacheServer, $memcachePort, false, 50, 5, 5, true, array($this, 'failureCallback'))) {

200行目
- throw new Exception('memcache '.$host.':'.$port' failed');
+ throw new Exception('memcache '.$host.':'.$port.' failed');

それから、PHPExcelのメモリリークを調べていた際に参考にさせて頂いたサイト【PHP】PHPExcelがループ内でメモリを使いすぎるで「さらに注意点としては、1.7.3cはaddExternalSheetにバグがあるので、最新のソースをダウンロードした方がいい。」との記述がありますが、これもPHPExcel.phpに以下のパッチを当てれば良さそう。

437行目
- $cell = $sheet->getCell($cellID);
+ $cell = $pSheet->getCell($cellID);

パッチを当てた1.7.3cで当分しのいで、1.7.4の修正版が出るのを待つ作戦をおすすめ。

2010年10月10日日曜日

PHPExcelでセルの背景色指定

背景色の付け方自体は以下の参考リンクから。

PHPExcel-セルのスタイル
PHPExcel(リファレンス1)
PHPExcel セル背景色 & 行と列からアドレスに変換

上記、すごく助かったんですが、実際にどんな値設定すれば何色になるのかリファレンスは見つからず。
PHPExcel_Style_Colorにいくつか有用な定義がされていますが、今一つ何色になるのか分かりにくい。

    const COLOR_BLACK                        = 'FF000000';
    const COLOR_WHITE                        = 'FFFFFFFF';
    const COLOR_RED                            = 'FFFF0000';
    const COLOR_DARKRED                        = 'FF800000';
    const COLOR_BLUE                        = 'FF0000FF';
    const COLOR_DARKBLUE                    = 'FF000080';
    const COLOR_GREEN                        = 'FF00FF00';
    const COLOR_DARKGREEN                    = 'FF008000';
    const COLOR_YELLOW                        = 'FFFFFF00';
    const COLOR_DARKYELLOW                    = 'FF808000';

    public static function indexedColor($pIndex) {
        // Clean parameter
        $pIndex = intval($pIndex);

        // Indexed colors
        if (is_null(self::$_indexedColors)) {
            self::$_indexedColors = array();
            self::$_indexedColors[] = '00000000';
            self::$_indexedColors[] = '00FFFFFF';
            self::$_indexedColors[] = '00FF0000';
            self::$_indexedColors[] = '0000FF00';
            self::$_indexedColors[] = '000000FF';
            self::$_indexedColors[] = '00FFFF00';
            self::$_indexedColors[] = '00FF00FF';
            self::$_indexedColors[] = '0000FFFF';
            self::$_indexedColors[] = '00000000';
            self::$_indexedColors[] = '00FFFFFF';
            self::$_indexedColors[] = '00FF0000';
            self::$_indexedColors[] = '0000FF00';
            self::$_indexedColors[] = '000000FF';
            self::$_indexedColors[] = '00FFFF00';
            self::$_indexedColors[] = '00FF00FF';
            self::$_indexedColors[] = '0000FFFF';
            self::$_indexedColors[] = '00800000';
            self::$_indexedColors[] = '00008000';
            self::$_indexedColors[] = '00000080';
            self::$_indexedColors[] = '00808000';
            self::$_indexedColors[] = '00800080';
            self::$_indexedColors[] = '00008080';
            self::$_indexedColors[] = '00C0C0C0';
            self::$_indexedColors[] = '00808080';
            self::$_indexedColors[] = '009999FF';
            self::$_indexedColors[] = '00993366';
            self::$_indexedColors[] = '00FFFFCC';
            self::$_indexedColors[] = '00CCFFFF';
            self::$_indexedColors[] = '00660066';
            self::$_indexedColors[] = '00FF8080';
            self::$_indexedColors[] = '000066CC';
            self::$_indexedColors[] = '00CCCCFF';
            self::$_indexedColors[] = '00000080';
            self::$_indexedColors[] = '00FF00FF';
            self::$_indexedColors[] = '00FFFF00';
            self::$_indexedColors[] = '0000FFFF';
            self::$_indexedColors[] = '00800080';
            self::$_indexedColors[] = '00800000';
            self::$_indexedColors[] = '00008080';
            self::$_indexedColors[] = '000000FF';
            self::$_indexedColors[] = '0000CCFF';
            self::$_indexedColors[] = '00CCFFFF';
            self::$_indexedColors[] = '00CCFFCC';
            self::$_indexedColors[] = '00FFFF99';
            self::$_indexedColors[] = '0099CCFF';
            self::$_indexedColors[] = '00FF99CC';
            self::$_indexedColors[] = '00CC99FF';
            self::$_indexedColors[] = '00FFCC99';
            self::$_indexedColors[] = '003366FF';
            self::$_indexedColors[] = '0033CCCC';
            self::$_indexedColors[] = '0099CC00';
            self::$_indexedColors[] = '00FFCC00';
            self::$_indexedColors[] = '00FF9900';
            self::$_indexedColors[] = '00FF6600';
            self::$_indexedColors[] = '00666699';
            self::$_indexedColors[] = '00969696';
            self::$_indexedColors[] = '00003366';
            self::$_indexedColors[] = '00339966';
            self::$_indexedColors[] = '00003300';
            self::$_indexedColors[] = '00333300';
            self::$_indexedColors[] = '00993300';
            self::$_indexedColors[] = '00993366';
            self::$_indexedColors[] = '00333399';
            self::$_indexedColors[] = '00333333';
        }

        if (array_key_exists($pIndex, self::$_indexedColors)) {
            return new PHPExcel_Style_Color(self::$_indexedColors[$pIndex]);
        }

        return new PHPExcel_Style_Color();
    }

ということで、Excel標準のカラーパレットに定義されている色のARGBを調べました。
カラーパレット左上から右順で、

FF000000, FF993300, FF333300, FF003300, FF003366, FF000080, FF333399, FF333333,
FF800000, FFFF6600, FF808000, FF008000, FF008080, FF0000FF, FF666699, FF808080,
FFFF0000, FFFF9900, FF99CC00, FF339966, FF33CCCC, FF3366FF, FF800080, FF969696,
FFFF00FF, FFFFCC00, FFFFFF00, FF00FF00, FF00FFFF, FF00CCFF, FF993366, FFC0C0C0,
FFFF99CC, FFFFCC99, FFFFFF99, FFCCFFCC, FFCCFFFF, FF99CCFF, FFCC99FF, FFFFFFFF,

控えておくと地味に役に立つこともあるかも。

2010年9月29日水曜日

Formを作らずにJSでPOST送信

AタグやButtonのonclickなんかを引っ掛けてPOSTしたい場合、hidden要素しか持たない隠しformを作ったりしていましたが、Javascriptだけで処理できたのでメモ。

※参考
HTMLでformを作らずにJavaScriptでPOST送信

/**
 * データをPOSTする
 * @param String アクション
 * @param Object POSTデータ連想配列
 */
function execPost(action, data) {
 // フォームの生成
 var form = document.createElement("form");
 form.setAttribute("action", action);
 form.setAttribute("method", "post");
 form.style.display = "none";
 document.body.appendChild(form);
 // パラメタの設定
 if (data !== undefined) {
  for (var paramName in data) {
   var input = document.createElement('input');
   input.setAttribute('type', 'hidden');
   input.setAttribute('name', paramName);
   input.setAttribute('value', data[paramName]);
   form.appendChild(input);
  }
 }
 // submit
 form.submit();
}

こんな感じで呼び出し。

<a onclick="execPost('/hoge', {'fuga':'fuga_val', 'piyo':'piyo_val'});return false;" href="#">POST送信</a>