blog1.mammb.com に引き続き、 blog1.mammb.com をKotlin化してみます。
設定ファイルなどは同じものを利用するので必要に応じてこちらを参照してください。
Resources.kt
CDIの Produces
を提供する Resources です。
package example; import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Produces; import javax.faces.context.FacesContext; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; public open class Resources { [Produces] [PersistenceContext] open var em : EntityManager? = null [Produces] [RequestScoped] public open fun produceFacesContext() : FacesContext? { return FacesContext.getCurrentInstance() } }
Kotlin では C# 風にアノテーションを[]で定義します。 Kotlin の将来のリリースでは Java のように @ を利用するように変更される予定となっています。
Kotlin では null セーフな変数を扱うため、null となる可能性があるものは型定義の後ろに ? を付ける必要があります。
Member.kt
Member エンティティです。name と email フィールドと id フィールドを定義します。
package example.model import java.io.Serializable import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.Id import javax.validation.constraints.NotNull import javax.validation.constraints.Pattern import javax.validation.constraints.Size [Entity] public class Member( [Id] [GeneratedValue] var id : Long = 0, [NotNull] [Size(min = 1, max = 25)] [Pattern(regexp = "[^0-9]*", message = "Must not contain numbers")] var name : String = "", [NotNull] [Pattern(regexp = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$", message = "not email")] var email : String = "" ) : Serializable { }
getter/setter はクラスへのコンパイル時に生成されるため、フィールド定義だけしておけば大丈夫です。
なお、フィールド値は JPA で扱うため var で宣言します。
MemberRepository.kt
リポジトリです。Member を id で取得するメソッドと、email で取得するメソッド。一覧を取得するメソッドを定義します。
package example.data import example.model.Member import javax.enterprise.context.ApplicationScoped import javax.inject.Inject import javax.persistence.EntityManager import javax.persistence.criteria.Expression [ApplicationScoped] public open class MemberRepository { [Inject] open var em : EntityManager? = null public open fun findById(id : Long) : Member = em!!.find(javaClass<Member>(), id) public open fun findByEmail(email : String):Member { val cb = em!!.getCriteriaBuilder() val criteria = cb.createQuery(javaClass<Member>()) val member = criteria.from(javaClass<Member>()) val emailExp : Expression<String> = member.get("email") criteria.select(member).where(cb.equal(emailExp, email)) return em!!.createQuery(criteria).getSingleResult() } public open fun findAllOrderedByName():List<Member> { val cb = em!!.getCriteriaBuilder() val criteria = cb.createQuery(javaClass<Member>()) val member = criteria.from(javaClass<Member>()) val nameExp : Expression<String> = member.get("name") criteria.select(member).orderBy(cb.asc(nameExp)) return em!!.createQuery(criteria).getResultList() } }
ApplicationScoped
アノテーションにてCDI管理のスコープを指定しています。
クラスは open
を付けています。open 指定が無い場合、final となり CDI がプロキシを生成できなくなるためです。
Java で cb.createQuery(Member.class)
このようなクラス指定は cb.createQuery(javaClass<Member>())
このように書きます。
MemberRegistration.kt Member の登録用のステートレスセッションビーンです。
package example.service import example.model.Member import javax.ejb.Stateless import javax.enterprise.event.Event import javax.inject.Inject import javax.persistence.EntityManager [Stateless] public open class MemberRegistration { [Inject] var em : EntityManager? = null [Inject] var memberEventSrc :Event<Member>? = null open fun register(member:Member?) { em!!.persist(member) memberEventSrc!!.fire(member) } }
こちらも open 指定します。
MemberListProducer.kt
Member の一覧を供給する CDI イネーブルドビーンです。
package example.data import example.data.MemberRepository import example.model.Member import java.util.ArrayList import javax.annotation.PostConstruct import javax.enterprise.context.RequestScoped import javax.enterprise.event.Observes import javax.enterprise.event.Reception import javax.enterprise.inject.Produces import javax.inject.Inject import javax.inject.Named [RequestScoped] public open class MemberListProducer { [Inject] open var memberRepository : MemberRepository? = null var memberList : List<Member> = ArrayList<Member>() [Produces] [Named] open fun getMembers():List<Member> = memberList public open fun onMemberListChanged([Observes(notifyObserver = Reception.IF_EXISTS)] member :Member) { retrieveAllMembersOrderedByName(); } [PostConstruct] public open fun retrieveAllMembersOrderedByName() { memberList = memberRepository!!.findAllOrderedByName(); } }
MemberController.kt
jSF のバッキングビーンにです。
package example.controller import example.model.Member import example.service.MemberRegistration import javax.annotation.PostConstruct import javax.enterprise.inject.Model import javax.enterprise.inject.Produces import javax.faces.application.FacesMessage import javax.faces.context.FacesContext import javax.inject.Inject import javax.inject.Named [Model] public open class MemberController { [Inject] open var facesContext : FacesContext? = null [Inject] open var memberRegistration : MemberRegistration? = null [Produces] [Named] open var newMember : Member? = null [PostConstruct] public open fun initNewMember() { newMember = Member() } public open fun register() { try { memberRegistration!!.register(newMember) facesContext!!.addMessage(null, FacesMessage(FacesMessage.SEVERITY_INFO, "Registered!", "Registration successful")) initNewMember() } catch (e : Exception) { val errorMessage = "Registration failed. See server log for more information" val m = FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, "Registration unsuccessful") facesContext!!.addMessage(null, m) } } }
build.gradle
kotlin-gradle-plugin を使います。
buildscript { repositories { mavenCentral() jcenter() } dependencies { classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.11.91.4' classpath 'com.bmuschko:gradle-cargo-plugin:2.1' } } apply plugin: 'java' apply plugin: 'kotlin' apply plugin: 'war' apply plugin: 'idea' apply plugin: 'com.bmuschko.cargo' sourceCompatibility = 1.8 targetCompatibility = 1.8 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' repositories { mavenCentral() } dependencies { compile 'org.jetbrains.kotlin:kotlin-stdlib:0.11.91.4' providedCompile 'org.jboss.spec:jboss-javaee-all-7.0:1.0.2.Final' testCompile 'junit:junit:4.11' } cargo { containerId = 'wildfly8x' deployable { file = file(war.archivePath) } local { installer { installUrl = 'http://download.jboss.org/wildfly/8.2.0.Final/wildfly-8.2.0.Final.zip' downloadDir = new File(buildDir, "download") extractDir = file("wildfly") } containerProperties { property 'cargo.jboss.configuration', 'standalone-full' } } }
実行
./gradlew war cargoRunLocal -i
起動したら以下をブラウザで開きます。
あまり Kotlin っぽいコードは出てきませんでしたが、簡単に動きます。