package org.superbiz
import jakarta.ejb.{LocalBean, Lock, LockType, Singleton}
object GreetingService {
val DefaultName = "Scala"
}
@LocalBean
@Singleton
@Lock(LockType.READ)
class GreetingService {
def hi(name: String = GreetingService.DefaultName) = {
if (name != null) {
"hi " + name
} else {
"hi " + GreetingService.DefaultName
}
}
}
Basic Scala EJB
This example shows that an EJB does not have to be written in Java. The
GreetingService singleton bean is written in Scala, compiled with Maven and
tested with ScalaTest against the embedded OpenEJB container, which injects the
bean into the Scala test class just as it would into a Java one.
The bean
GreetingService is a plain Scala class carrying the usual Jakarta EE
annotations: @Singleton makes it a singleton session bean, @LocalBean exposes
a no-interface view, and @Lock(LockType.READ) lets concurrent callers invoke it
without waiting on each other.
The companion object holds the default name. The hi method uses a Scala
default parameter value, so it can be called with or without an argument. A
null name falls back to the default as well.
An empty src/main/resources/META-INF/beans.xml is also part of the module:
<beans/>
Building Scala sources with Maven
The pom.xml sets the source directories to src/main/scala and
src/test/scala and uses the scala-maven-plugin to compile them. Its
compile and testCompile goals are bound to the build, and the Scala version
comes from the scala.full-version property (2.13.15):
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<scala.version>2.13</scala.version>
<scala.full-version>${scala.version}.15</scala.full-version>
</properties>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<!-- compile scala sources -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>4.9.7</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<scalaVersion>${scala.full-version}</scalaVersion>
</configuration>
</plugin>
...
</plugins>
</build>
The tests are ScalaTest suites rather than JUnit tests, so Surefire is skipped
and the scalatest-maven-plugin runs them in the test phase instead. It
writes its reports to target/surefire-reports:
<!-- disable surefire because we use scalatest -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.3</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!-- enable scalatest as a replacement for surefire -->
<plugin>
<groupId>org.scalatest</groupId>
<artifactId>scalatest-maven-plugin</artifactId>
<version>2.2.0</version>
<configuration>
<forkMode>never</forkMode>
<reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
<junitxml>.</junitxml>
<filereports>TestSuite.txt</filereports>
</configuration>
<executions>
<execution>
<id>test</id>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
The Jakarta EE API comes from the tomee-webprofile-api BOM (scope
provided), and the tomee-webprofile BOM (scope test) supplies the
embedded container for the tests. The module also depends on scala-library
and on scalatest_2.13 for testing.
Testing
GreetingServiceTest is a ScalaTest AnyFunSuite. It mixes in
BeforeAndAfterAll and BeforeAndAfterEach to manage the embedded container:
-
beforeAllstarts the container withEJBContainer.createEJBContainer(), which deploysGreetingService. -
beforeEachbinds the test instance under the nameinject, so the container fills the@Injectfieldservicebefore each test. -
afterAllcloses the container.
package org.superbiz
import jakarta.ejb.embeddable.EJBContainer
import jakarta.inject.Inject
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
import org.scalatest.funsuite.AnyFunSuite
class GreetingServiceTest extends AnyFunSuite with BeforeAndAfterAll with BeforeAndAfterEach {
@Inject
private var service: GreetingService = null
test("Hi scala") {
val message = service hi "scala"
assert(message == "hi scala")
}
test("Default Hi") {
val message = service.hi()
assert(message == "hi Scala")
}
test("Hi null") {
val message = service.hi(null)
assert(message == "hi Scala") // when called with null we use default name
}
private var container: EJBContainer = null
override def beforeAll() {
container = EJBContainer.createEJBContainer()
}
override def beforeEach() {
container.getContext().bind("inject", this)
}
override def afterAll() {
if (container != null) {
container.close()
}
}
}
The three tests check each branch of hi:
-
Hi scalacalls the method with an explicit name, using Scala infix notation (service hi "scala"), and expectshi scala. -
Default Hicallsservice.hi()without an argument, so the default parameter value applies, and expectshi Scala. -
Hi nullpassesnulland expects the default name,hi Scala.
Running
Run the tests with Maven:
mvn clean test
The example also has a build.sbt file for sbt users. The Maven build
described above is the one covered in this README.