WildFly Swarmには、Project Stagesという概念があります。
一方で、CDIに関する機能強化をしてくれるフレームワーク、Apache DeltaSpikeにも
ProjectStageという概念があります。
WildFly Swarmには、Apache DeltaSpikeを使ったサンプルなどもあるのですが、
特にProject Stagesについては考慮されていません。
thorntail-examples/jaxrs/jaxrs-cdi-deltaspike at master · thorntail/thorntail-examples · GitHub
thorntail-examples/jaxrs/jaxrs-deltaspike-data at master · thorntail/thorntail-examples · GitHub
よって、例えば「実行時にdevelopment」にしたい、と考えるとシステムプロパティにこういう指定を
することになります。
-Dswarm.project.stage=development -Dorg.apache.deltaspike.ProjectStage=Development
また、Apache DeltaSpike側のProjectStageは、型として決まっているので以下の
いずれから選択する(もしくは実装して追加する)必要があります。
- UnitTest
- Development
- SystemTest
- IntegrationTest
- Staging
- Production
なにも指定しないと、「Production」が選択されます。
以上です。
ですが
これで終わってしまってはあんまりなので、少しハック的に回避策を考えてみます。
できれば、WildFly SwarmとApache DeltaSpikeのProject Stageを合わせたいですよね?
追記)
@emaggameさんに別解をいただいたので、最後に追記しています。
というわけで、お題を
- システムプロパティ「swarm.project.stage」で設定したProject Stageを、Apache DeltaSpikeにも反映する
- 「swarm.project.stage」で指定する値は、Apache DeltaSpike側で取りうるProjectStageに限るものとする(大文字・小文字区別なし)
- Apache DeltaSpike側で理解できないProject Stageを指定した場合は、例外を投げる
という感じでチャレンジしたいと思います。
どうするか
Apache DeltaSpike側には、ProjectStageProducerという現在のProjectStageを管理している
クラスがあります。
このクラスのsetProjectStageメソッドを使うと、現在のProjectStageを変更することができます。
ただ、本来はUnitTestで使うためみたいですけどね…。
Apache DeltaSpikeのProjectStageはいつ決定するのか
かなり速いタイミングで決まります。
Apache DeltaSpikeが実装しているCDI ExtensionのstaticイニシャライザーでConfigurationを
扱う時に、一緒に決まってしまいます。
この時に、ProjectStageを意識した設定を見ようとするので、ProjectStageProducerの初期化が
完了する、と。
なので、CDI Extensionが呼び出される時(しかもBeforeBeanDiscoveryでは遅い)までに
先にProjectStageProducerに指定のProjectStageを設定してあげる必要があります。
となると、ServletContextListenerではすでに遅いみたいで、それよりも前に起動するなにかを
使う必要があります。
CDI Extensionを使う?
で、今回はCDI Extensionを使いました。WARのclassesにあるクラスなら、とりあえずlibの方より
先に読まれそう?な感じなので。
危うい感覚ですよね。こうなると素直に両方システムプロパティ指定した方がよい気も。
準備
pom.xmlは、こんな感じで作りました。Apache DeltaSpike側にもbomが提供されているので、
そちらを使用しています。
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.littlewings</groupId> <artifactId>project-stages-with-deltaspike</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <scala.major.version>2.12</scala.major.version> <scala.version>${scala.major.version}.1</scala.version> <scala.maven.plugin.version>3.2.2</scala.maven.plugin.version> <failOnMissingWebXml>false</failOnMissingWebXml> <wildfly.swarm.version>2016.12.1</wildfly.swarm.version> <apache.deltaspike.version>1.7.2</apache.deltaspike.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>bom</artifactId> <version>${wildfly.swarm.version}</version> <scope>import</scope> <type>pom</type> </dependency> <dependency> <groupId>org.apache.deltaspike.distribution</groupId> <artifactId>distributions-bom</artifactId> <version>${apache.deltaspike.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>jaxrs</artifactId> </dependency> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>cdi</artifactId> </dependency> <dependency> <groupId>org.apache.deltaspike.core</groupId> <artifactId>deltaspike-core-api</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.deltaspike.core</groupId> <artifactId>deltaspike-core-impl</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <version>${scala.maven.plugin.version}</version> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <scalaVersion>${scala.version}</scalaVersion> <args> <arg>-Xlint</arg> <arg>-unchecked</arg> <arg>-deprecation</arg> <arg>-feature</arg> </args> <recompileMode>incremental</recompileMode> </configuration> </plugin> <plugin> <groupId>org.wildfly.swarm</groupId> <artifactId>wildfly-swarm-plugin</artifactId> <version>${wildfly.swarm.version}</version> <executions> <execution> <goals> <goal>package</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
サンプルアプリケーション
続いて、動作確認用のサンプルコードを実装します。
JAX-RSリソースクラス。このクラスに突っ込むDecoratorを、Apache DeltaSpikeのProjectStageで
切り替えるようにしましょう。
src/main/scala/org/littlewings/wildflyswarm/deltaspike/MessageResource.scala
package org.littlewings.wildflyswarm.deltaspike import javax.enterprise.context.ApplicationScoped import javax.inject.Inject import javax.ws.rs.core.MediaType import javax.ws.rs.{GET, Path, Produces, QueryParam} @Path("message") @ApplicationScoped class MessageResource { @Inject private[deltaspike] var decorator: Decorator = _ @GET @Produces(Array(MediaType.TEXT_PLAIN)) def message(@QueryParam("p") p: String): String = decorator.decorate(p) }
で、Decorator側。名前が紛らわしいですが、CDIのDecoratorとは関係ありません。
Development以外ではメッセージを「***」で装飾し、Developmentの時は「Development」という
prefixと「|」で装飾します。
src/main/scala/org/littlewings/wildflyswarm/deltaspike/Decorator.scala
package org.littlewings.wildflyswarm.deltaspike import javax.enterprise.context.ApplicationScoped import org.apache.deltaspike.core.api.exclude.Exclude import org.apache.deltaspike.core.api.projectstage.ProjectStage trait Decorator { def decorate(message: String): String } @ApplicationScoped @Exclude(ifProjectStage = Array(classOf[ProjectStage.Development])) class AsteriskDecorator extends Decorator { override def decorate(message: String) = s"***${message}***" } @ApplicationScoped @Exclude(exceptIfProjectStage = Array(classOf[ProjectStage.Development])) class PipeDecorator extends Decorator { override def decorate(message: String) = s"[Development] |${message}|" }
CDI Extension
で、Apache DeltaSpikeのProject Stageを制御するのに、実装したCDI Extensionはこちら。
src/main/scala/org/littlewings/wildflyswarm/deltaspike/DeltaSpikeProjectStageInitializeExtension.scala
package org.littlewings.wildflyswarm.deltaspike import javax.enterprise.inject.spi.Extension import org.apache.deltaspike.core.api.projectstage.ProjectStage import org.apache.deltaspike.core.util.ProjectStageProducer import org.jboss.logging.Logger object DeltaSpikeProjectStageInitializeExtension { private[deltaspike] val logger = Logger.getLogger(classOf[DeltaSpikeProjectStageInitializeExtension]) { val swarmProjectStage = System.getProperty("swarm.project.stage", "production") val filteredProjectStages = ProjectStage .values() .filter(_.toString.equalsIgnoreCase(swarmProjectStage)) if (filteredProjectStages.nonEmpty) { val selectedDeltaSpikeProjectStage = filteredProjectStages(0) logger.infof("set DeltaSpike ProjectStage: [%s]", selectedDeltaSpikeProjectStage) ProjectStageProducer.setProjectStage(selectedDeltaSpikeProjectStage) } else { throw new IllegalStateException(s"Unknown ProjectStage[$swarmProjectStage]") } } } class DeltaSpikeProjectStageInitializeExtension extends Extension { DeltaSpikeProjectStageInitializeExtension }
意味的にはApache DeltaSpikeと同様、staticイニシャライザーで設定しているに等しいです。
内容的には、WildFly Swarm側で指定したProject Stageのシステムプロパティの値を、大文字・小文字の
区別なしでApache DeltaSpikeのenumから引き当てている感じの実装です。
ServiceLoaderの設定も、合わせて。
src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
org.littlewings.wildflyswarm.deltaspike.DeltaSpikeProjectStageInitializeExtension
また、CDI Extensionを作成すると、beans.xmlを作成する必要があるみたいです。空でもかまいませんが、
必須のようで。
src/main/resources/META-INF/beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="annotated"> </beans>
CDI Extensionの実装がクラスパスにあってbeans.xmlがない状態だと、@Injectが動作しなくなる
みたいで、かなりハマりました。
[THORN-655] Using a custom CDI extension breaks injection - JBoss Issue Tracker
これ自体は、CDIの仕様みたいです。
今回はCDI Extensionで実装したわけですが、要するにApache DeltaSpikeのCDI Extensionが
動作する前にこんな感じのコードを差し込めればApache DeltaSpikeの初期化時を含めて
Project Stageをコントロールできますよ、と。
project-stages.yml
あとは、WildFly Swarm側もProject Stageの切り替えがわかるように、なにかProject Stageで
設定を変えてみましょう。
今回は、リッスンポートを変えました。
src/main/resources/project-stages.yml
swarm: http: port: 8080 --- project: stage: development swarm: http: port: 18080
動作確認
それでは、動作確認してみます。
パッケージングして
$ mvn package
「development」なProject Stageで起動。
$ java -Dswarm.project.stage=development -jar target/project-stages-with-deltaspike-0.0.1-SNAPSHOT-swarm.jar
通常ではこんな感じに出力されるところが
2017-01-01 19:02:20,379 INFO [org.apache.deltaspike.core.util.ProjectStageProducer] (MSC service thread 1-7) Computed the following DeltaSpike ProjectStage: Production
(今回は)代わりに自前で仕込んだログが出るようになれば成功です。
2017-01-01 19:04:25,495 INFO [org.littlewings.wildflyswarm.deltaspike.DeltaSpikeProjectStageInitializeExtension] (MSC service thread 1-3) set DeltaSpike ProjectStage: [Development]
確認。
$ curl http://localhost:18080/message?p=HelloWorld [Development] |HelloWorld|
リッスンポートも変わっています。
続いて、Project Stage指定なしで起動。
$ java -jar target/project-stages-with-deltaspike-0.0.1-SNAPSHOT-swarm.jar
デフォルトを「production」にしているので、Apache DeltaSpike側は「Production」になります。
2017-01-01 19:05:30,408 INFO [org.littlewings.wildflyswarm.deltaspike.DeltaSpikeProjectStageInitializeExtension] (MSC service thread 1-3) set DeltaSpike ProjectStage: [Production]
確認。
$ curl http://localhost:8080/message?p=HelloWorld ***HelloWorld***
リッスンポート、文字列の装飾が切り替わったことが確認できました、と。
まとめ
だいぶやっつけな方法ですが、WildFly SwarmとApache DeltaSpikeのProject Stageを
合わせてみました。
本来は両方のシステムプロパティをそれぞれ指定するのがベターかとは思いますが、
Apache DeltaSpikeの起動の前に割り込めればなんとかなりそうなので、そこが把握できた
ところまでで良しとしましょう。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/wildfly-swarm-scala-examples/tree/master/project-stages-with-deltaspike
別解
@emaggameさんから、教えていただいた方法です。
@kazuhira_r あと、昨日の話題の続きとして、project-stages.yml に DeltaSpike のステージ書いちゃうの、けっこういいかなって気がしてますURL
2017-01-02 22:47:42 via TweetDeck to @emaggame
project-stages.ymlに書いた項目は、システムプロパティにも反映されるようなので、これを
利用してWildFly SwarmのProject Stageに合わせたApache DeltaSpikeの設定を書いておくのも
よいかもしれません。
org.apache.deltaspike.ProjectStage: Production --- project: stage: it org.apache.deltaspike.ProjectStage: IntegrationTest
成功法としては、これが素直かもですねぇ…。