Introduction
Below is a tutorial to document what I did to create a fully working Scala project utilizing the best or the most popular frameworks or tools in enterprise Java such as Spring, Hibernate/JPA, Wicket, Maven and Intellij IDEA. The purpose is to help other Java programmers get started quickly with a fully working enterprise Scala project.
Setting up the IDE
First, download the Intellij IDEA 9.0.x (community edition) as it is the best scala IDE right now. Then choose File | Settings | Plugins, choose the Available tab to install the Scala plugin.
Creating the Maven project
In IDEA, choose File | New Project and choose the Maven module to create a Maven project. Then modify pom.xml as shown below. This will add all the dependencies you need and set up the compilation of Scala classes in the build processes:
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.foo</groupId>
<artifactId>myapp</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>myapp Java EE 6 Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<spring.version>3.0.3.RELEASE</spring.version>
</properties>
<repositories>
<repository>
<id>java.net2</id>
<name>Repository hosting the jee6 artifacts</name>
<url>http://download.java.net/maven/2</url>
</repository>
<repository>
<id>scala-tools.org</id>
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</repository>
<repository>
<id>wpt-release</id>
<url>http://wicketpagetest.sourceforge.net/m2-repo/releases</url>
</repository>
<repository>
<id>wpt-snapshot</id>
<url>http://wicketpagetest.sourceforge.net/m2-repo/snapshots</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>scala-tools.org</id>
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</pluginRepository>
</pluginRepositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.2.120</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.4.0.GA</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
<dependency>
<groupId>com.ttdev</groupId>
<artifactId>wpt-core</artifactId>
<version>1.5.2-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.ttdev</groupId>
<artifactId>wpt-runtime-spring</artifactId>
<version>1.5.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.8.0.RC3</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket</artifactId>
<version>1.4.9</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.4.2</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
</plugin>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<version>2.9.1</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1-beta-1</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<finalName>myapp</finalName>
</build>
</project>
Wait for a while and IDEA will prompt you on whether to import the changes into the project. Say yes.
Setting up web.xml
Next, modify main/webapp/WEB-INF/web.xml as below. This sets up the Wicket filter, the Spring filter to open the JPA entity manager and the Spring listener to initialize Spring itself.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<filter>
<filter-name>f2</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter>
<filter-name>f1</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>com.foo.myapp.MyApp</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>f1</filter-name>
<url-pattern>/app/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>f2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
Setting up the Spring beans for database access and transaction
To define those Spring beans, create main/resources/beans.xml with the content below. This defines the entity manager factory, the transaction manager and etc.
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:component-scan base-package="com.foo.myapp"/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath:/META-INF/my-persistence.xml"/>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf"/>
</bean>
<tx:annotation-driven />
</beans>
The entity manager factory will read the database configuration from the my-persistence.xml file. So, create it in main/resources/META-INF with the content below. Here, you'll access an H2 database named myapp in your home directory.
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="myapp" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
<property name="hibernate.connection.url" value="jdbc:h2:tcp://localhost/~/myapp"/>
<property name="hibernate.connection.username" value="sa"/>
<property name="hibernate.connection.password" value=""/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
</properties>
</persistence-unit>
</persistence>
Creating the Wicket page
Here, you'll create a Wicket page to display some products loaded from the database. So, create the main/scala folder, right click it and choose New | Scala Class. Name the class as MyPage and put it into the com.foo.myapp package. The class is shown below:
package com.foo.myapp
import org.apache.wicket.markup.html._
import basic.Label
import list.{ListItem, ListView}
import org.apache.wicket.spring.injection.annot.SpringBean
import org.apache.wicket.model.CompoundPropertyModel
class MyPage extends WebPage {
@SpringBean
var ps: ProductService = _
val productListView = new ListView[Product]("productListView", ps.getAll) {
def populateItem(item: ListItem[Product]) = {
item.setModel(new CompoundPropertyModel[Product](item.getDefaultModelObject))
item.add(new Label("name"))
item.add(new Label("price"))
}
}
add(productListView)
}
Note that it is using a ProductService object to load the products. You'll create it later. In addition, note that the field is assigned to an underscore (_), which tells the Scala compile to NOT initialize, but leave it at the default state (null in this case). This is required for the injection to work. If you assign it to null explicitly, you
will overwrite the Spring bean as the injection will occur before the constructor of MyPage is executed.
Now, create the MyPage.html file in src/main/resources/com/foo/myapp:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<table border="1">
<tr wicket:id="productListView">
<td wicket:id="name"></td>
<td wicket:id="price"></td>
</tr>
</table>
</html>
Creating the ProductService
Right click the com.foo.myapp package in the src/main/scala folder and choose New | Scala Class, then choose to create a trait named ProductService:
package com.foo.myapp
import java.util.List
trait ProductService {
def getAll: List[Product]
}
This is the interface. To create the implementation, create a DefaultProductService Scala class in the same package:
package com.foo.myapp
import javax.persistence.{PersistenceContext, EntityManager}
import org.springframework.stereotype.{Service}
import org.springframework.transaction.annotation.Transactional
import org.springframework.beans.factory.annotation.Autowired
@Service
@Transactional
class DefaultProductService extends ProductService {
@Autowired
var products: Products = _
def getAll = products.getAll
}
Note that it is using a DAO named of type Products to do the work.
Creating the Products DAO
To create the DAO, create a trait named Products in the same package:
package com.foo.myapp
import java.util.List
trait Products {
def getAll: List[Product]
}
Then create the implementation Scala class DefaultProducts in the same package:
package com.foo.myapp
import javax.persistence.{PersistenceContext, EntityManager}
import org.springframework.stereotype.Repository
import java.util.List
@Repository
class DefaultProducts extends Products {
@PersistenceContext
var em: EntityManager = _
def getAll = {
em.createQuery("select p from Product p").getResultList.asInstanceOf[List[Product]]
}
}
Creating the entity class
Create the Product class and map it to the database:
package com.foo.myapp
import javax.persistence.{GeneratedValue, Id, Entity}
@Entity
class Product {
@Id
@GeneratedValue
var id: Long = _
var name: String = _
var price: Double = _
}
Creating the Wicket application class
Finally, create MyApp Scala class in the same package:
package com.foo.myapp
import org.apache.wicket.protocol.http.WebApplication
import com.ttdev.wicketpagetest.MockableSpringBeanInjector
class MyApp extends WebApplication {
def getHomePage = classOf[MyPage]
override def init = {
MockableSpringBeanInjector.installInjector(this)
}
}
Here you specify MyPage as the home page and install an injector that can inject Spring beans (as well as mock objects, even though you won't use this capability here).
Running the application
As the Intellij IDEA community edition doesn't include the integration with app servers, you'll embed Jetty to run your application. This is easy. Just create a Scala class ManualTest in the com.foo.myapp package in the test/scala folder (NOT the main/scala folder). The content is below:
package com.foo.myapp
import com.ttdev.wicketpagetest.{WebAppJettyConfiguration, WicketAppJettyLauncher}
object ManualTest {
def main(args: Array[String]) {
val l = new WicketAppJettyLauncher
l.startAppInJetty(new WebAppJettyConfiguration)
}
}
To run the application, you need to have the H2 database server running first. So, go to http://www.h2database.com to download and unpack it. Then change into h2/bin and run h2.bat (or h2.sh on Linux).
While the ManualTest class is the active editor in IDEA, choose Run | Run in IDEA. It will run ManualTest which will launch Jetty to run your application.
To test it, try accessing http://localhost:8888/app in a browser. It should display nothing as there is no product in the database. However, it will create the table required.
To add some products, go to http://localhost:8082 to access the H2 web client. Enter jdbc:h2:tcp://localhost/~/myapp as the JDBC URL. Click Connect. Then issue the SQL statement:
SELECT * FROM PRODUCT
to select the product. Nothing should be there. That's fine. In the result display, click the plus sign to create a product record. Feel free to add more.
Finally, reload http://localhost:8888/app and you should see the products displayed in the Wicket page.
Updated: You can download the project folder.
You can safe yourself some typing with the Wicket/Scala maven archetype available on http://jweekend.com/dev/LegUp. You'll still have to add Spring and Hibernate.
ReplyDeleteYeah, I knew a similar archetype like the one provided by wicket-scala project in wicketstuff. However, I did it from scratch so that I knew how it all worked together.
ReplyDeleteHi Kent! Thank's for your article. It's a great timesaver for me.
ReplyDeleteP.S. We are on the same wave - for the next application i'll choose the same techologies + idea.
Not trying to push my opinion on you here, but have you looked at liftweb (www.liftweb.net)?
ReplyDeleteIt has a very similar template system to wicket for rendering your web pages, and to me it feels a little neater to work in Scala-land and has a number of other features that I find nice (things like comet and jquery integration).
You can still use JPA & Hibernate as your persistence layer; the only real thing that gets left out is Spring. I've found, however, that in Scala I didn't really need Spring anymore anyhow as a set of well thought out traits and implicits can replace (and in a safer way) most of the nice characteristics you get out of DI and AOP anyhow.
Sorry about sticking my nose in here; just that the more and more I work with liftweb, the more I love it so I thought I would drop its name in case you hadn't tried it yet. :)
Of course, there are a number of reasons that you'd stick with Wicket and Spring. If you're working on a hybrid java/scala project and care about testing, then Spring is an invaluable thing to have around.
Good post. You beat me to it.
ReplyDeleteCan I suggest you putting up a tar file or a git/svn repo with the code as it should be with the functionality level you have at the end of the post? I have been asked for that several times, and I would be happy to send people your way instead of repeating your work.
Hi Ben,
ReplyDeleteThanks for your comment! I haven't checked out Lift in any depth yet, but I prefer component-based frameworks for good re-usability.
Regarding AOP, an aspect in AOP can easily, say, attach a transactional advice to multiple methods in multiple classes, regardless of the method names. How can a Scala trait do that?
Hi Geir,
ReplyDeleteThanks for your suggestion. I've made the project folder available as zip file download.
Hi
ReplyDeleteI am not sure I like the heavy use of Annotations in Scala, since they are only a crutch Java needs for lack of other syntax richness.
I am also not sure Scala benefits from the use of Spring.
[...] http://agileskills2.org/blog/2010/06/19/getting-started-with-scala-spring-hibernate-wicket/ LikeBe the first to like this post. [...]
ReplyDeleteHey, Two suggestions,
ReplyDeleteFirstly you may want to use the maven jetty plugin:
org.mortbay.jetty
maven-jetty-plugin
6.1.16
true
5
/
If you do this you don't need to create the test class
Jetty can be launch/debugged with mvn jetty:run
This also allows you to build from the command line (mvn clean install) without blowing up on the (now non-existent) test class.
Secondly in beans.xml
I had to change the bean name from emf to 'entityManagerFactory' in order to get the sample app to work.
That said, great article thank you so much for putting it together.