GlassFish V3 Previewで最新のEclipseLinkを触ってみたけど

そろそろJPA2.0の実装とかも進んでるかな?と思って動かしてみましたが・・・

Caused by: javax.persistence.PersistenceException: Not Yet Implemented
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.getQueryBuilder(EntityManagerImpl.java:1924)

EntityManager#getQueryBuilderすら実装されていないようです(苦笑)
ドキュメントだけ読んでもあまりやる気しないし、動く実装が出てくるまではまだまだ様子見といったところでしょうか。
最近C#を勉強しているのですが、JPQLもLINQのように言語レベルでサポートされてコンパイルチェックされて、DB以外の様々なリソースに対してもクエリ発行出来るようになれば、一皮向ける気がしました。・・・が、保守的なJavaでそんなことが実現される日は多分来ないでしょうね。

CakePHPのPaginator

自分が忘れたときの為にメモ。
Controllerのpaginateを利用するとき、細かな条件は$this->paginateフィールドに定義してやるとOK。形式はfindに渡す定義と同じ。
CakeのModelのJOIN定義は、SELECT文発行するときにあまり柔軟に使えないのが難点ですが、このパラメータにjoinsという名前で定義してやれば、柔軟に選択することが出来るようになります。

$this->paginate['joins'] = array(
			array(
				'type' => 'INNER',
				'table' => 'users',
				'alias' => 'User',
				'conditions' => 'Product.user_id = User.id'
			),
			array(
				'type' => 'LEFT OUTER',
				'table' => 'companies',
				'alias' => 'Company',
				'conditions' => 'Product.company_id = Company.id'
			)
);

こんな感じで。難点は、aliasやconditionsをいちいち書かなきゃいけないところでしょうか。せっかくModelに結合定義しているのだから、関連するModel名を書いておけば、ここら辺は省略可能とかできたらより便利になるんでしょうけど。
また、GROUP BY等を設定していると、ページングの総数を取得するSQLがおかしくなって正常な総数が取れなくなってしまいます。これは渡した条件のfields部分を、Cake側が単純にcount(*)に置き換えているのが原因です。本来なら、Hibernate等のように、作成されたSQL文全体を副問い合わせとして、その結果にたいするCOUNT(*)を取ってやるのが確実なんですけど、今のCakeの作りだとその方法が取り辛いです。なので、利用するModelにpaginateCountメソッドを定義して、そこで個別に対応することになります。前述したGROUP BYが原因の場合は、group定義をunsetして(参照渡ししないように注意)count(distinct )とか使ってデータ取得時と同じ件数になるように調整してみました。
後、ハマったのがViewで使うHelperの部分。何も考えずにpaging関連のHelperを使うと、FORMで定義したパラメータがpagingでは一つも送られないという酷い状態になります(苦笑)。ここでは、引数に

	<?php echo $paginator->prev('<< '.__('previous', true), array('url' => $this->data['User}']), null, array('class'=>'disabled'));?>
 | 	<?php echo $paginator->numbers(array('url' => $this->data['User']));?>
	<?php echo $paginator->next(__('next', true).' >>', array('url' => $this->data['User']), null, array('class'=>'disabled'));?>

のようにパラメータを渡してやり、Controller側では

		if (!isset($this->data['User']['name'])
			&& isset($this->passedArgs['name'])) {
			$this->data['User']['name'] = $this->passedArgs['name'];
		}
		$this->User->set($this->data);
		if (!$this->User->validates()) {
			$this->render('index');
			return; 
		}

みたいな感じで、passedArgsからも値を取得出来るようにしておきます。Cakeのvalidateはinsert、update時にしか自動で呼ばれないので、SELECT文で使うときには個別に呼び出してやる必要があります。ここもちょっと面倒・・・

CakePHPのScaffold作成クラスをまとめる

ここ一ヶ月ほど、CakePHPを使ったシステムの仕事をしていました。その中で調べたり作ったりしたものを忘れたときの為にメモ。
まずは、簡単なマスタメンテ画面をScaffold作成ソースをベースに作成したのですが、どれも似たようなソースになるので、できるだけ一つのクラスにまとめられないか検討してみました。まずは、Controllerクラスから。

<?php
class AppController extends Controller {
}

class ModelController extends AppController {
	var $helpers = array('Html', 'Form', 'Time');

	function __construct() {
		parent::__construct();
		$this->indexDataName = strtolower(substr($this->name, 0, 1)) . substr($this->name, 1);
		$this->viewDataName = strtolower(substr($this->modelClass, 0, 1)) . substr($this->modelClass, 1);
		App::import('Model', 'Column');
		App::import('Model', 'Table');
		$this->Column = new Column();
		$this->Table = new Table();
	}
	
