Java EE 6 Web Profile. On the cloud. Easy.
That’s what I always used to think. Well, not anymore, now. Let me share my experience.
Some weeks ago, I started thinking about porting a legacy spring+hibernate+tomcat application to a new platform :
SAP NetWeaver Cloud. I known what you geeks out there are thinking : this post is getting worse. It starts with Java EE, not exactly a geeky thing, and now enters SAP, not exactly a geek company… Please, give me another ten minutes !
The configuration of the spring layer of my legacy application was xml-based (it was written before annotations came in the game). I was dreaded by the prospect of diving into – my own – xml horror again.
Then came this tweet :
Welcome @sap to #JavaEE6 party, NetWeaver Cloud is now Java EE 6 Web Profile Certified: bit.ly/Wf8VNS
— GlassFish (@glassfish) November 16, 2012
and, some days later, this documentation. And I tried it out. And it worked. And I changed my mind about Java EE. There is a blog post by ‘Bill the Plumber’ that precisely describe what are my thoughts after this experience.
So much with the bla bla bla. Let’s start coding! If you are in a hurry, clone the complete application from https://github.com/cthiebaud/adventscloud
Before cutting and pasting like mad, let’s describe briefly what the code below is about. We will construct and deploy in the cloud (free) a tiny web application that:
1. logs in the user (sorry, you’ll need a SAP Community Network account, do not worry it’s free),
2. upon login, say ‘hello’ to the rest of the world on behalf of the user,
3. upon successive logins, instead of saying ‘hello’ again and again, merely store in a database how many ‘hellos’ were said, and
4. that’s it.
To achieve that, we’ll need one java interface, three java classes, one java server page, and a final touch of persistence.xml (for the database configuration) and web.xml (for security constraint wizardry).
For the sake of brevity, packages, imports, getters and setters are omitted from the code below. But, as just said, the complete source is available @ github
Write one Hello.java POJO (complete class here):
public class Hello { private Long id; private String username; private Integer counter; private Timestamp when; // ... getters and setters ... }
Fairly obvious : for each username
, this POJO will store in a counter
how many time the user hit the index.jsp of our app, and when
was the last time.
Annotate this Hello.java POJO with JPA annotations (complete class here):
@Entity @Table(name="T_HELLO") @NamedQueries( { @NamedQuery(name = "allHellos", query = "select h from Hello h"), @NamedQuery(name = "helloFromUsername", query = "select h from Hello h where h.username = :username") }) public class Hello { @Id @GeneratedValue private Long id; @Column(name="A_USER", unique=true, nullable=false) private String username; @Column(name="A_COUNTER", nullable=false) private Integer counter; @Version @Column(name="A_TIMESTAMP", nullable=false) private Timestamp when; public Hello() { this.counter = 1; } // ... getters and setters ... }
Write one HelloDao.java interface that accesses the POJO (complete interface here)
@Local public interface HelloDao { List<hello> getAll(); Hello fromUsername(String username); Hello save(Hello hello); }
Write one HelloBean.java, annotated with EJB annotations, that implements the HelloDao interface (complete class here):
@Stateless public class HelloBean implements HelloDao { @PersistenceContext private EntityManager em; @Override public List<hello> getAll() { @SuppressWarnings("unchecked") List<hello> hellos = (List<hello>)em.createNamedQuery("allHellos").getResultList(); Collections.sort(hellos, new Comparator<hello>() { @Override public int compare(Hello o1, Hello o2) { return o2.getWhen().compareTo(o1.getWhen()); // latest first } }); return hellos; } @Override public Hello fromUsername(String username) { Query query = em.createNamedQuery("helloFromUsername"); query.setParameter("username", username); Hello hello = null; try { hello = (Hello)query.getSingleResult(); } catch (NoResultException ignored) { } return hello; } @TransactionAttribute @Override public Hello save(Hello hello) { hello = em.merge(hello); return hello; } }
Write one HelloFilter.java Java Servlet 3 Filter, that 1. bumps the counter upon login, and 2. exposes the HelloBean instance to the – coming soon – Java Server page (complete class here):
@WebFilter("/index.jsp") public final class HelloFilter implements Filter { @EJB HelloDao helloDao; @Override public void init(FilterConfig fConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { request.setAttribute("helloDao", helloDao); String username = ((HttpServletRequest)request).getRemoteUser(); Hello hello = helloDao.fromUsername(username); if (hello == null) { hello = new Hello(); hello.setUsername(username); } else { hello.setCounter(hello.getCounter()+1); } hello = helloDao.save(hello); chain.doFilter(request, response); } finally { request.removeAttribute("helloDao"); } } @Override public void destroy() { } }
NB. in bold above, the magic plumbing done by our Java EE 6 Web Profile container between all these classes :
@PersistenceContext EntityManager em; @EJB HelloDao helloDao; @WebFilter('/index.jsp')
Write one persistence.xml JPA configuration (complete xml here)
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" 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"> <persistence-unit name="adventscloud-persist" transaction-type="JTA"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <jta-data-source>jdbc/DefaultDB</jta-data-source> <class>net.aequologica.adventscloud.Hello</class> <properties> <property name="eclipselink.ddl-generation" value="create-or-extend-tables" /> </properties> </persistence-unit> </persistence>
Write one web.xml to trigger login when user access index.jsp and to inform the web application of the presence of a container managed database (complete xml here):
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <login-config> <auth-method>FORM</auth-method> </login-config> <security-constraint> <web-resource-collection> <web-resource-name>Protected Area</web-resource-name> <url-pattern>/index.jsp</url-pattern> </web-resource-collection> <auth-constraint> <role-name>Everyone</role-name> </auth-constraint> </security-constraint> <security-role> <description>All SAP NetWeaver Cloud users</description> <role-name>Everyone</role-name> </security-role> <resource-ref> <res-ref-name>jdbc/DefaultDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> </resource-ref> </web-app>
Again, NB. above in bold the further magic plumbing:
<jta-data-source>jdbc/DefaultDB</jta-data-source> <class>net.aequologica.adventscloud.Hello</class> <res-ref-name>jdbc/DefaultDB</res-ref-name>
Finally, write one index.jsp java server page that displays all ‘hellos’ (complete page here):
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt_rt" %> <!DOCTYPE html> <html> <head> <title>adventscloud</title> </head> <body> <table> <tbody> <c:forEach var="hello" items="${requestScope.helloDao.all}" varStatus="status"> <tr> <td><fmt:formatDate type="both" value="${hello.when}" /></td> <td>${hello.counter}</td> <td>hello<c:if test = "${hello.counter > 1}">(s)</c:if> from</td> <td>${hello.username}</td> </tr> </c:forEach> </tbody> </table> </body> </html>
We’re nearly done … two last things: 1. classpath hell, and 2. JPA 2.0 metamodel generation with javac -processor.
1. Classpath hell.
In order to compile all this stuff, you’ll need somehow to have the following jars on the classpath :
group | artifact | version javax.persistence : persistence-api : >= 1.0 javax.ejb : ejb-api : >= 3.0 javax.servlet : javax.servlet-api : >= 3.0 javax.servlet : jstl : >= 1.2
Of course the easiest way is to declare these dependencies in a maven project like mine, but if you are maven-averse, I took the time to hyperlink to the jars above to maven central to spare you some time chasing the jars.
2. JPA 2.0 metamodel generation with javac -processor.
Finally , the build must be able to generate JPA 2.0 metamodel classes. Here I choose the eclipselink generator, as eventually eclipselink is the JPA implementation used by SAP NetWeaver Cloud. I believe that any JPA 2.0 compliant generator should do the job as well. Here also, maven do help, with the following xml fragment in the <build><plugins> section of the pom.xml:
<plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <version>2.1.0</version> <executions> <execution> <id>process</id> <goals> <goal>process</goal> </goals> <phase>generate-sources</phase> <configuration> <processors> <processor>org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProcessor</processor> </processors> </configuration> </execution> </executions> </plugin>
Maven-averse can refer to eclipselink documentation about JPA 2.0 matamodel generation for alternative means to generate JPA 2.0 metamodel classes.
At this point, we have a adventscloud.war file that should run verbatim on any Java EE 6 Web Profile compliant container.
Among them is SAP NetWeaver Cloud. You can have a look at the application running at my lifelong-free trial instance of SAP NetWeaver Cloud. It is a bit richer than the code shown in this blog post, with a spark of twitter bootstrap bells and whistles. Follow the github ribbon in the app if you are interested by sparks.
If you’d like to get your lifelong-free trial instance of SAP NetWeaver Cloud, follow the initial steps described here.
Reference: Java EE 6 Web Profile. On the cloud. Easy. from our JCG partner Christophe Thiebaud at the Java Advent Calendar blog.