2008年6月25日水曜日

OpenIdea.jp

OpenIdea.jp というサービスを作ってみました。

作った理由:
  • 日ごろ思いつくアイデアを公開したかった。(普段はメールの下書きでためていました。)
  • 何でも良いからサービスを 1つ公開したかった。
苦手な HTML コーディングや、ロゴなども自分でやってます。
Java、ClickFramework、DbUtils、MySQL などで動いてます。まだ検索やランキングも動いてなかったり。。

今のとこ、何ら新しいことはないのですが、実験場として色々追加していく予定です。

今後の予定:
  • アイデアグラフ(最初はツリー表示でいいかな)
  • OpenID 2.0 対応
  • メール投稿
まだプレスリリースとかしてません。もうちょっと触ったらやろうと思います。

2008年6月4日水曜日

memcached でキーの列挙(2)

前回(memcached でキーの列挙)の続きです。サンプルの Perl コードです。ちょこっといじればイベント駆動のモジュールが作れそう。。

my $size = 1000;
my $cache = Cache::Memcached->new({
servers => ['localhost:11211'],
});
my $slabs = $cache->stats('slabs');
my @stat_ids = ();
for my $host (keys %{$slabs->{hosts}}) {
my $slabs_text = $slabs->{hosts}->{$host}->{slabs};
my @lines = split("\n", $slabs_text);
for my $line (@lines) {
($line =~ /STAT ([0-9]+):chunk_size [0-9]+/) or next;
push(@stat_ids, $1);
}
}
for my $stat_id (@stat_ids) {
my $cmd = "cachedump $stat_id $size";
print "[$cmd]\n";
my $cachedump = $cache->stats($cmd);
for my $host (keys %{$cachedump->{hosts}}) {
my $cachedump_text = $cachedump->{hosts}->{$host}->{$cmd};
my @lines = split("\n", $cachedump_text);
for my $line (@lines) {
($line =~ /^ITEM (.+) \[(.+) b; (.+) s\]/) or next;
my ($key, $b, $s) = ($1, $2, $3);
print "\t$key [$b bytes]\n";
}
}
}

2008年6月3日火曜日

memcached でキーの列挙