	function index() {
		return $this->admin_index();
	}

	function view($id = null) {
		return $this->admin_view($id);
	}

	function add() {
		return $this->admin_add();
	}

	function edit($id = null) {
		return $this->admin_edit($id);
	}

	function delete($id = null) {
		return $this->admin_delete($id);
	}


	function admin_index() {
		$this->{$this->modelClass}->recursive = 0;
		$this->set($this->indexDataName, $this->paginate());
		$this->set('tables', $this->Table->getTableName($this->{$this->modelClass}));
		$this->set('columns', $this->Column->getColumns($this->{$this->modelClass}));
		$this->render('index');
	}

	function admin_view($id = null) {
		if (!$id) {
			$this->Session->setFlash(__('Invalid '. $this->modelClass .'.', true));
			$this->redirect(array('action'=>'index'));
		}
		$this->set($this->viewDataName, $this->{$this->modelClass}->read(null, $id));
		$tables = $this->Table->getTableName($this->{$this->modelClass});
		$columns = $this->Column->getColumns($this->{$this->modelClass});
		$relations = array_merge(
			$this->{$this->modelClass}->hasOne,
			$this->{$this->modelClass}->hasMany,
			$this->{$this->modelClass}->hasAndBelongsToMany);
		foreach ($relations as $key => $value) {
			$tables = array_merge($tables, $this->Table->getTableName($this->{$this->modelClass}->{$key}));
			$columns = array_merge($columns, $this->Column->getColumns($this->{$this->modelClass}->{$key}));
		}
		$this->set('tables', $tables);
		$this->set('columns', $columns);
		$this->render('view');
	}

	function admin_add() {
		if (!empty($this->data)) {
			$this->{$this->modelClass}->create();
			if ($this->{$this->modelClass}->save($this->data)) {
				$this->Session->setFlash(__('The '.$this->modelClass.' has been saved', true));
				$this->redirect(array('action'=>'index'));
			} else {
				$this->Session->setFlash(__('The '.$this->modelClass.' could not be saved. Please, try again.', true));
			}
		}
		foreach ($this->{$this->modelClass}->belongsTo as $key => $value) {
			$list = $this->{$this->modelClass}->{$key}->find('list');
			$attrName = Inflector::pluralize($key);
			$this->set(strtolower(substr($attrName, 0, 1)) . substr($attrName, 1), $list);
		}
		$this->set('tables', $this->Table->getTableName($this->{$this->modelClass}));
		$this->set('columns', $this->Column->getColumns($this->{$this->modelClass}));
		$this->render('add');
	}

	function admin_edit($id = null) {
		if (!$id && empty($this->data)) {
			$this->Session->setFlash(__('Invalid '.$this->modelClass, true));
			$this->redirect(array('action'=>'index'));
		}
		if (!empty($this->data)) {
			if ($this->{$this->modelClass}->save($this->data)) {
				$this->Session->setFlash(__('The '.$this->modelClass.' has been saved', true));
				$this->redirect(array('action'=>'index'));
			} else {
				$this->Session->setFlash(__('The '.$this->modelClass.' could not be saved. Please, try again.', true));
			}
		}
		if (empty($this->data)) {
			$this->data = $this->{$this->modelClass}->read(null, $id);
		}
		foreach ($this->{$this->modelClass}->belongsTo as $key => $value) {
			$list = $this->{$this->modelClass}->{$key}->find('list');
			$attrName = Inflector::pluralize($key);
			$this->set(strtolower(substr($attrName, 0, 1)) . substr($attrName, 1), $list);
		}
		$this->set('tables', $this->Table->getTableName($this->{$this->modelClass}));
		$this->set('columns', $this->Column->getColumns($this->{$this->modelClass}));
		$this->render('edit');
	}

	function admin_delete($id = null) {
		if (!$id) {
			$this->Session->setFlash(__('Invalid id for '.$this->modelClass, true));
			$this->redirect(array('action'=>'index'));
		}
		$this->{$this->modelClass}->getDatasource()->begin($this->{$this->modelClass});
		if ($this->{$this->modelClass}->del($id)) {
			$this->{$this->modelClass}->getDatasource()->commit($this->{$this->modelClass});
			$this->Session->setFlash(__($this->modelClass.' deleted', true));
			$this->redirect(array('action'=>'index'));
		} else {
			$this->{$this->modelClass}->getDatasource()->rollback($this->{$this->modelClass});
		}
	}
}
?>

