ちょっと脇道系でしょうか?InfinispanのTree APIが気になっていたので、遊んでみました。
Tree API Module
https://docs.jboss.org/author/display/ISPN/Tree+API+Module
けっこう面白そうな機能なのですが、予想外のところでハマったりしましたけど…。
Tree APIとは?
データを階層化して格納するためのAPIで、Fqn(Fully Qualified Name)で表現されたパスを使用します。例えば、
/this/is/a/fqn/path /another/path
みたいな感じで。特別なパスとして、rootもあり
/
で表現されます。
Infinispanの前身であるJBoss Cacheで提供していた機能に似たようなAPIがあったようで、移行しやすくしてますよ〜みたいなことを、ドキュメントでは謳っています。
はい。
で、Fqnで定義されたパスは、Nodeとして表現されます。そして、Nodeにはキーと値のペア(要はMapです)を保存することができます。
ここ、最初ちょっとわかりにくいのですが…。例えば、NoSQLの分類をFqnで表して、キーをプロダクト名、値をURLみたいな感じで表現すると、こんな感じでしょうか。あと、RDBMSもつけてみましょう。
パス(Fqn)=Node | キー | 値 |
---|---|---|
/NoSQL/カラムデータベース | Cassandra | http://cassandra.apache.org/ |
HBase | http://hbase.apache.org/ | |
/NoSQL/ドキュメントデータベース | MongoDB | http://www.mongodb.org/ |
/RDBMS | MySQL | http://dev.mysql.com/ |
PostgreSQL | http://www.postgresql.org/ |
で、この表は最後に考えたので、今から載せるサンプルとはちょっと違いますが…まあ、APIを使ってみます。
Tree APIを使ってみる
最初にビルドの用意ですが、Tree APIはsbtを使うとクラスファイルがうまく解釈できなかったので、今回はGradleを使いました。
build.gradle
apply plugin: 'java' apply plugin: 'application' mainClassName = 'InfinispanTreeExample' repositories { mavenCentral() maven { url 'https://repository.jboss.org/nexus/content/groups/public' } } dependencies { compile 'org.infinispan:infinispan-tree:5.3.0.CR1' }
Tree APIは、InfinispanCoreモジュールではないので、明示的に指定する必要があります。
言語も、Javaにしておきました。…そういえば、InfinispanをJavaから使うのは初めてですね。
Tree APIを使うためには、設定をする必要があります。以下は、「treeCache」と名付けたCacheに対して、設定を行っている例です。
<namedCache name="treeCache"> <invocationBatching enabled="true" /> <transaction transactionManagerLookupClass="org.infinispan.transaction.lookup.GenericTransactionManagerLookup" transactionMode="TRANSACTIONAL" /> </namedCache>
って、Batching APIを有効化しているだけですが。これを有効化しないと、Tree APIは使用することができません。
では、Javaコードの方に移ります。以降のJavaコードでは、以下のimport文が入っているものとします。
import java.io.IOException; import java.io.Serializable; import org.infinispan.Cache; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.tree.Fqn; import org.infinispan.tree.Node; import org.infinispan.tree.TreeCacheFactory; import org.infinispan.tree.TreeCache;
まあ、Serializableはなくてもいいですが…。
Tree APIの使うためには、まずはTreeCacheを作成する必要があります。
EmbeddedCacheManager manager = null; Cache<String, Entry> cache = null; try { manager = new DefaultCacheManager("infinispan.xml"); cache = manager.getCache("treeCache"); TreeCacheFactory treeCacheFactory = new TreeCacheFactory(); TreeCache<String, Entry> treeCache = treeCacheFactory.createTreeCache(cache);
Cacheを作る箇所まではいつも通りですが、その後でTreeCacheFactoryのインスタンスを生成して、そこからTreeCacheクラスを取得します。Cacheの名前を「treeCache」にしたのは、ちょっと紛らわしかったかも…。
TreeCacheには、登録するキーと値を型パラメータとして指定します。今回は、値としてそれ用のクラスを用意しました。
class Entry implements Serializable { public static final long serialVersionUID = 1L; private String name; public Entry(String name) { this.name = name; } @Override public String toString() { return String.format("This is [%s]", name); } }
まあ、大したことないものですが…。
では、まずはFqnを作成し、データを登録してみます。TreeCache#putで、Fqnとキー、そして値を指定します。
Fqn dataGridFqn = Fqn.fromString("/nosql/dataGrid"); treeCache.put(dataGridFqn, "infinispan", new Entry("Infinispan")); treeCache.put(dataGridFqn, "gridGain", new Entry("GridGain"));
Fqnは、Fqnクラスのstaticメソッドから作成します。今回はfromStringメソッドを使用していますが、他にもfromElementsなどがあります。この例では、キー「infinispan」と「gridGain」に対して値を登録しています。
他にも、StringでいきなりFqnを指定してデータを登録することもできます。
treeCache.put("/nosql/dataGrid", "hazelcast", new Entry("Hazelcast"));
登録した情報を、ちょっと取得してみましょう。TreeCacheから取得します。
System.out.printf("get => %s%n", treeCache.get(dataGridFqn, "infinispan")); // V System.out.printf("getData => %s%n", treeCache.getData(dataGridFqn)); // Map<K, V> System.out.printf("getKeys => %s%n", treeCache.getKeys(dataGridFqn)); // Set<K> System.out.printf("getNode => %s%n", treeCache.getNode(dataGridFqn)); // Node<K, V> System.out.printf("getNode#get => %s%n", treeCache.getNode(dataGridFqn).get("infinispan")); // V
結果を、以下に載せます。
get => This is [Infinispan] getData => {infinispan=This is [Infinispan], hazelcast=This is [Hazelcast], gridGain=This is [GridGain]} getKeys => [infinispan, hazelcast, gridGain] getNode => NodeImpl{fqn=/nosql/dataGrid} getNode#get => This is [Infinispan]
コメントにも書いていますが、だいたいこんな対応付けです。
メソッド名 | 戻り値 |
---|---|
get(Fqn, K) | Fqnで指定したNodeから、指定のキーに対応する値を返却 |
getData(Fqn) | Fqnで指定したNodeに登録してあるMapを返却 |
getNode(Fqn) | Fqnで指定した、Node自身を返却 |
getKeys(Fqn) | Fqnで指定したNodeから、登録してあるキーの集合を返却 |
最後の例は、getNode#getなのでNodeを特定した後、Nodeに対して指定のキーに対応する値を取得しています。
今度は、Fqn.fromElementsでFqnを作成してみます。
Fqn documentFqn = Fqn.fromElements("nosql", "documentDatabase"); treeCache.put(documentFqn, "mongoDB", new Entry("MongoDB")); treeCache.put(documentFqn, "couchDB", new Entry("CouchDB"));
TreeCacheからrootのNodeが取得できるので、ここからNodeに対して子を追加していくこともできます。
Node<String, Entry> rootNode = treeCache.getRoot(); Node<String, Entry> columnDatabaseNode = rootNode .addChild(Fqn.fromElements("nosql")) .addChild(Fqn.fromElements("columnDatabase")); columnDatabaseNode.put("cassandra", new Entry("Apache Cassandra")); columnDatabaseNode.put("hbase", new Entry("Apache HBase"));
Node#addChildで指定しているFqnは、相対パス的な扱いになっていますね。
というわけで、Fqn.fromRelativeFqnから特定のFqnからの相対パスでFqnを作成することもできます。
Fqn nosqlFqn = Fqn.fromElements("nosql"); Fqn kvsFqn = Fqn.fromRelativeFqn(nosqlFqn, Fqn.fromElements("kvs")); treeCache.put(kvsFqn, "redis", new Entry("Redis"));
Nodeを削除するには、TreeCache#removeNodeで。
treeCache.removeNode(Fqn.fromString("/nosql/documentDatabase"));
NodeからgetChildしていって、Node#removeChildで子を消すこともできます。
treeCache.getRoot().getChild(Fqn.fromString("nosql")).removeChild(Fqn.fromString("columnDatabase"));
なお、TreeCache#removeNodeではFqnを深い階層していして削除することができますが、Node#removeNodeでは自分の直下のNodeしか削除できないようです。つまり、こういう指定は無効です。
treeCache.getRoot().removeChild(Fqn.fromElements("nosql", "columnDatabase"));
特定のNodeに登録されているキーを指定して、対応するキーと値のペアを削除できます。
treeCache.getNode(Fqn.fromElements("nosql", "dataGrid")).remove("hazelcast");
Node#clearDateで、Nodeが持つ全データを削除。
treeCache.getNode(Fqn.fromElements("nosql", "dataGrid")).clearData();
TreeCache#moveで、Nodeの付け替えも可能です。
Node<String, Entry> graphDatabaseNode = treeCache.getRoot().addChild(Fqn.fromElements("other", "graphDatabase")); graphDatabaseNode.put("neo4j", new Entry("Neo4j")); treeCache.move(Fqn.fromString("/other/graphDatabase"), Fqn.fromString("/nosql"));
ここでは、「/other/graphDatabase」をFqnとして登録したパスを、「/nosql/graphDatabase」に移動しています。注意点は、第2引数は「新しい親となるFqnを指定する」ため、Linuxのmvコマンドみたいな感覚で使えるわけではありません。つまり、「リネームはできない」ということです。
Node#getChildrenで、Nodeの直接の子NodeのListを取得することもできます。
treeCache.getRoot().getChild(Fqn.fromElements("nosql")).getChildren();
Nodeは先ほど見たようにtoStringするとFqnが見えますが、そのNodeから下のFqnまで文字列化して表現するわけではありません。あくまで、自分自身までです。
ロックについて
Treeを操作している時には、ロックが発生するケースがあるようです。
Locking In Tree API
https://docs.jboss.org/author/display/ISPN/Tree+API+Module#TreeAPIModule-LockingInTreeAPI
- Nodeに対してputすると、そのNodeにWriteロックを取得する。この場合、親NodeにWriteロックはかからないし、子Nodeにもロックはかからない
- Nodeを追加したり削除した場合、親NodeにWriteロックはかからない
- Nodeを移動する時は、そのNodeと子Nodeをロックする。移動先の新しいNodeの位置とその子Node(移動対象Nodeの子だと思います)にもロックがかかる
Nodeの移動の時だけは、本人以外にも親と子のNodeにロックがかかるということなのでしょうか…。
ところで、最初はsbt+Scalaでやろうとしていたのですが、こんなエラーが出てしまって進めなかったので、ここは諦めました…。
[error] error while loading Fqn, class file '/xxxxx/.ivy2/cache/org.infinispan/infinispan-tree/bundles/infinispan-tree-5.3.0.CR1.jar(org/infinispan/tree/Fqn.class)' is broken [error] (class java.lang.RuntimeException/bad constant pool index: 0 at pos: 8277)
ちょっと前のバージョンのInfinispanにしても、ダメでした。
Tree APIって、ここ1〜2年くらい更新されていないみたいですし、Infinispan Moduleの中ではけっこう微妙な存在なのでしょうか?
最後、書いたコード全体です。
import java.io.IOException; import java.io.Serializable; import org.infinispan.Cache; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.tree.Fqn; import org.infinispan.tree.Node; import org.infinispan.tree.TreeCacheFactory; import org.infinispan.tree.TreeCache; public class InfinispanTreeExample { public static void main(String[] args) { EmbeddedCacheManager manager = null; Cache<String, Entry> cache = null; try { manager = new DefaultCacheManager("infinispan.xml"); cache = manager.getCache("treeCache"); TreeCacheFactory treeCacheFactory = new TreeCacheFactory(); TreeCache<String, Entry> treeCache = treeCacheFactory.createTreeCache(cache); Fqn dataGridFqn = Fqn.fromString("/nosql/dataGrid"); treeCache.put(dataGridFqn, "infinispan", new Entry("Infinispan")); treeCache.put(dataGridFqn, "gridGain", new Entry("GridGain")); treeCache.put("/nosql/dataGrid", "hazelcast", new Entry("Hazelcast")); System.out.printf("get => %s%n", treeCache.get(dataGridFqn, "infinispan")); // V System.out.printf("getData => %s%n", treeCache.getData(dataGridFqn)); // Map<K, V> System.out.printf("getKeys => %s%n", treeCache.getKeys(dataGridFqn)); // Set<K> System.out.printf("getNode => %s%n", treeCache.getNode(dataGridFqn)); // Node<K, V> System.out.printf("getNode#get => %s%n", treeCache.getNode(dataGridFqn).get("infinispan")); // V System.out.printf("getRoot => %s%n", treeCache.getRoot()); Fqn documentFqn = Fqn.fromElements("nosql", "documentDatabase"); treeCache.put(documentFqn, "mongoDB", new Entry("MongoDB")); treeCache.put(documentFqn, "couchDB", new Entry("CouchDB")); Node<String, Entry> rootNode = treeCache.getRoot(); Node<String, Entry> columnDatabaseNode = rootNode .addChild(Fqn.fromElements("nosql")) .addChild(Fqn.fromElements("columnDatabase")); columnDatabaseNode.put("cassandra", new Entry("Apache Cassandra")); columnDatabaseNode.put("hbase", new Entry("Apache HBase")); Fqn nosqlFqn = Fqn.fromElements("nosql"); Fqn kvsFqn = Fqn.fromRelativeFqn(nosqlFqn, Fqn.fromElements("kvs")); treeCache.put(kvsFqn, "redis", new Entry("Redis")); System.out.printf("get => %s%n", treeCache.get(kvsFqn, "redis")); System.out.printf("getChildren => %s%n", treeCache.getRoot().getChild(Fqn.fromElements("nosql")).getChildren()); treeCache.removeNode(Fqn.fromString("/nosql/documentDatabase")); //treeCache.removeNode(Fqn.fromElements("nosql", "documentDatabase")); treeCache.getRoot().getChild(Fqn.fromString("nosql")).removeChild(Fqn.fromString("columnDatabase")); //treeCache.getRoot().removeChild(Fqn.fromElements("nosql", "columnDatabase")); treeCache.getNode(Fqn.fromElements("nosql", "dataGrid")).remove("hazelcast"); System.out.printf("getData => %s%n", treeCache.getData(dataGridFqn)); // Map<K, V> treeCache.getNode(Fqn.fromElements("nosql", "dataGrid")).clearData(); System.out.printf("getData => %s%n", treeCache.getData(dataGridFqn)); // Map<K, V> Node<String, Entry> graphDatabaseNode = treeCache.getRoot().addChild(Fqn.fromElements("other", "graphDatabase")); graphDatabaseNode.put("neo4j", new Entry("Neo4j")); treeCache.move(Fqn.fromString("/other/graphDatabase"), Fqn.fromString("/nosql")); System.out.printf("getChildren => %s%n", treeCache.getRoot().getChild(Fqn.fromElements("nosql")).getChildren()); } catch (IOException e) { e.printStackTrace(); } finally { if (cache != null) { cache.stop(); } if (manager != null) { manager.stop(); } } } } class Entry implements Serializable { public static final long serialVersionUID = 1L; private String name; public Entry(String name) { this.name = name; } @Override public String toString() { return String.format("This is [%s]", name); } }