Scala, JSF 2, and NetBeans

I am working on a web site that will help students practice their Scala programming skills. As I labored along, writing my JSF app code, I thought “this is silly—why not practice Scala at the same time?” But I like JSF and wasn't ready to jump to Lift or Vaadin.

With Eclipse, this isn't all that hard. Install the Java plugin. Make a dynamic web project in the usual way, using the Java EE perspective. Then, switch to the Scala perspective, right-click on the project, and, if all planets are aligned correctly, you will get a menu item “Add Scala nature”. (If they are not, see here for a manual approach.) Add your managed beans as Scala classes. Finally, switch back to the Java EE perspective, select the project properties, and add the Scala library JAR as a Java EE module dependency.

But I like NetBeans and wasn't ready to switch to Eclipse. (Unfortunately, JSF 2 support in Eclipse is pretty minimal, the Glassfish integration is a bit flaky, and the Scala plugin has very little usable code completion.)

NetBeans doesn't let me add a “Scala nature” to a web project. If I add Scala files to the project, I can edit them with the Scala editor, but they just get copied to the WAR file, without any compilation. I had one look at the Ant scripts for a Scala and a web project and decided that I wasn't going to figure out how to merge them.

This blog shows how you can use Maven to make a mixed Scala/Java project in NetBeans. So I gathered up JSF and Scala pom.xml files from here and here, cut out the considerable crud from the JSF POM file that was probably meant for supporting Tomcat, and merged the results to the best of my ability—see below.

You use the usual Maven directory structure, but with a src/main/scala directory instead of src/main/java:

This is the first example from Core JSF, but with a Scala managed bean:

@Named("user") @SessionScoped
class UserBean extends Serializable {
   @BeanProperty var name : String = ""
   @BeanProperty var password : String = ""
   def login = if (name != "") "welcome" else null
}

Now isn't that nicer than

@Named("user") @SessionScoped
public class UserBean implements Serializable {
    private String name = "";
    private String password = "";

    public String getName() { return name; }   
    public void setName(String newValue) { name = newValue; }

    public String getPassword() { return password; }
    public void setPassword(String newValue) { password = newValue; }   

    public String login() {
        if (!name.equals("")) { 
            return "welcome" 
        } else { 
            return null;
        }
    }
}

Here is a zip file of the project. To test that it works, run

mvn package embedded-glassfish:run

Then point your browser to http://localhost:7070/scala-login, and the app should come up. Login with any non-blank user name, and you get this:

(Yes, it truly is JSF—the URL in the browser bar is one step behind...)

Now on to NetBeans. Select File -> New Project -> Maven -> Maven Project with Existing POM.

Just keep going in the wizard, and remember for next time that you could simply select File -> Open Project... instead.

What you get is pretty good—a project that you can build, deploy, and debug inside the IDE. Changes in the JSF pages are hot-deployed, but if you change your source code, you have to select the Debug -> Apply Code Changes menu option.

Also, you don't get autocompletion of CDI beans. Somehow NetBeans doesn't discover them from the deployed code. It does discover the message bundle for code completion, so maybe this is something they could fix in the future.

Here is the POM file:

<?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/maven-v4_0_0.xsd">

   <modelVersion>4.0.0</modelVersion>
   <groupId>com.horstmann.corejsf</groupId>
   <artifactId>scala-login</artifactId>
   <packaging>war</packaging>
   <version>1.0.0-SNAPSHOT</version>
   
   <properties>
      <scala.version>2.8.0</scala.version>
   </properties>
   
   <repositories>
      <repository>
         <id>scala-tools.org</id>
         <name>Scala-Tools Maven2 Repository</name>
         <url>http://scala-tools.org/repo-releases</url>
      </repository>
      <repository>
         <id>maven2-repository.dev.java.net</id>
         <name>Java.net Repository for Maven</name>
         <url>http://download.java.net/maven/2</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>
      <pluginRepository>
         <id>glassfish</id>
         <name>GlassFish Maven 2 Repository</name>
         <url>http://download.java.net/maven/glassfish</url>
      </pluginRepository>
   </pluginRepositories>
   
   <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>6.0</version>
      </dependency>
   </dependencies>

   <build>
      <sourceDirectory>src/main/scala</sourceDirectory>
      <finalName>${artifactId}</finalName>
      <plugins>
         <plugin>
            <groupId>org.scala-tools</groupId>
            <artifactId>maven-scala-plugin</artifactId>
            <executions>
               <execution>
                  <goals>
                     <goal>compile</goal>
                  </goals>
               </execution>
            </executions>
            <configuration>
               <scalaVersion>${scala.version}</scalaVersion>
            </configuration>
         </plugin>
         
         <!-- Compiler plugin enforces Java 1.6 compatibility -->
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
               <source>1.6</source>
               <target>1.6</target>
            </configuration>
         </plugin>

         <!-- Configure the Embedded GlassFish Maven plugin -->
         <plugin>
            <groupId>org.glassfish</groupId>
            <artifactId>maven-embedded-glassfish-plugin</artifactId>
            <version>3.0</version>
            <configuration>
               <app>${project.build.directory}/${build.finalName}.war</app>
               <port>7070</port>
               <containerType>web</containerType>
            </configuration>
         </plugin>
      </plugins>
   </build>

   <reporting>
      <plugins>
         <plugin>
            <groupId>org.scala-tools</groupId>
            <artifactId>maven-scala-plugin</artifactId>
            <configuration>
               <scalaVersion>${scala.version}</scalaVersion>
            </configuration>
         </plugin>
      </plugins>
   </reporting>

</project>