WildFly31でTsurugiにアクセスする方法。
WildFlyのアプリケーションからTsurugiにアクセスする場合、TsurugiのクライアントライブラリーであるIceaxe(Tsubakuro)をwarファイルの中に入れておけば実行できる。
しかし、Iceaxeは言わばJDBCと同じ位置づけであり、通常、JDBCのjarファイルはwarファイルの中には含めず、APサーバーの静的ライブラリーとして配置しておくものらしい。
したがって、IceaxeやTsubakuroも同様にWildFlyの静的ライブラリーとして置いておくのが良さそう。
(特にTsubakuroはIPC接続する際にJNIを使うので、再デプロイと相性が悪い)
※JDBCだとJNDIを使ってコネクションプールからコネクションを取得したりするが、IceaxeやTsubakuroでそれを作るのは難しそうなので、その方法はひとまず採らない。
シンプルに、warファイルの中にIceaxe(Tsubakuro)を含める構成の例。
plugins { id 'java' id 'war' } java { toolchain { languageVersion = JavaLanguageVersion.of(11) } } tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } repositories { mavenCentral() } war { archiveFileName = 'example-wildfly.war' } dependencies { compileOnly 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0' implementation 'com.tsurugidb.iceaxe:iceaxe-core:1.2.0' }
import java.util.Set; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application;
@ApplicationPath("/api") public class IceaxeApplication extends Application { @Override public Set<Class<?>> getClasses() { return Set.of(IceaxeResource.class); } }
import java.io.IOException; import com.tsurugidb.iceaxe.TsurugiConnector; import com.tsurugidb.iceaxe.transaction.option.TgTxOption; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType;
@Path("/iceaxe") public class IceaxeResource { private final TsurugiConnector connector = TsurugiConnector.of("tcp://localhost:12345"); @GET @Produces(MediaType.TEXT_PLAIN) public String select() throws IOException, InterruptedException { try (var session = connector.createSession(); // var ps = session.createQuery("select * from test")) { var tm = session.createTransactionManager(TgTxOption.ofRTX()); var list = tm.executeAndGetList(ps); return list.toString(); } } }
$ ./gradlew war $ ls build/libs/ example-wildfly.war
$ cd wildfly-31.0.1.Final/bin $ ./jboss-cli.sh --connect [standalone@localhost:9990 /] deploy --force=true /path/to/example-wildfly.war
ブラウザーから「http://localhost:8080/example-wildfly/api/iceaxe
」にアクセスする。
Tsubakuroでは、初回のセッション作成時にJMXにオブジェクト(SessionInfo)を登録している。
warファイルにTsubakuroが入っている状態で再デプロイすると、実行時に「SessionInfoが既に登録済みである」というエラー
メッセージが出る。
JMX経由でSessionInfoを参照しないなら、ひとまず無視しておけばよい。
(このエラーが出てもTsurugiへのアクセスは可能)
19:50:34,111 ERROR [stderr] (default task-1) javax.management.InstanceAlreadyExistsException: com.tsurugidb.tsubakuro.diagnostic.common:type=SessionInfo 19:50:34,113 ERROR [stderr] (default task-1) at java.management/com.sun.jmx.mbeanserver.Repository.addMBean(Repository.java:436) 19:50:34,116 ERROR [stderr] (default task-1) at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerWithRepository(DefaultMBeanServerInterceptor.java:1855) 19:50:34,119 ERROR [stderr] (default task-1) at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerDynamicMBean(DefaultMBeanServerInterceptor.java:955) 19:50:34,119 ERROR [stderr] (default task-1) at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerObject(DefaultMBeanServerInterceptor.java:890) 19:50:34,120 ERROR [stderr] (default task-1) at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:320) 19:50:34,120 ERROR [stderr] (default task-1) at java.management/com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:522) 19:50:34,121 ERROR [stderr] (default task-1) at org.jboss.as.jmx@23.0.3.Final//org.jboss.as.jmx.PluggableMBeanServerImpl$TcclMBeanServer.registerMBean(PluggableMBeanServerImpl.java:1512) 19:50:34,124 ERROR [stderr] (default task-1) at org.jboss.as.jmx@23.0.3.Final//org.jboss.as.jmx.PluggableMBeanServerImpl.registerMBean(PluggableMBeanServerImpl.java:861) 19:50:34,125 ERROR [stderr] (default task-1) at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.diagnostic.JMXAgent.setUp(JMXAgent.java:28) 19:50:34,125 ERROR [stderr] (default task-1) at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.diagnostic.JMXAgent.sessionInfo(JMXAgent.java:42) 19:50:34,126 ERROR [stderr] (default task-1) at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.common.SessionBuilder.(SessionBuilder.java:45) 19:50:34,127 ERROR [stderr] (default task-1) at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.common.SessionBuilder.connect(SessionBuilder.java:77) 19:50:34,127 ERROR [stderr] (default task-1) at deployment.example-wildfly.war//com.tsurugidb.iceaxe.TsurugiConnector.createLowSession(TsurugiConnector.java:244) 19:50:34,128 ERROR [stderr] (default task-1) at deployment.example-wildfly.war//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:224) 19:50:34,128 ERROR [stderr] (default task-1) at deployment.example-wildfly.war//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:188) 19:50:34,129 ERROR [stderr] (default task-1) at deployment.example-wildfly.war//com.example.wildfly31.IceaxeResource.select(IceaxeResource.java:21)
warファイルにTsubakuroが入っている状態で、TsurugiへのアクセスにIPC接続を使用した後に再デプロイして再びIPC接続しようとすると、「libtsubakuro.soが既に別のクラスローダーでロードされている」というエラーが出て、続行不可能になる。
(TCP接続しか使っていない場合はこのエラーは発生しない)
19:58:04,529 ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /example-wildfly/api/iceaxe: java.lang.UnsatisfiedLinkError: Native Library /usr/lib/tsurugi-1.0.0-BETA3/lib/libtsubakuro.so already loaded in another classloader at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2476) at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2705) at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2635) at java.base/java.lang.Runtime.load0(Runtime.java:768) at java.base/java.lang.System.load(System.java:1850) at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.channel.ipc.NativeLibrary.(NativeLibrary.java:84) at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.channel.ipc.connection.IpcConnectorImpl. (IpcConnectorImpl.java:42) at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.channel.ipc.connection.IpcConnectorFactory.tryCreate(IpcConnectorFactory.java:44) at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.channel.common.connection.ConnectorHelper.create(ConnectorHelper.java:20) at deployment.example-wildfly.war//com.tsurugidb.tsubakuro.channel.common.connection.Connector.create(Connector.java:35) at deployment.example-wildfly.war//com.tsurugidb.iceaxe.TsurugiConnector.of(TsurugiConnector.java:104) at deployment.example-wildfly.war//com.tsurugidb.iceaxe.TsurugiConnector.of(TsurugiConnector.java:40) at deployment.example-wildfly.war//com.example.wildfly31.IceaxeResource. (IceaxeResource.java:17)
TsurugiへのアクセスにIPC接続を使用する場合、TsubakuroではIPC接続にJNIを使っているため、native
libraryをロードしている。
ta_morimotoさんのJNIのライブラリ導入時にハマった事象によると、Javaのnative
libraryはひとつのクラスローダーにしかロードできないらしい。
ひとまずの回避方法としては、再デプロイせず、WildFly自身を再起動すること。
根本的な解決策としては、Tsubakuroをwarファイルに含めず、WildFlyの静的ライブラリーとすること。
Iceaxeおよびその依存ライブラリー(主にTsubakuro)をWildFlyの静的ライブラリーに入れる例。
まず、Iceaxeと依存ライブラリーのjarファイルを集める。
dependenciesのiceaxe-coreはprovidedCompileにする。
そして、static_libsというディレクトリーにjarファイルを出力するGradleタスク(copyStaticLibs)を用意する。
〜 dependencies { compileOnly 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0' providedCompile 'com.tsurugidb.iceaxe:iceaxe-core:1.2.0' } task copyStaticLibs(type: Copy) { from configurations.providedCompile into "${buildDir}/static_libs" }
$ ./gradlew copyStaticLibs $ ls build/static_libs/ iceaxe-core-1.2.0.jar tsubakuro-common-1.2.0.jar jackson-annotations-2.13.3.jar tsubakuro-connector-1.2.0.jar jackson-core-2.13.3.jar tsubakuro-explain-1.2.0.jar jackson-databind-2.13.3.jar tsubakuro-ipc-1.2.0.jar java-jwt-3.19.2.jar tsubakuro-proto-1.2.0.jar jsr305-3.0.2.jar tsubakuro-session-1.2.0.jar protobuf-java-3.17.3.jar tsubakuro-stream-1.2.0.jar slf4j-api-1.7.36.jar
次に、出力されたjarファイルを、WildFlyのモジュール置き場にコピーする。
モジュールの置き場所は、wildfly-31.0.1.Final/modules/system/layers/base。
この下に、パッケージ名を逆順にしたディレクトリーを作ってそこに置くことになっているようだ。
つまり、Iceaxeはcom/tsurugidb/iceaxe、Tsubakuroはcom/tsurugidb/tsubakuroに置くのが本来の姿だと思われる。
しかし個別にコピーするのは面倒なので、今回はexample-wildflyというひとつのディレクトリー内に全部置くことにする。
jarファイルを置く場所は、具体的にはさらにmainというディレクトリーの下になる。
$ mkdir -p /path/to/wildfly-31.0.1.Final/modules/system/layers/base/example-wildfly/main $ cp build/static_libs/* /path/to/wildfly-31.0.1.Final/modules/system/layers/base/example-wildfly/main/
WildFlyに静的ライブラリーとして認識させるためには、ただjarファイルを置けばいいだけではなく、module.xmlというファイルも必要になる。
<?xml version="1.0" encoding="UTF-8"?> <module name="example-wildfly" xmlns="urn:jboss:module:1.9"> <resources> <resource-root path="iceaxe-core-1.2.0.jar"/> <resource-root path="tsubakuro-common-1.2.0.jar"/> <resource-root path="tsubakuro-connector-1.2.0.jar"/> <resource-root path="tsubakuro-explain-1.2.0.jar"/> <resource-root path="tsubakuro-ipc-1.2.0.jar"/> <resource-root path="tsubakuro-proto-1.2.0.jar"/> <resource-root path="tsubakuro-session-1.2.0.jar"/> <resource-root path="tsubakuro-stream-1.2.0.jar"/> <resource-root path="protobuf-java-3.17.3.jar"/> <resource-root path="slf4j-api-1.7.36.jar"/> </resources> <dependencies> <module name="java.management"/> <module name="java.logging"/> </dependencies> </module>
moduleタグのname属性は、ディレクトリーに付けた名前と同じものにする必要がある。
(xxx/yyyというディレクトリーの場合、「name="xxx.yyy"
」)
そして、使用するjarファイルをresourcesに列挙していく。
jackson系はWildFlyが自動的にインポートしているので、明示的なインポートは不要。(module.xmlに書かなくてよい)
java-jwtやjsr305も無くても大丈夫そう。
最後に、warファイル内の指定で、上記で用意したexample-wildflyモジュールを依存関係に含める必要がある。
jboss-deployment-structure.xmlというファイルを作り、その中にモジュール名を記載する。
<?xml version="1.0" encoding="UTF-8"?> <jboss-deployment-structure> <deployment> <dependencies> <module name="example-wildfly"/> </dependencies> </deployment> </jboss-deployment-structure>
これで、warファイルを作り直してWildFlyを起動し直せば、静的ライブラリーを利用して実行されるようになる。
MalformedObjectNameExceptionはJREに含まれているのだが、どういうわけか静的ライブラリーからデフォルトでは参照できないようだ。
module.xmlのdependenciesに特に何も指定していないと実行時に以下のようなエラーが出る。
20:41:18,918 ERROR [org.jboss.resteasy.core.providerfactory.DefaultExceptionMapper] (default task-1) RESTEASY002375: Error processing request GET /example-wildfly/api/iceaxe - com.example.wildfly31.IceaxeResource.select: java.lang.NoClassDefFoundError: javax/management/MalformedObjectNameException at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.common.SessionBuilder.(SessionBuilder.java:45) at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.common.SessionBuilder.connect(SessionBuilder.java:77) at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createLowSession(TsurugiConnector.java:244) at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:224) at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:188) at deployment.example-wildfly.war//com.example.wildfly31.IceaxeResource.select(IceaxeResource.java:22) 〜 Caused by: java.lang.ClassNotFoundException: javax.management.MalformedObjectNameException from [Module "example-wildfly" version 1.7.36 from local module loader @36546a22 (finder: local module finder @285c08c8 (roots: 〜))] at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:200) at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410) at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398) at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)
module.xmlのdependenciesにjava.managementを指定すると通る。
(javax.management.MalformedObjectNameExceptionクラスは、JREのjava.managementモジュールに含まれている)
同様に、java.util.logging.Loggerが見つからないというエラーが出た場合、module.xmlのdependenciesにjava.loggingを指定すると通る。
(java.util.logging.Loggerクラスは、JREのjava.loggingモジュールに含まれている)
20:53:12,168 ERROR [org.jboss.resteasy.core.providerfactory.DefaultExceptionMapper] (default task-1) RESTEASY002375: Error processing request GET /example-wildfly/api/iceaxe - com.example.wildfly31.IceaxeResource.select: java.lang.NoClassDefFoundError: java/util/logging/Logger at example-wildfly@1.7.36//com.google.protobuf.CodedOutputStream.(CodedOutputStream.java:60) at example-wildfly@1.7.36//com.tsurugidb.endpoint.proto.EndpointRequest$Request.getSerializedSize(EndpointRequest.java:313) at example-wildfly@1.7.36//com.google.protobuf.AbstractMessageLite.writeDelimitedTo(AbstractMessageLite.java:89) at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.channel.common.connection.wire.impl.WireImpl.toDelimitedByteArray(WireImpl.java:238) at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.channel.common.connection.wire.impl.WireImpl.handshake(WireImpl.java:208) at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.channel.stream.connection.StreamConnectorImpl.connect(StreamConnectorImpl.java:46) at example-wildfly@1.7.36//com.tsurugidb.tsubakuro.common.SessionBuilder.createAsync(SessionBuilder.java:155) at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createLowSession(TsurugiConnector.java:250) at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:224) at example-wildfly@1.7.36//com.tsurugidb.iceaxe.TsurugiConnector.createSession(TsurugiConnector.java:188) at deployment.example-wildfly.war//com.example.wildfly31.IceaxeResource.select(IceaxeResource.java:22)