Edge にも Extension
Edge にも Extension が実装されるのか。まだ Preview のようだけど。
Microsoft Edge extensions now available to preview - Microsoft Edge Blog
Content-Disposition によるダウンロードファイル名の指定について
Content-Disposition ヘッダーによるダウンロード時のファイル名の指定について、色々調べたことをまとめます。
まずファイル名の指定方法には2種類あり、ブラウザによって対応状況が違います。
- attachment; filename="hogehoge.pdf"
- attachment; filename*=uti-8'ja'hogehoge.pdf
いずれも RFC 5987 に規定されている書き方に準拠しているので、両方とも正しいです。
IE の場合、IE9 は両方に対応していますが、IE6-8 は前者のみに対応しています。
Chrome (18.0) と Firefox (11.0) は両方に対応しています。Safari (5.1.5) は前者のみで、2バイト文字の取り扱いはできません。
ファイル名はパーセントエンコードする必要があります。パーセントエンコーディングについては、RFC 3986 で規定されています。HTMLのフォームからGET,POST した時にお目にかかるx-www-form-encoded エンコード方式に類似しているものですが、半角スペースを "%20" に変化するか "+" に変換するかの違い等があるので注意が必要です。
- 全ブラウザ
- 半角英数字はパーセントエンコーディングが不要
- 半角スペースは"%20"に。"+" への変換ではNG。
- 2バイト文字はパーセントエンコーディングが必要。
- 半角記号については、ブラウザによって差異がある。
- IE6-8, IE9 (attachment; filename="hogehoge.pdf"の場合)
-- "#" ";" "%" はパーセントエンコーディングが必要。
-- その他の半角記号はエンコーディング不要。
- Chrome (attachment; filename*=uti-8'ja'hogehoge.pdf; の場合)
-- "'" "," ";" "%" はパーセントエンコーディングが必要。
-- その他の半角記号はエンコーディング不要。
-- "(" ")" などデコードができないので、エンコーディングしてはいけない文字もある。
- Firefox (attachment; filename*=uti-8'ja'hogehoge.pdf の場合)
-- Chrome より賢いので、Chrome と同じで良い。
Cassandra へ Java でアクセスする
どうも 6月2日に新しい安定バージョン 0.8.0 がリリースされていた模様。
それだとまた元のプログラムのままでは動かない!
以下が修正後のサンプルです。
が変わっています。
import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.List; import org.apache.cassandra.thrift.Cassandra; import org.apache.cassandra.thrift.Column; import org.apache.cassandra.thrift.ColumnOrSuperColumn; import org.apache.cassandra.thrift.ColumnParent; import org.apache.cassandra.thrift.ColumnPath; import org.apache.cassandra.thrift.ConsistencyLevel; import org.apache.cassandra.thrift.InvalidRequestException; import org.apache.cassandra.thrift.KeyRange; import org.apache.cassandra.thrift.KeySlice; import org.apache.cassandra.thrift.NotFoundException; import org.apache.cassandra.thrift.SlicePredicate; import org.apache.cassandra.thrift.SliceRange; import org.apache.cassandra.thrift.TimedOutException; import org.apache.cassandra.thrift.UnavailableException; import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; /** * @author hatanaka */ public class CassandraTest { /** キースペース */ public static final String KEYSPACE = "Keyspace1"; /** カラムファミリー */ public static final String COLUMN_FAMILY = "Standard2"; /** * @param args */ public static void main(String[] args) { TSocket socket = new TSocket("192.168.1.87", 9160); TTransport transport = new TFramedTransport(socket); TProtocol protocol = new TBinaryProtocol(transport); Cassandra.Client client = new Cassandra.Client(protocol); try { transport.open(); client.set_keyspace(KEYSPACE); final long timestamp = System.currentTimeMillis(); //特定キーに1件カラムをインサート Column value = new Column(strToBB("sample1")); value.setValue(strToBB("サンプルの値")); value.setTimestamp(timestamp); client.insert(strToBB("test"), new ColumnParent(COLUMN_FAMILY), value, ConsistencyLevel.ONE); System.out.println("インサート完了."); //特定キーの特定カラムの値を取得 System.out.println(); System.out.println("特定キーの特定カラムの値を取得"); ColumnPath path = new ColumnPath(COLUMN_FAMILY); path.setColumn(strToBB("sample1")); ColumnOrSuperColumn column = client.get(strToBB("test"), path, ConsistencyLevel.ONE); System.out.println(byteToStr(column.getColumn().getName()) + ":" + byteToStr(column.getColumn().getValue())); //特定キーの全カラムの値を取得 System.out.println(); System.out.println("特定キーの全カラムの値を取得"); SlicePredicate predicate = new SlicePredicate(); predicate.setSlice_range(new SliceRange(ByteBuffer.wrap(new byte[0]), ByteBuffer.wrap(new byte[0]), false, 10)); List<ColumnOrSuperColumn> columns = client.get_slice(strToBB("test"), new ColumnParent(COLUMN_FAMILY), predicate, ConsistencyLevel.ONE); for (ColumnOrSuperColumn aColumn : columns) { System.out.println(byteToStr(aColumn.getColumn().getName()) + ":" + byteToStr(aColumn.getColumn().getValue())); } //複数キーの全カラムの値を取得 System.out.println(); System.out.println("複数キーの全カラムの値を取得"); KeyRange range = new KeyRange(); range.setStart_key(new byte[0]); range.setEnd_key(new byte[0]); List<KeySlice> keys = client.get_range_slices(new ColumnParent(COLUMN_FAMILY), predicate, range, ConsistencyLevel.ONE); for (KeySlice key : keys) { for (ColumnOrSuperColumn aColumn : key.getColumns()) { System.out.println(byteToStr(key.getKey()) + ":" + byteToStr(aColumn.getColumn().getName()) + ":" + byteToStr(aColumn.getColumn().getValue())); } } } catch (InvalidRequestException e) { throw new RuntimeException(e); } catch (UnavailableException e) { throw new RuntimeException(e); } catch (TimedOutException e) { throw new RuntimeException(e); } catch (TException e) { throw new RuntimeException(e); } catch (NotFoundException e) { throw new RuntimeException(e); } finally { try { transport.flush(); } catch (TTransportException e) { throw new RuntimeException(e); } finally { transport.close(); } } } /** * String から ByteBuffer へのコンバータ * @param msg String * @return ByteBuffer */ private static ByteBuffer strToBB(String msg) { Charset charset = Charset.forName("UTF-8"); return ByteBuffer.wrap(msg.getBytes(charset)); } /** * byte[] から String へのコンバータ * @param buf byte[] * @return String */ private static String byteToStr(byte[] buf) { Charset charset = Charset.forName("UTF-8"); return new String(buf, charset); } }
Cassandra に Java でアクセス
Cassandra に Java でアクセスする方法について、試行錯誤した内容をまとめます。
アクセスには高レベルAPIと低レベルAPIの2種類方法があるのですが、ここでは低レベルAPIについて記載しています。
Cassandra の最新版(2011年6月3日時点で0.7.6-2)の場合、公式ページにあるサンプルでは、コンパイルが通りません。おそらく API が変わったのだと思われます。
サンプルと同じ内容のプログラムを、0.7.6-2でコンパイルおよび実行可能なように書き直したものが以下です。
import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.List; import org.apache.cassandra.thrift.Cassandra; import org.apache.cassandra.thrift.Column; import org.apache.cassandra.thrift.ColumnOrSuperColumn; import org.apache.cassandra.thrift.ColumnParent; import org.apache.cassandra.thrift.ColumnPath; import org.apache.cassandra.thrift.ConsistencyLevel; import org.apache.cassandra.thrift.InvalidRequestException; import org.apache.cassandra.thrift.NotFoundException; import org.apache.cassandra.thrift.SlicePredicate; import org.apache.cassandra.thrift.SliceRange; import org.apache.cassandra.thrift.TBinaryProtocol; import org.apache.cassandra.thrift.TimedOutException; import org.apache.cassandra.thrift.UnavailableException; import org.apache.thrift.TException; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; /** * @author hatanaka */ public class CassandraTest { /** キースペース */ public static final String KEYSPACE = "Keyspace1"; /** カラムファミリー */ public static final String COLUMN_FAMILY = "Standard2"; /** * @param args */ public static void main(String[] args) { TSocket socket = new TSocket("192.168.28.129", 9160); TTransport transport = new TFramedTransport(socket); //TSocketを直接引数に取れるが、TFramedTransportでないと、以降の処理でエラー TProtocol protocol = new TBinaryProtocol(transport); Cassandra.Client client = new Cassandra.Client(protocol); try { transport.open(); client.set_keyspace(KEYSPACE); final long timestamp = System.currentTimeMillis(); //特定キーに1件カラムをインサート client.insert(strToBB("test"), new ColumnParent(COLUMN_FAMILY), new Column(strToBB("sample1"), strToBB("サンプルの値"), timestamp), ConsistencyLevel.ONE); System.out.println("インサート完了."); //特定キーの特定カラムの値を取得 ColumnPath path = new ColumnPath(COLUMN_FAMILY); path.setColumn(strToBB("sample1")); ColumnOrSuperColumn column = client.get(strToBB("test"), path, ConsistencyLevel.ONE); System.out.println(byteToStr(column.getColumn().getName()) + ":" + byteToStr(column.getColumn().getValue())); //特定キーの全カラムの値を取得 SlicePredicate predicate = new SlicePredicate(); predicate.setSlice_range(new SliceRange(ByteBuffer.wrap(new byte[0]), ByteBuffer.wrap(new byte[0]), false, 10)); List<ColumnOrSuperColumn> columns = client.get_slice(strToBB("test"), new ColumnParent(COLUMN_FAMILY), predicate, ConsistencyLevel.ONE); for (ColumnOrSuperColumn aColumn : columns) { System.out.println(byteToStr(aColumn.getColumn().getName()) + ":" + byteToStr(aColumn.getColumn().getValue())); } } catch (InvalidRequestException e) { throw new RuntimeException(e); } catch (UnavailableException e) { throw new RuntimeException(e); } catch (TimedOutException e) { throw new RuntimeException(e); } catch (TException e) { throw new RuntimeException(e); } catch (NotFoundException e) { throw new RuntimeException(e); } finally { try { transport.flush(); } catch (TTransportException e) { throw new RuntimeException(e); } finally { transport.close(); } } } /** * String から ByteBuffer へのコンバータ * @param msg String * @return ByteBuffer */ private static ByteBuffer strToBB(String msg) { Charset charset = Charset.forName("UTF-8"); return ByteBuffer.wrap(msg.getBytes(charset)); } /** * byte[] から String へのコンバータ * @param buf byte[] * @return String */ private static String byteToStr(byte[] buf) { Charset charset = Charset.forName("UTF-8"); return new String(buf, charset); } }
挿入検索のメソッドの引数が違っているのと、キーストアの指定の仕方も違っています。
簡単なプログラムなのに、perlのサンプルを見たり、色々苦労しました...
マルチインスタンスタスク(続き)
まずはじめに、前回の内容に誤りがあったので訂正します。
マルチインスタンスタスクにおいて、「いくつのインスタンスが生成されるか」という指定は、"loopCardinality" 属性または "loopDataInput" 属性で指定されます。
- "loopCardinality"属性が使われる場合は、この属性で指定されている値(計算式の結果)に基づいて、インスタンスが生成されます。5の場合は、5つインスタンスが生成されます。
- "loopDataInput"属性が使われる場合は、この属性で指定された入力データ(コレクションデータであることが前提)の要素の数だけ、インスタンスが生成されます。
いずれの場合においても、評価が行われるのは最初の1度だけで、その時に生成されるインスタンス数が決定されます。
続いて「いつインスタンスが終了するか」に言及します。どこかのタイミングでマルチインスタンスタスク全体を終了させて、トークンを次のノードに向けて動かす必要があります。それはどのタイミングでしょうか。
これは "completionCondition" 属性によって決定されます。マルチインスタンスタスク内のインスタンス1つ1つが終了する度ごとに、この属性の値が評価されます。
評価された結果、true になるとマルチインスタンスタスクは終了します。この時点で終了していないマルチインスタンスタスク内のインスタンスについては、キャンセルされます。
これによって「最初の1つが終了したら、他はすべてキャンセルする」「全部が終了するまで、全体も終了しない」といった終了条件や、もっと複雑な終了条件を表現することができるようになっています。
実は Questetra BPM Suite の「チームタスク」は、この「マルチインスタンスタスク」の特殊な形といえます。
内部実装としてインスタンスは1つしか生成されていないのですが、
- loopCardinality は 担当者の数
- completionCondition は 1つインスタンスが終了したら、全体も終了
という形になっているといえます。
マルチインスタンスタスク
BPMN のタスクの種類として、マルチインスタンスタスクというものがあります。プロセスにおいて、トークンがこのマルチインスタンスタスクに到達すると、タスク(インスタンス)が複数生成されます。
たとえば「確認」というタスクがあって、これを複数の人が実施する場合などに使用します。分岐を使ってフローを複数に分けることで表現することもできますが、複数のタスク(インスタンス)でフローを分ける必要がない場合には、シンプルに記述することができます。
ただしこのタスク(インスタンス)の生成の仕方で、大きく2つに分かれます。
「Parallel(並列)」と「Sequential(直列)」の2種類です。マーカーも異なります。
こちらは並列です。トークンが到達すると同時に、複数のインスタンスが生成され実行されます。
こちらは直列です。トークンが到達すると1つインスタンスが生成されます。1つが終わるとその次が生成され、という形で、順々に実行されます。ループタスクというのが別にあるのですが、それとの違いは正直、まだよく解っていないです。後日の課題にします。
さて並列においても直列においても、「いくつのインスタンスが生成されるか」という問題があります。これについては "loopCounter""loopCardinality" または "loopDataInput" 属性で指定します。この属性が "5" の場合、5つインスタンスが生成されます。
もう1つ「いつインスタンスが終了するか」という問題があります。これについては、後日まとめます。
タスクの種類
XPDLでは、タスクに複数の種類があります。ひとまず仕様書を抜粋して、一覧にしました。ちょっと違いの分からないものがいくつかあります。
- TaskService
- A Service Task is a Task that provides some sort of service, which could be a Web service or an automated application.
- TaskSend
- A TaskType of Send MUST NOT have an incoming Message Flow. A Send Task is a simple Task that is designed to send a message to an external participant (relative to the Business Process). Once the message has been sent, the Task is completed.
- TaskReceive
- A TaskType of Receive MUST NOT have an outgoing Message Flow. A Receive Task is a simple Task that is designed to wait for a message to arrive from an external participant (relative to the Business Process). Once the message has been received, the Task is completed.
- TaskUser
- A User Task is a typical workflow task where a human performer performs the Task with the assistance of a software application and is scheduled through a task list manager of some sort.
- TaskManual
- A TaskType of Manual MUST NOT have an incoming or an outgoing Message Flow.
- TaskApplication
- The Activity is implemented by (one or more) tools. A tool may be an application program (link to entity Application); which may be invoked via Interface 3 (WfMC) - see the Workflow Client Application API (WAPI - Interface 2).
- TaskScript
- A Task Type of Script MUST NOT have an incoming or an outgoing Message Flow. A Script Task is executed by a business process engine. The modeler or implementer defines a script in a language that the engine can interpret. When the Task is ready to start, the engine will execute the script. When the script is completed, the Task will also be completed.
- TaskReference
- There may be times where a modeler may want to reference another activity that has been defined.
BPMNでも、タスクに複数の種類があるので、同じく一覧にします。
- Service Task
- A Service Task is a Task that uses some sort of service, which could be a Web service or an automated application.
- Send Task
- A Send Task is a simple Task that is designed to send a Message to an external Participant (relative to the Process). Once the Message has been sent, the Task is completed.
- Receive Task
- Receive Task is a simple Task that is designed to wait for a Message to arrive from an external Participant (relative to the Process). Once the Message has been received, the Task is completed.
- User Task
- A User Task is a typical “workflow” Task where a human performer performs the Task with the assistance of a software application and is scheduled through a task list manager of some sort.
- Manual Task
- A Manual Task is a Task that is expected to be performed without the aid of any business process execution engine or any application. An example of this could be a telephone technician installing a telephone at a customer location.
- Business Rule Task
- A Business Rule Task provides a mechanism for the Process to provide input to a Business Rules Engine and to get the output of calculations that the Business Rules Engine might provide.
- Script Task
- A Script Task is executed by a business process engine. The modeler or implementer defines a script in a language that the engine can interpret. When the Task is ready to start, the engine will execute the script. When the script is completed, the Task will also be completed.
XPDL と BPMN は相互参照しながら仕様を策定していると思われるので、まず似ていると思うのですが、この部分はまったく一緒といっても良さそうですね。文章もコピーしたと思われます。
以下に一覧としてまとめました。
| BPMN | XPDL | 説明 |
|---|---|---|
| Service Task | TaskService | サービス(典型的にはWebサービス)によって実行されるタスク |
| Send Task | TaskSend | プロセスに関係している外部のParticipant(人や組織とは限らない)にメッセージを送るタスク |
| Receive Task | TaskReceive | プロセスに関係している外部のParticipant(人や組織とは限らない)からメッセージを受け取るタスク |
| User Task | TaskUser | ソフトウェアを使って、人によって処理されるタスク。いわゆるワークフローシステムにおけるタスク |
| Manual Task | TaskManual | ソフトウェアの助けを借りずに、人によって処理されるタスク。 |
| Business Rule Task | - | Business Rule Engine によって処理されるタスク。Service Task の特別なものと考えるべきか。 |
| - | TaskApplication | 何らかのアプリケーションによって処理されるタスク。Workflow Client Application API (WAPI) を介して実行される想定。 |
| Script Task | TaskScript | BusinessProcessEngine 自信によって、自動処理されるタスク。処理内容はスクリプトによって記述される。 |
| - | TaskReference | 他のタスク定義を参照する。複数同じ定義を行う場合に使用する。 |
XPDL における TaskApplication の位置づけが正直よく解らないのですが、それ以外は解りやすいです。何らかのソフトウェアによって自動処理されるものが、Service Task, Business Rule Task, Script Task の3種類で、人によって処理されるものが User Task と Manual Taskの2種類。人かソフトウェアか、プロセスの外部にメッセージを送信 or 受信するだけのものが、Send Task, Receive Task になります。