memcached で、キーの列挙ができないか調査していたところ、PHPで実現している方のブログ記事を見つけました。( http://blog.cles.jp/item/2141 )

これ Perl の Cache::Memcached でできるだろうと思って挑戦したのですが、何故か stats cachedump が動作しないんです。

そこで telnet を使って直接コマンドをたたいてみることにしました。まずは接続。。
> telnet localhost 11211

次に stats slabs コマンドを実行してみます。
stats slabs
STAT 1:chunk_size 80
STAT 1:chunks_per_page 13107
STAT 1:total_pages 1
STAT 1:total_chunks 13107
STAT 1:used_chunks 13107
STAT 1:free_chunks 0
STAT 1:free_chunks_end 13065
STAT 2:chunk_size 100
STAT 2:chunks_per_page 10485
STAT 2:total_pages 1
STAT 2:total_chunks 10485
STAT 2:used_chunks 10485
STAT 2:free_chunks 0
STAT 2:free_chunks_end 10447
.....
STAT active_slabs 16
STAT total_malloced 16763844
END

この出力結果のうちの STAT {数字} の部分を使って stats cachedump コマンドを実行します。STAT {数字} は連番ではないみたいなので注意が必要です。
stats cachedump {数字} {ダンプするキーの最大数}
ITEM {キー} [{統計情報}]
.....
END

おお。キーの列挙できるじゃん。ちゅうことで Cache::Memcached のソースみたら stats メソッドは引数を最初の 1個しか使ってないのか。
# 中身も見ずにこういう呼び出しをしてしまってた。
$cache->stats('cachedump', 1, 100);

# こうすりゃ良かったんすね。
$cache->stats('cachedump 1 100');

memcached でキーの列挙(2)へ続く

2008年4月17日木曜日

会社案内サイト

知り合いの "タローさん" のお力添えで会社案内サイトができました。ありがとうございます m(_ _)m

http://trycatch.jp/

遊び心のある Flash ですw 肝心の内容がまだまだ薄っぺらいですが、ニュースや事例で埋めていきたいと思います。

2008年4月10日木曜日

意外と面倒くさい autolink

どうも。こんにちは。今回は Wiki などに使われるテキスト中の URL を自動的にリンクする方法について書いてみたいと思います。

Java で正規表現を使って実装するとこんな感じでしょうか。
import java.util.regex.*;
private static Pattern linkPattern = Pattern.compile(
"(https?)(://[\\p{Alnum}\\+\\$\\;\\?\\.%,!#~*/:@&=_-]+)");
public static String link(String s, int length, String suffix) {
StringBuffer buff = new StringBuffer((int) (s.length() * 1.2f));
Matcher m = linkPattern.matcher(s);
while (m.find()) {
String g = m.group();
if (length > 0 && g.length() > length) {
g = g.substring(0, length);
if (suffix != null) {
g += suffix;
}
}
g = m.quoteReplacement(g);
m.appendReplacement(buff, "<a href=\"$1$2\">" + g + "</a>");
}
m.appendTail(buff);
return new String(buff);
}

ところが、ユーザの投稿した文書をこれでフィルタして HTML として出力するときには CSS(クロスサイトスクリプティング)対策としてサニタイジングを行う必要があり、少し注意しなければなりません。例えば、次のような文書をユーザが投稿したとします。
URL サンプルでは <http://example.com/> を使いましょう。

まず、CSS 対策としてサニタイジングします。
URL サンプルでは &lt;http://example.com/&gt; を使いましょう。

これを自動リンクフィルタすると次のようになります。
URL サンプルでは&lt;<a href="http://example.com/&gt;">http://example.com/&gt;</a>を使いましょう。

本来は http://example.com/ をリンクとして出力したいのですが、http://example.com/&gt; がリンクになってしまいます。

これを回避するためにサニタイジングと自動リンクを同時に行います。
private static Pattern linkHtmlPattern = Pattern.compile(
"((https?)(://[\\p{Alnum}\\+\\$\\;\\?\\.%,!#~*/:@&=_-]+))|([&<>\"\'])");
public static String linkHtml(String s, int length, String suffix) {
StringBuffer buff = new StringBuffer((int) (s.length() * 1.2f));
Matcher m = linkHtmlPattern.matcher(s);
while (m.find()) {
String g = m.group();
if (g.length() == 1) {
if (g.equals("&")) {
g = "&amp;";
} else if (g.equals("<")) {
g = "&lt;";
} else if (g.equals(">")) {
g = "&gt;";
} else if (g.equals("\"")) {
g = "&quot;";
} else if (g.equals("\'")) {
g = "&#x27;";
}
m.appendReplacement(buff, g);
continue;
}
if (length > 0 && g.length() > length) {
g = g.substring(0, length);
if (suffix != null) {
g += suffix;
}
}
g = m.quoteReplacement(g);
m.appendReplacement(buff, "<a href=\"$1\">" + g + "</a>");
}
m.appendTail(buff);
return new String(buff);
}
結構面倒くさいです。。色々考えたのですが、今のところこれが一番かなと思います。

Perl の場合は URI::Find という便利なクラスがあります。使い方はこんな感じです。
use URI::Find;
my $finder = URI::Find->new(
sub {
my($uri, $orig_uri) = @_;
return qq {<a href="$uri">$orig_uri</a>};
});
$finder->find(\$text);

では、サニタイジングと併せて使う場合はどうやったらいいのでしょう?すごく悩んでこんな実装にしたことがあります。
$text =~ s/&gt;/ __GT__ /g;
$text =~ s/&lt;/ __LT__ /g;
$text =~ s/&amp;/ __AMP__ /g;
$text =~ s/&quot;/ __QUOT__ /g;
$text =~ s/&#x27;/ __APOS__ /g;
my $finder = URI::Find->new(
sub {
my($uri, $orig_uri) = @_;
return qq {<a href="$uri">$orig_uri</a>};
});
$finder->find(\$text);
$text =~ s/ __GT__ /&gt;/g;
$text =~ s/ __LT__ /&lt;/g;
$text =~ s/ __AMP__ /&amp;/g;
$text =~ s/ __QUOT__ /&quot;/g;
$text =~ s/ __APOS__ /&#x27;/g;

うむむ。もっといい方法ないですかね。。

2008年4月3日木曜日

DbUtils でアンダバー区切りをハンガリアン記法にマッピング

今回は Jakarta Commons の DbUtils について少し書こうと思います。

DbUtils は次のコードサンプルのように SQLクエリの結果を JavaBeans として得ることができるシンプルで便利な機能を持っています。
QueryRunner run = new QueryRunner(dataSource);
ResultSetHandler h = new BeanHandler(Person.class);
Person p = (Person) run.query(
"SELECT * FROM Person WHERE name=?", "John Doe", h);

例えば、Person テーブルの定義が id と name カラムを持つ場合、
create table person (
id integer not null primary key,
name varchar(20) not null
);

Person クラスはこんな感じです。
public class Person implements java.io.Serializable {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

最近 DbUtils を使い始めたのですが、1つ気に入らない問題が出てきました。Java 側の命名規則はハンガリアン記法、データベース側の命名規則はアンダーバー区切りで、次のようなテーブルに対応する JavaBeans は、
create table person (
id integer not null primary key,
first_name varchar(20) not null,
last_name varchar(20) not null
);

first_name カラムは first_name という JavaBeans プロパティにマッピングされるので、専用の setFirst_name などという専用のセッターを用意すればなんとかなりますがあまり美しくありません。
public class Person implements java.io.Serializable {
private int id;
private String firstName;
private String lastName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName= firstName;
}
public String getLastName() {
return lastName;
}
public void setFirstName(String lastName) {
this.lastName= lastName;
}
// NOTE: for DbUtils
public void setFirst_name(String firstName) {
setFirstName(firstName);
}
public void setLast_name(String lastName) {
setLastName(lastName);
}
}

上記のサンプルが書かれているサイトの下の方にも記述がありますが、
http://commons.apache.org/dbutils/examples.html
org.apache.commons.dbutils.BeanProcessor の mapColumnsToProperties メソッドをカスタマイズすることで first_name から firstName へのマッピングを行うことができます。
import java.beans.PropertyDescriptor;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import org.apache.commons.dbutils.BeanProcessor;
public class MyBeanProcessor extends BeanProcessor {
@Override
protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
PropertyDescriptor[] props) throws SQLException {
int cols = rsmd.getColumnCount();
int columnToProperty[] = new int[cols + 1];
Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
for (int col = 1; col <= cols; col++) {
String columnName = rsmd.getColumnName(col);
for (int i = 0; i < props.length; i++) {
// NOTE: 元のコードを
// if (columnName.equalsIgnoreCase(props[i].getName())) {
// NOTE: 次のように変更
if (equalsColumnProperty(columnName, props[i].getName())) {
columnToProperty[col] = i;
break;
}
}
}
return columnToProperty;
}
private boolean equalsColumnProperty(String colName, String propName) {
// NOTE: 頻繁に呼び出されるので実際にはキャッシュを使うなどして
// 高速化したほうがよいです。
return colName.replaceAll("_", "").equalsIgnoreCase(propName);
}
}