それぞれのControllerは、自分の名前に紐付けられたモデル名を$this->modelClassに持っているので、$this->{$this->modelClass}と書いてやればそのモデルを呼び出すことが出来ます。
ここでは、Tableモデルがinformation_schemaのtablesテーブルを、Columnモデルがcolumnsテーブルを見に行ってます。そのソースはこちら

<?php
class Table extends AppModel {
	var $useDbConfig = 'info_schema';
	
	
	function getTableName($model) {
		$data = $this->find('first', array(
			'conditions' => array('Table.table_schema' => $model->getDataSource()->config['database'],
								'Table.table_name' => $model->table),
			'fields' => array('Table.table_comment')));
		if ($data['Table']['table_comment']) {
			$data2 = mb_split(';', $data['Table']['table_comment']);
			$data['Table']['table_comment'] = $data2[0];
		}
		return array($model->name => $data['Table']['table_comment']);
	}
}
?>
<?php
class Column extends AppModel {
	var $useDbConfig = 'info_schema';
	
	
	function getColumns($model) {
		$list = $this->find('all', array(
			'conditions' => array('Column.table_schema' => $model->getDataSource()->config['database'],
								'Column.table_name' => $model->table),
			'fields' => array('Column.column_name', 'Column.column_comment')));
		$returnList = array();
		foreach($list as $data) {
			$returnList[$model->name][$data['Column']['column_name']] = $data['Column']['column_comment'];
		}
		return $returnList;
	}
}
?>

これらのモデルを使って、DBのスキーマに定義したテーブルやカラムに対するコメントを取得しています。information_schemaを見る権限が必要というところがちょっと気になる部分ではありますが。
あとは、ModelControllerを継承してControllerを作成してやればOK。

飛行機に乗れなかった

朝一の飛行機で帰省する筈が、突然の腹痛で乗れませんでした。
昨日の食事にあたったっぽいです・・・
乗る前に解約できたからお金は大丈夫だったけど、地元の忘年会とか、親にMac教える予定とか、いろいろ潰れたのが残念。
仕方ないから、回復したら散らかってる部屋の掃除でもするか・・・

技術者もビジネスとしての開発を考える

今年の後半から仕事の方向性を少しずつ変えていってまして、最近は小規模案件を中心に受託の仕事をしています。以前と比べて短期の仕事が増えるので、営業活動・プレ活動が重要になったり、エンドユーザに直接成果物を見せたりデモをしたりという機会が増えてきました。
・・・で、これがなかなか面白い。昔営業やっていた頃もエンドユーザと直接話すのは様々な刺激があったり顧客視点が改めてわかったりして面白かったものですが、技術者としてエンドユーザに触れる機会が増えると、自分が作ったシステムを利用者に直接説明する場面が多くて、自分の成果物に対する反応がダイレクトに見れてとても刺激になります。
思えば、営業時代は直接自分がシステム構築に関われないことにストレスを感じ、開発者時代はあまりに階層化された開発体制の中に埋もれて、エンドユーザが全く見えない仕事内容に違和感を感じたものでした。営業側は開発者との距離が遠すぎて、開発者側はエンドユーザとの距離が遠すぎる・・・というのが今までやってきた開発の仕事に対して一番問題を感じた部分でした。
システム開発の環境的な問題を改善する為に努力するのも一つの道ですが、自分はもう一つの道として、既存のシステム開発の仕事からはちょっと離れた場所を模索してみようと思っています。大企業相手に、大企業が管理して、何層もの下請構造の中に挟まれて仕事をしていると、やはり大部分の人は本質的ではない部分に振り回されて、技術者としての本分を見失ってしまうことが多いと感じています。実際、そうなってしまった職場や人をこれまで沢山見てきました。だからこそ、技術者も一つの仕事を単に技術的な側面だけではなく、ビジネス・商売といった側面で捉えて「より良い仕事が出来るビジネス」を自分で模索することが、これからは重要になっていくのではと感じています。

Amazon EC2

仕事がらみで少し触ったのですが、あっという間に好きなサーバ環境が立ち上げられるのはとても面白いですね。初期費用もほとんどかからないし。
ただ従量制なので、立ち上げっぱなしにすると安価な専用サーバサービス並の金額はかかってしまうみたいです。それでも十分魅力的ではありますが。
様々なハード保守まで加えたシステムのトータルサポートは、現状では一部のメーカー系SIerでしか提供することができないのが現状ですが、EC2のようなサービスを利用すれば、ソフトベンダーでも結構なレベルのトータルサポートを提供できるのでは・・・とか感想を持ちました。今後も仕事の合間にでも触ってみたいですね。