"Unsuccessful: create table"エラーの解決
PlayFrameworkを用いて開発していたところ,db=mem環境では動いていたのに,MySQLに移行したところ,動かなくなってしまった.
調べてみると,Entityクラスを元にMySQLのテーブル自動生成時にエラーがでており,一部のテーブルが生成されていなかった.
元となったEntityクラス
package models; import javax.persistence.Entity; import javax.persistence.Lob; import play.db.jpa.Model; /** * @author Syo.Takasaki */ @Entity public class TestCondition extends Model { @Lob public String condition; public Integer priority; @Lob public String comment; }
出力されたエラー
20:44:43,596 ERROR ~ Unsuccessful: create table TestCondition (id bigint not null auto_increment, comment longtext, condition longtext, priority integer, primary key (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 20:44:43,597 ERROR ~ You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'condition longtext, priority integer, primary key (id)) ENGINE=InnoDB DEFAULT CH' at line 1
なお,MySQLのバージョンは5.1である.
出力されたcreate tableを直接叩いてみたが,同様のエラー.
解決(というか原因)
もしかして予約語使っていないか? と気づき,MySQLの仕様を調べ……る前にカラム名を変えてみた.
結果,conditionを使わなければ見事(?)にテーブルが生成された.
結論:conditionは使ってはダメです.
http://dev.mysql.com/doc/refman/5.1/ja/reserved-words.html の予約語一覧にもしっかり書いてありました.
PlayFrameworkでOutOfMemory
そんなにメモリ食うコードは直せよ,というのはおいておいて.
環境にも寄ると思うが,通常,256MB以上のメモリは確保しないため,OutOfMemoryエラーが発生してしまう.
ので,より多くのメモリを確保できるようにオプションを設定する.
application.confに以下の行を追記する.
jvm.memory=-Xmx512M
結局ファイル書き出しでmecab&cabocha
パフォーマンス低下が著しいし,TOKENの設定が難しいので,入力データも出力データも一旦ファイルシステムを使うことに.
将来的にはtmpfsを使えばいいかな.
オプション指定があるので,前回とは違い,mecabおよびcabocha専用.
CabochaTest.java
import java.io.*; import org.jpn.syo.*; /** * * @author SyoTakasaki */ public class CabochaTest { /** * @param args the command line arguments */ public static void main(String[] args) throws Exception { // INIT String file_text = args[0]; String data_text = null; String data_mecab = null; String data_cabocha = null; // テキストファイルの読み込み data_text = read_file_as_text(file_text); data_text = data_text.replaceAll("。", "。\n"); data_text = data_text.replaceAll(".", ".\n"); // Mecab data_mecab = Execute.exec("mecab", data_text); // Cabocha data_cabocha = Execute.exec("cabocha", data_mecab); System.out.println(data_mecab); } private static String read_file_as_text(String file_path) throws FileNotFoundException, UnsupportedEncodingException, IOException{ StringBuilder sb = new StringBuilder(); BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file_path), "UTF-8")); String line; while((line = br.readLine()) != null){ sb.append(line).append("\n"); } br.close(); return sb.toString(); } }
org/jpn/syo/Execute.java
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.jpn.syo; import java.io.*; /** * * @author SyoTakasaki */ public class Execute { private static final java.util.Random random = new java.util.Random(); // cmdには"mecab"あるいは"cabocha"を受け付ける public static String exec(String cmd, String inputText) throws IOException, InterruptedException, Exception { String result = null; String file_name = null; String command = null; // ファイル名用ランダム文字列 file_name = Long.toHexString(random.nextLong()); file_name = new File(file_name).getAbsolutePath(); // テキストファイルへの書き出し write_file_as_text(file_name + ".in", inputText); // Mecabの場合 if(cmd.equals("mecab")){ command = "/usr/bin/mecab " + file_name + ".in --output=" + file_name + ".out"; } // Cabochaの場合 if(cmd.equals("cabocha")){ command = "/usr/bin/cabocha -f1 -I1 " + file_name + ".in --output=" + file_name + ".out"; } if(command == null){ Exception ex = new Exception("UnsupportedCommandException"); throw ex; } // 実行 Process p = new ProcessBuilder(command.split(" ")).start(); p.waitFor(); p.destroy(); // 結果の読み込み result = read_file_as_text(file_name + ".out"); // ファイル削除 new File(file_name + ".in").delete(); new File(file_name + ".out").delete(); return result; } private static String read_file_as_text(String file_path) throws FileNotFoundException, UnsupportedEncodingException, IOException{ StringBuilder sb = new StringBuilder(); BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file_path), "UTF-8")); String line; while((line = br.readLine()) != null){ sb.append(line).append("\n"); } br.close(); return sb.toString(); } private static void write_file_as_text(String file_path, String text) throws UnsupportedEncodingException, IOException { BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file_path), "UTF-8")); bw.write(text); bw.flush(); bw.close(); } }
Mecab | Cabocha -I1 -f1 from Java
先日,ProcessBuilderを使ったMecabの呼び出しを書いたが,テキストの規模が大きくなるとフリーズする(プロセスがどっかへ逝く)ことが判明した.
Mecabより先にCabochaの方が限界を迎える.
原因は,JavaのBufferedStream(Reader|Writer)のバッファサイズの限界.バッファサイズを大きくしても解決出来なかった.
STDOUTとSTDERRをスレッドで読み込んで……STDINもスレッドで書き出して……とやってみたが,STDINが途中で止まってしまう.(1文STDINに出力する度にSTDOUTに解析結果が出力されることを期待したのだが)
ので,適当なデータサイズで区切って呼び出すようにしてみた.ついでに外部コマンド呼び出しをパッケージ化.
3万行のサンプルデータでの実行を確認.仮想マシン上で45秒.catとパイプを使って実行すると20秒なので,50%以上がオーバーヘッドになってしまった.
TOKEN_SIZEを150にしたところ,手持ちのデータではダメだったので,安全策を取って100に設定.
これは,テキストデータの100行分を1セットにしてMecabやCabochaを呼び出すことを意味する.
本当は,MecabとCabochaの限界は違うから別の値を設定するべきだし,バイト数で考えるべきだが,今日はここまで.
CabochaTest.java
import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import org.jpn.syo.*; /** * * @author SyoTakasaki */ public class CabochaTest { /** * @param args the command line arguments */ public static void main(String[] args) throws Exception { // INIT String file_text = args[0]; String data_text = null; String data_mecab = null; String data_cabocha = null; // テキストファイルの読み込み data_text = read_file_as_text(file_text); data_text = data_text.replaceAll("。", "。\n"); data_text = data_text.replaceAll(".", ".\n"); // Mecab data_mecab = Execute.exec("/usr/bin/mecab", data_text); // Cabocha data_cabocha = Execute.exec("/usr/bin/cabocha -f1 -I1", data_mecab); System.out.println(data_cabocha); } private static String read_file_as_text(String file_path) throws FileNotFoundException, UnsupportedEncodingException, IOException{ StringBuilder sb = new StringBuilder(); BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file_path), "UTF-8")); String line; while((line = br.readLine()) != null){ sb.append(line).append("\n"); } br.close(); return sb.toString(); } }
org/jpn/syo/Execute.java
package org.jpn.syo; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * * @author SyoTakasaki */ public class Execute { private static final int TOKEN_SIZE = 100; public static String exec(String cmd, String inputText) throws IOException, InterruptedException { StringBuilder stdout = new StringBuilder(); StringBuilder stderr = new StringBuilder(); // command List<String> command = new ArrayList<String>(); command.addAll(Arrays.asList(cmd.split(" "))); // input text ArrayList<String> data = new ArrayList<String>(); data.addAll(Arrays.asList(inputText.split("\n"))); // mecab or cabochaの場合,適度なサイズに区切る ArrayList<String> data2 = null; if (command.get(0).endsWith("mecab") || command.get(0).endsWith("cabocha")) { data2 = new ArrayList<String>(); boolean isCabocha = false; boolean isMecab = false; if (command.get(0).endsWith("cabocha")) { isCabocha = true; } if (command.get(0).endsWith("mecab")) { isMecab = true; } int token_size = 0; StringBuilder tmp = new StringBuilder(); for (String line : data) { if ((isCabocha && line.equals("EOS")) || isMecab) { tmp.append(line).append("\n"); if (token_size > TOKEN_SIZE) { data2.add(tmp.toString()); tmp = new StringBuilder(); token_size = 0; } else { token_size++; } } else { tmp.append(line).append("\n"); } } data2.add(tmp.toString()); } else { data2 = data; } for (String d2 : data2){ Process p = new ProcessBuilder(command).start(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(p.getOutputStream())); bw.write(d2); bw.flush(); bw.close(); BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); String line; while ((line = br.readLine()) != null) { stdout.append(line).append("\n"); } p.waitFor(); p.destroy(); } return stdout.toString(); } }
実行
$ java CabochaTest sample.txt
JavaでXMLを簡単に解析する方法
JAXB(Java Architecture for XML Binding)を使う方法を,Yahoo校正支援APIを題材に解説します.
どこら辺が「簡単」なのか?
各種API系のサービスで配布されている,XMLスキーマファイル(xsd)から,Javaクラスファイル群を自動生成することで,
自前のコードはほんの一握り(テンプレートにできそうなぐらい,一部のみです)になり,かつ自前でParseすることを考えたら,手間も品質も雲泥の差です.
正規表現で無理やり,とか怪しすぎるし,SAXやDOMを使えば意のままにコーディングできるけど拡張大変だし.
というわけで,JAXBに丸投げします.
前提環境
- JDK6
- Ubuntu Desktop 10.04(Windowsでもできるとは思いますが)
- Yahoo!デベロッパーネットワークのアプリケーションIDは取得済み
0. JAXB用のクラスファイルの自動生成
まず,http://jlp.yahooapis.jp/KouseiService/V1/kousei.xsd をダウンロードします.次に,Terminalで,以下のコマンドを入力
$ xjc kousei.xsd
すると,以下のディレクトリ,ファイルが生成されます.
./yahoo `-- jp `-- jlp `-- kouseiservice |-- ObjectFactory.java |-- ResultSet.java |-- WordType.java `-- package-info.java
私は,この後に,kouseiserviceと同じところにkousei.xsdを移動しました.
1. サクッとテストプログラム作成
Kousei.javaを作成
import java.net.URL; import java.net.URLEncoder; import javax.xml.XMLConstants; import javax.xml.bind.*; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.xml.sax.SAXException; import yahoo.jp.jlp.kouseiservice.*; /** * * @author Syo Takasaki */ public class Kousei { private static String appid = "dummy"; private static String appuri = "http://jlp.yahooapis.jp/KouseiService/V1/kousei"; private static String path_xsd = "yahoo/jp/jlp/kouseiservice/kousei.xsd"; private static URL schemaURL; private static Schema schema; private static String path_context = "yahoo.jp.jlp.kouseiservice"; private static JAXBContext context; private static Unmarshaller unmarshaller; public static void main(String[] args) throws Exception{ Kousei exe = new Kousei(); ResultSet results = exe.getResults("遙か彼方に小形飛行機が見える."); System.out.println("SENTENCE:遙か彼方に小形飛行機が見える."); for(WordType data : results.getResult()){ System.out.print(data.getStartPos() + "\t"); System.out.print(data.getLength() + "\t"); System.out.print(data.getSurface() + "\t"); System.out.print(data.getShitekiInfo() + "\t"); System.out.println(data.getShitekiWord()); } results = exe.getResults("モルジブ大学行く.大学行く."); System.out.println("SENTENCE:モルジブ大学行く.大学行く."); for(WordType data : results.getResult()){ System.out.print(data.getStartPos() + "\t"); System.out.print(data.getLength() + "\t"); System.out.print(data.getSurface() + "\t"); System.out.print(data.getShitekiInfo() + "\t"); System.out.println(data.getShitekiWord()); } } public Kousei() throws SAXException, JAXBException{ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); schemaURL = this.getClass().getClassLoader().getResource(path_xsd); schema = factory.newSchema(schemaURL); context = JAXBContext.newInstance(path_context); unmarshaller = context.createUnmarshaller(); unmarshaller.setSchema(schema); } public ResultSet getResults(String sentence){ ResultSet results = null; try{ URL _url = new URL(appuri + "?appid=" + appid + "&sentence=" + URLEncoder.encode(sentence, "UTF-8")); results = (ResultSet)unmarshaller.unmarshal(_url); }catch (Exception e){ System.err.println("ERROR:" + e); } return results; } }
2. 実行
$ javac Kousei.java $ java Kousei SENTENCE:遙か彼方に小形飛行機が見える. 0 2 遙か 表外漢字あり ●か 2 2 彼方 用字 彼方(かなた)、かなた 5 5 小形飛行機 誤変換 小型飛行機 SENTENCE:モルジブ大学行く.大学行く. 0 4 モルジブ 外国地名 モルディブ 9 4 大学行く 助詞不足の可能性あり
後書きと懺悔
きっかけは,Play framework上でGroovyがうまく使えなかったから(テンプレートエンジンはGroovyだけど,それ以外のところで使いたかった).
「GroovyならサクッとXMLパースもできるんだけど……Pure Javaか」……と意気消沈したところで,調べてみたわけ.
で,エイヤと作ったはいいものの,エラーハンドリングは適当過ぎるし,いくつかのサイトを参考にしたからコーディングスタイルもごちゃごちゃだし.
そもそも引数でテスト文字列与えられないってどういうことよ,自分orz
でも動いたから満足してしまった.path_xsdとpath_contextが良く分からず,いろんな指定の組み合わせをがんばって,一番良さげな書き方に落ち着いたところが一番満足.
くれぐれも「このまま」は使わないように.
なお,生成は以下のURLの1番目を参考にしてください.
marshallerを追加するだけでいけると思います.
Play frameworkでmecab apiを自炊する
Play frameworkのインストールは,zipを解凍するだけなので省略.
環境はUbuntu Desktop 10.04.MecabはUTF-8阪.
Mecabのインストールは,http://d.hatena.ne.jp/Syo-Takasaki/20090528/1243484754 を参照してください.
api/app/controllers/Mecab.javaの作成
$ which mecab /usr/bin/mecab
package controllers; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import play.mvc.*; public class Mecab extends Controller { public static void mecab(String sentence) throws IOException { StringBuilder sb = new StringBuilder(); Process process = Runtime.getRuntime().exec("/usr/bin/mecab"); OutputStream os = process.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os)); bw.write(sentence + "\n"); bw.flush(); bw.close(); InputStream is = process.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; while((line = br.readLine()) != null){ sb.append(line).append("\n"); } process.destroy(); String mecab_result = sb.toString(); render(mecab_result); } }
Citrix XenServer上でプライベートネットワーク構築
背景(読まなくてOK)
i7 12GBメモリのマシンにCitrix XenServerを入れ,「これで仮想マシン使い放題!」と思ったのもつかの間.
そのまま仮想マシンを作っても,ブリッジ接続にしかならず,IPが枯渇してしまう.IPv4の枯渇も問題だけど,うちのネットワークは既に*.254がDHCPから割り振られてしまう状態.絶賛枯渇中です.
で,XenCenterでNetworkタブを開いてみたら,「Add Network...」のボタンを発見.これはプライベートネットワークを構築すれば,これ以上IP消費せずにいけるんじゃなかろうか? と思い立ったわけです.
Agenda
の5本立てです.
プライベートネットワークの作成
XenServer上では,「Internal Network」.
- XenCenterを立ち上げる
- 左側のツリーから,物理サーバを選ぶ
- 右側上部のタブから「Network」を選ぶ
- 「Add Netowrk...」をクリック
- Internal Networkを作成する
この時点ではIPレンジ等はまったく関係ない.
XenServer内部に,新たなハブが出来上がったと考えると分かりやすいかも.
NATルータとなる仮想マシンの作成
既に仮想マシンがある場合は,シャットダウンしてNetowrkタブを開いて,「Add Interface...」をクリックし,先程作ったInternal Networkを追加.
新規に仮想マシンを作成する場合は,Networkの設定画面にて,「Network 0」はそのままに,新たに作成したInternal Networkも追加.
MAC Addressは自動生成のままで.
あとは普通に(OpenSSHは入れましたが)インストール.
NATルータ上にDHCPサーバ構築
ネットワーク設定
/etc/network/interfacesを編集.編集後,"/etc/init.d/networking restart".
# The loopback network interface auto lo iface lo inet loopback # The primary network interface auto eth0 iface eth0 inet dhcp # ここはstaticに設定して良いが,今回は重要でないのでDHCPのまま. auto eth1 iface eth1 inet static address 192.168.2.1 netmask 255.255.255.0 gateway 192.168.1.1
DHCPサーバ設定
/etc/dhcp3/dhcpd.confに以下を追記.
subnet 192.168.2.0 netmask 255.255.255.0 { range 192.168.2.100 192.168.2.200; option routers 192.168.2.1; }
NATルータの設定
ufwを使う.
https://help.ubuntu.com/10.04/serverguide/C/firewall.html#ip-masquerade-ufw
を参考に(というかそのまま).
/etc/ufw/before.rules に追記
追記というよりも,前追? 既にいろいろと書いてあるのだが,先頭の11文ぐらいのコメントの直後に以下を書く.COMMITまで忘れずに.
# nat Table rules *nat :POSTROUTING ACCEPT [0:0] # Forward traffic from eth1 through eth0. -A POSTROUTING -s 192.168.2.0/24 -o eth0 -j MASQUERADE # don't delete the 'COMMIT' line or these nat table rules won't be processed COMMIT