カスタマイズした MyBeanProcessor の使い方はこんな感じです。実際はRowProcessor のインスタンスはシステムに 1つでいいかと思います。
RowProcessor rp = new BasicRowProcessor(new MyBeanProcessor());
QueryRunner run = new QueryRunner(dataSource);
ResultSetHandler h = new BeanHandler(Person.class, rp);
Person p = (Person) run.query(
"SELECT * FROM Person WHERE first_name=?", "John Doe", h);

2008年4月2日水曜日

会社設立!

4月1日づけで登記申請を行いました。この日付が会社設立になるようです。専門の方の助けを借りるとあっという間です。
会社作ると言い出してからはや5年ぐらい。 長かったー。パチパチ。
早速、何かのモノの本で読んだ最初に決めるべき経営理念とやらを書いてみます。

経営理念
  • 自分、まわりの人、ひいてはすべての人を幸せに。
  • 常に創意工夫を。
  • サービスをもって文化、社会の発展に貢献を。
ふむ。まぁまぁですかね。。今考えたんで。

もうちょっと具体的な私個人の「~したい」をまとめると
  • 世間に認知され使われるサービスを作りたい。
  • たくさんの人を雇いたい。もしくは仕事の基盤を作り出したい。
  • 自社ビルを持ちたい。
こんな感じでしょうか。「たくさんの人を雇いたい」については、たまに実家に帰ると景気が悪いような話を聞いたり、職のない若者がいると聞いていたのでなんとかしないといけないなーと常々思っていることです。「自社ビルをもちたい」は幼稚園のころ母親に約束をしていたようです。今はまったくもって道筋は立っていませんが、いつかは。。という目標にして忘れないようにしたいです。

さて、次は事業計画やら会社のホームページ作らなければなりません。(今から作るんです。遅すぎですかねw)