1. Contents

Table of Contents

2. Introductions

Pragmatach - A pragmatic MVC framework for B2C Developers

Pragmatach is a simple web framework aimed at providing server-site support for B2C web developers using technologies which are familiar to developers developing real-world sites.

Features in Pragmatach include:

License

Pragmatach is licensed under the GPL.

Requirements

Pragmatach requires:

Quickstart

To create the skeleton of a test project use the pragmatach maven archetype.


mvn archetype:generate                                  \
  -DarchetypeGroupId=com.khubla.pragmatach       				\
  -DarchetypeArtifactId=pragmatach-archetype     				\
  -DarchetypeVersion=1.39                        				\
  -DgroupId=com.khubla.exampleproject            				\
  -DartifactId=myexampleproject				  				 				\
  -DinteractiveMode=false                        				\
  -DarchetypeRepository=http://www.pragmatach.com/repo/
  

This will generate:

To run the generated project, type:


cd myexampleproject
mvn clean package jetty:run

Then browse to:

http://localhost:8080

If you are using the Tomcat6, Tomcat7, GlassFish, JBoss-AS7 or TomEE maven profiles, your application will be at:

http://localhost:8080/myexampleproject-1.0-SNAPSHOT/

Application Server Support

The pom.xml for Pragmatach applications includes a number of maven profiles for different application servers. These include:

Jetty 8

mvn clean package jetty:run and browse to http://localhost:8080

or

mvn -Pjetty8 clean package jetty:run and browse to http://localhost:8080

Jetty 9

mvn -Pjetty97clean package jetty:run and browse to http://localhost:8080

Note that Jetty9 requires JDK7

Jetty 7

mvn -Pjetty7 clean package jetty:run and browse to http://localhost:8080

Tomcat 6

mvn -Ptomcat6 clean package tomcat:run-war and browse to http://localhost:8080/<application name>

Tomcat 7

mvn -Ptomcat7 clean package tomcat:run-war and browse to http://localhost:8080/<application name>

JBoss 7

mvn -Pjbossas7 clean package jboss-as:run and browse to http://localhost:8080/<application name>

TomEE

mvn -Ptomee clean package tomee:run and browse to http://localhost:8080/<application name>

GlassFish

mvn -Pglassfish embedded-glassfish:run and browse to http://localhost:8080/<application name>

Pragmatach Design Principles

Pragmatach was designed based on a number of core principles:

3. Framework

Configuration

Configuration for the Pragmatach framework is contained in the file

src\main\resources\pragmatach.properties

Basic configuration values include:

pragmatach.cookie.cryptokey The crypto key for AES cookie encryption

pragmatach.adminapp.username The username to login to the Adminstration console

pragmatach.adminapp.password The password to login to the Administration console

pragmatach.applicationuser The HTTP Basic Auth login username

pragmatach.applicationpassword The HTTP Basic Auth login password

pragmatach.applicationrealm The HTTP Basic Auth realm

Plugins also use the configutation file, for example the JCR plugin requires these configuration settings:

jcr.url The URL of the JCR server

jcr.workspace The JCR workspace name

jcr.username The JCR username

jcr.password The JCR password

Static Resources

In order to differentiate static resources from Pragmatach pages, static resources are served from under the context path /public. The filesystem path for static resources is src/main/webapp/public/.

Site Login

Pragmatach includes the ability to require HTTP Basic Auth login to sites. This can be useful for sites that are deployed on internet facing servers, but are not yet in production.

To enable this feature simply put this into pragmatach.properties


pragmatach.applicationuser=user
pragmatach.applicationpassword=password
pragmatach.applicationrealm=MySite

Controller Scope

By default, all Controllers are Request scoped. However, Controllers can be declared as Session Scoped:

@Controller(name="myController", scope = Controller.Scope.session)

Both the name and scope parameters are optional on @Controller. The default value for Name is the class name of the annotated controller and the default value for scope is Controller.Scope.request

Controllers classes extends a template engine controller such as FreemarkerController, VelocityController or ThymeleafController can specify the view template to use with the@View annotatation.

For example a controller that renders the template page.html would use this @View annotation:

@View(view="page.html")

The file system path for view template is

/src/main/webapp

The @View annoation is valid on both classes and members. It is therefore possible to have multiple methods that render different templates, from the same Controller class.

Template Variables

The following variables are available in the templating engines (Velocity and Freemarker)

So, to access the session in FreeMarker:

${session}

or to access the current Controller in Velocity

$controller

To access the Session-scoped controller declared with @Controller(name="userController", scope = Controller.Scope.session) in FreeMarker:

${userController}

There is a controller API url_for that can be used to build URLs relative to the current URL. For example:

<a href="${controller.url_for('pragmatach/admin')}">Admin</a>

Request Routing

Pragmatch uses REST-like route mapping. Request routes are declared via the @Route annotation, in conjunction with the signature of the annotated method

Examples

In this example, the method render() will be called for the context path /.


@Route(uri = "/")
public Response render() throws PragmatachException

In this example the method myMethod(int) will be called with the value 2 for the context path /mycontroller/2.


@Route(uri = "/mycontroller")
public Response render(int myValue) throws PragmatachException

In order to bind parameters, both the @Route annotation and the @RouteParameter must be used. The @Route annotation is used to specify parameters passed in the URI, and the @RouteParametere is used to map those parameters to Java method parameters. For example:


@Route(uri = "/user/@id")
public Response render(@RouteParameter(name="id") String userId) throws PragmatachException

Regular Expressions can be specified for any parameter using @RouteParameter, for example:


@Route(uri = "/user/@id")
public Response render(@RouteParameter(name="id", regex="\b\d+\b") int userId) throws PragmatachException

Wildcard routes


@Route(uri = "/user/*")
public Response render(String[] parameters) throws PragmatachException

Route Cache

The process of mapping arbitrary incoming request URLs to routes is computationally expensive. Therefore, Pragmatach maintains an internal LRU cache of route mapping results. The route cache statics are in the Admin Console and additionally the route cache can be cleared via the Admin Console. The default maximum size of the cache is 100 routes, however that maximum can be specified in the pragmatach configuration using this flag

pragmatach.routecache.size

Cache Control

Cache control headers are specfied via the @CacheControl annotation on controllers. For example:


@Controller(name = "IndexController")
@View(view = "index.ftl")
@CacheControl(maxAge = 10)
public class IndexController extends FreemarkerController

Logging

Pragmatach uses [LogBack] for logging. The default settings are:

Cookie Encryption

Pragmatach supports cookie encryption with AES. Cookie encryption is supported by setting the crypto key as the pragmatach.properties file value pragmatach.cookie.cryptokey.

Pragmatach custom HTTP headers

Pragmatach responses return the custom header X-Pragmatach-RenderTime containing the request render time, in milliseconds.

i8n support

I8N support is provided by Pragmatach plugins which expose implementation of I8NProvider to Pragmatach. The variable i8n is then available in template engines to use for internationalization. The plugin pragmatach-i8n provides simple internationalization of strings and dates.

An example i8n file, used by pragmatach-i8n for the locale en_us would have the name i8n.properties_en_us. The contents of the example are:

 admin=Administration home=Home rssviewer=RSS Viewer 

An example of using this localization data from a Freemarker template is:

<a class="brand" href="#">${i8n.text("en_us","rssviewer")}</a>

which generates this HTML:

<a class="brand" href="#">RSS Viewer</a>

JCR Support

Support for the JCR is provided via the pragmatach-jcr plugin. This plugin will accept a HTTP GET with an encoded JCR Node path and will return JSON containing the names-value pairs for every JCR Property in that Node.

The below code binds the route /example/@propertyName and returns JSON for the properties of the node path passed as propertyName.


@Controller(name = "ExampleJCRController")
public class ExampleJCRController extends JCRController {
   @Route(uri = "example/@propertyName")
   public Response render(@RouteParameter(name = "propertyName") String propertyName) throws PragmatachException {
      return super.render(propertyName);
   }
}

An example link to this route is.

<a href="${session.getServletContext().getContextPath()}/example/hello%2Fworld">Example Route Binding 1</a>

This example link is querying the JCR for the properties of the Node hello/world

4. Plugins

Plugins

The Pragmatch framework does not include useful functionality by itself; all functionality is provided via plugins. The current plugins are:

To use a plugin in your project, simply include the plugin module in your project as a maven depenendency. For example, to include support for GETing and POSTing JSON:


<dependency>
	<groupId>com.khubla.pragmatach</groupId>
	<artifactId>pragmatach-json</artifactId>
	<version>1.28-RELEASE</version>
	<type>jar</type>
	<scope>compile</scope>
</dependency>

The plugin will automatically be detected at runtime and will be availalbe to your applications

5. Persistence

DAOs

Relational persistence in Pragmatach is accomplished via plugin DAOs, which support JPA 1.0. There are four persistence plugins currently:

The DAOs provided by the relational plugins (hibernate, openjpa, ebean) all implement the interface

com.khubla.pragmatach.framework.dao.DAO<T, I extends Serializable>

The full interface is:


public interface DAO<T, I extends Serializable> {
   long count() throws PragmatachException;

   void delete(T t) throws PragmatachException;

   void deletebyId(I i) throws PragmatachException;

   T findById(I i) throws PragmatachException;

   List<T> getAll() throws PragmatachException;

   List<T> getAll(int start, int count) throws PragmatachException;

   Class<I> getIdentifierClazz();

   Pager<T> getPager(int batchsize) throws PragmatachException;

   Class<T> getTypeClazz();

   void reloadConfig();

   void save(T t) throws PragmatachException;

   void update(T t) throws PragmatachException;
}

Because MongoDB only uses String keys, the MongoDB interface is essentially the, with the exception that it specifies String keys.

class MongoDBDAO<T> extends DAO<T, String>

JPA annotations are used to build ORM mappings. For example:


import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class ExamplePOJO {

   @Id
   @GeneratedValue
   private Long id;
   private String name;
   
   public Long getId() {
      return id;
   }

   public String getName() {
      return name;
   }

   public void setId(Long id) {
      this.id = id;
   }

   public void setName(String name) {
      this.name = name;
   }
}

A typical example of persisting a class using a Pragmatach DAO, in this case with Hibernate is:


DAO<ExamplePOJO, Long> dao = new HibernateDAO<ExamplePOJO, Long>(ExamplePOJO.class, Long.class);
final ExamplePOJO examplePOJO1 = new ExamplePOJO();
examplePOJO1.setName("abc123");r(34);
dao.save(examplePOJO1);
    

Simple CRUD testing of DAOs is supported by the class AbstractDAOTest. An example of using this class is given below:


@Test(enabled = true)
public class TestMyExamplePOJO extends AbstractDAOTest<MyExamplePOJO, Long> {
   @Override
   public DAO<MyExamplePOJO, Long> getDAO() {
      return MyExamplePOJO.dao;
   }
 
   @Override
   public Long getId(MyExamplePOJO myExamplePOJO) {
      return myExamplePOJO.getId();
   }
 
   @Override
   public MyExamplePOJO getInstance() {
      return new MyExamplePOJO();
   }
}
    

This code will create an instance, save it, retrieve it and perform a number of other simple DAO operations to verify that simple CRUD operations on the DAO work properly.

EBean

To use EBean, include the pragmatach-EBean module in your maven dependencies.

Configuration for EBean is provided by these properties in pragmatach.properties

An example configuration for EBean using manual configuration, in this case using HSQL would look like


ebean.driver=org.hsqldb.jdbcDriver
ebean.username=sa
ebean.password=
ebean.url=jdbc:hsqldb:mem:testdb
ebean.autocreate=true

A similar configuration, this time using JNDI could be


ebean.datasource=java:/comp/env/jdbc/MyDB
ebean.autocreate=true

To create a DAO for an object which has been annotated with JPA 1.0 Annotations, simply declare an EBean DAO. For example:


EBeanDAO<ExamplePOJO, Long> dao = new EBeanDAO<ExamplePOJO, Long>(ExamplePOJO.class, Long.class);

Hibernate

To use Hibernate, include the pragmatach-hibernate module in your maven dependencies.

Configuration for Hibernate is provided by these properties in pragmatach.properties

An example configuration for Hibernate, in this case using HSQL and manual data source config would look like


hibernate.driver=org.hsqldb.jdbcDriver
hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.connection.url=jdbc:hsqldb:mem:testdb
hibernate.connection.username=sa
hibernate.connection.password=
hibernate.hbm2ddl.auto=create

An example which uses a JNDI data source would look like:


hibernate.connection.datasource=java:/comp/env/jdbc/MyDB
hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.hbm2ddl.auto=create

To create a DAO for an object which has been annotated with JPA 1.0 Annotations, simply declare an Hibernate DAO. For example:


HibernateDAO<ExamplePOJO, Long> dao = new HibernateDAO<ExamplePOJO, Long>(ExamplePOJO.class, Long.class);

OpenJPA

To use OpenJPA, include the pragmatach-openjpa module in your maven dependencies.

Configuration for OpenJPA is provided by these properties in pragmatach.properties

An example manual configuration for OpenJPA, in this case using HSQL would look like


openjpa.ConnectionDriverName=org.hsqldb.jdbcDriver
openjpa.ConnectionURL=jdbc:hsqldb:mem:testdb
openjpa.ConnectionUserName="sa"
openjpa.ConnectionPassword=""
openjpa.jdbc.SynchronizeMapping=buildSchema(ForeignKeys=true)

An example of configuring a DB connection via JNDI is:


openjpa.ConnectionFactoryName=java:comp/env/jdbc/myDB
openjpa.jdbc.SynchronizeMapping=buildSchema(ForeignKeys=true)

To create a DAO for an object which has been annotated with JPA 1.0 Annotations, simply declare an OpenJPA DAO. For example:


OpenJPADAO<ExamplePOJO, Long> dao = new OpenJPADAO<ExamplePOJO, Long>(ExamplePOJO.class, Long.class);

MongoDB

To use EBean, include the pragmatach-mongodb module in your maven dependencies.

Configuration for MongoDB is provided by these properties in pragmatach.properties

An example configuration for EBean using manual configuration, in this case using HSQL would look like


mongodb.Hostname=mongoserver.khubla.com
mongodb.Database=mydb
mongodb.ConnectionUserName=
mongodb.ConnectionPassword=

To create a DAO for an object which has been annotated with JPA 1.0 Annotations, simply declare an MongoDB DAO. For example:


MongoDBDAO<ExamplePOJO> dao = new MongoDBDAO<ExamplePOJO>(ExamplePOJO.class);

The Pragmatach MongoDB DAO strategy for storing objects is to define a MongoDB collection for each entity type. Lazy loading and cascade saving is supported.

6. Administration

Administration Console

Pragmatach has a built in web administration console, just browse to the context path

/pragmatach/admin

in your application.

On localhost, the link is:

http://localhost:8080/<application context>/pragmatach/admin

In order for your project to use the Admin plugin, it must include the pragmatach-adminapp maven dependency.

JMX Bindings

In order to support runtime inspection of applications, Pragmatach supports JMX bindings using MXBeans. The easiest way to connect to Pragmatachs JMX server is using JConsole, which is part of the Java JDK

From your command-line:

JConsole

statsd

Pragmatach includes simple support for statsd, via the a plugin which subscribes to Pragmatach lifecycle events.

The configuration parameters to enable the pragmatach-statsd plugin are:

Events for statsd are supplied by an implemenation of the Pragmatach interface:

com.khubla.pragmatach.framework.lifecycle.LifecycleListener

7. FAQ

FAQ

Why are you using LogBack rather than log4j?

LogBack includes a number of improvements over log4j. The LogBack site has a page which describes the reasons to use LogBack rather than log4j.

Why bother supporting multiple persistence engines?

Different programmers are familiar with different persistence engines. In order to make Pragmatach easy to use, the framework supports three distinct JPA engines; Hibernate, EBean and OpenJPA

8. Release Notes

Release Notes

V25

  • Updated documentation
  • Fixed problem in rendering runtime exceptions

V24

  • Fixed a fairly serious bug in pragmatach-openjpa and pragmatach-ebean which was causing the DAOs to reload the configuration over and over
  • Fixed the admin app to always show controller names
  • Added the namespace com.khubla.pragmatach.framework.test with the class AbstractDAOTest to support simple CRUD testing of DAOs.

V23

  • Fixed route sorting bug

V21

  • Added streaming controller to support building controllers from simple streams

V20

  • Combined pragmatach-json and pragmatach-gson into a single plugin; pragmatach-json
  • Added support for collections to pragmatach-json

V19

  • Added OpenJPA Support
  • Application build date appears in the console
  • Minor updates to the Archetype
  • Route cache size can be configured via pragmatach.routecache.size
  • Switched from log4j to LogBack for logging
  • Expanded testing for OpenJPA, Hibernate and EBean to include embedded MySQL, H2, HSQL, and Derby
  • Added JNDI data source support to OpenJPA, EBean and Hibernate
  • Added Paging to the DAOs
  • Added support for statsd

V18

  • EBean and Hibernate support is complete

V17

  • @View is a valid property on both methods and classes; controllers can therefore now bind multiple views
  • @Controller no longer requires the name property; it will use the Controller name by default
  • Public resource are now on the file path /src/main/webapp/public. This prevents black hats from viewing templates stored at /src/main/public/
  • Added support for numerous additional containers, including jetty8, jetty7, tomcat7, tomcat6, tomee, glassfish, and jboss7
  • Refactoring to allow for easier implementation of Bean bound controllers
  • Added ok() and bad() APIs to AbstractController to ease in building simple controllers
  • Added EBean support via pragmatach-ebean plugin
  • Updated web.xml to specify that Servlet 2.4 is required

V13

  • Updated documentation
  • Added a web-based slideshow to introduce Pragmatach
  • Added a Pragmatach plugin for Facebook Authentication
  • Added a Pragmatach plugin for Google Authentication
  • Added optional support for HTTP Basic Auth
  • Significant refactoring of the codebase to support more complex plugins
  • Added pom.xml profiles for additional containers, including Jetty7,Jetty8, Tomcat6, Tomcat7, and JBoss-AS 7
  • Added YAML support
  • Configuration is now a static member of the Application object.

9. Examples

An example Velocity Controller


package com.khubla.com.pragmatach.exampleproject;

import com.khubla.pragmatach.framework.annotation.Controller;
import com.khubla.pragmatach.framework.annotation.View;
import com.khubla.pragmatach.framework.annotation.Route;
import com.khubla.pragmatach.framework.api.PragmatachException;
import com.khubla.pragmatach.framework.api.Response;
import com.khubla.pragmatach.plugin.velocity.VelocityController;

@Controller(name="index")
@View(view="index.ftl")
public class IndexController extends VelocityController {   
   @Route(uri = "/")
   public Response render(Request request) throws PragmatachException {
      return super.render(request);
   }
}

An example Gson Controller


package com.khubla.com.pragmatach.examples.gsonexample;

import java.util.Date;

import com.khubla.pragmatach.framework.annotation.Controller;
import com.khubla.pragmatach.framework.annotation.Route;
import com.khubla.pragmatach.framework.api.PragmatachException;
import com.khubla.pragmatach.framework.api.Response;
import com.khubla.pragmatach.plugin.gson.GSONController;

@Controller(name="index")
public class IndexController extends GSONController {
   /**
    * the message
    */
   private final String message = "hello world";
   /**
    * the time
    */
   private String time;

   public String getMessage() {
      return message;
   }

   public String getTime() {
      return time;
   }

   @Route(uri = "/")
   public Response render() throws PragmatachException {
      time = new Date().toString();
      return super.render();
   }

   public void setTime(String time) {
      this.time = time;
   }
}

The output produced is

{"message":"hello world","time":"Sun Feb 17 14:28:34 MST 2013"}

An example FreeMarker Controller


package com.khubla.com.pragmatach.exampleproject;

import com.khubla.pragmatach.framework.annotation.Controller;
import com.khubla.pragmatach.framework.annotation.View;
import com.khubla.pragmatach.framework.annotation.Route;
import com.khubla.pragmatach.framework.api.PragmatachException;
import com.khubla.pragmatach.framework.api.Response;
import com.khubla.pragmatach.plugin.freemarker.FreemarkerController;

@Controller(name="indexController")
@View(view = "index.html")
public class IndexController extends FreemarkerController {
   @Route(uri = "/")
   public Response render() throws PragmatachException {
      return super.render();
   }
}

An example FreeMarker Template

<!DOCTYPE html>
<head>
   <title>exampleproject</title>
</head>
<body>
<h1>exampleproject</h1>
    <img src="/public/logo.png">
    ${indexController.message}
    <h2>Current Time</h2>
    ${indexController.time}
</body>

Population of Controller fields via HTML Form POST

A typical HTML Form:

<form action="/showfeed" method="post" enctype="multipart/form-data">
    <input name="feedURL" type="text">
    <button type="submit" class="btn">Submit</button>
</form> 

When the form is posted to a controller route of type HttpMethod.post the Controller field feedURL will be automatically populated


@Controller(name="index")
@View(view = "acceptPost.html")
public class AcceptPostController extends FreemarkerController {
   /**
    * the URL
    */
   private String feedURL;

   public String getFeedURL() {
      return feedURL;
   }

   public void setFeedURL(String feedURL) {
      this.feedURL = feedURL;
   }

   @Route(uri = "/acceptpost", method = HttpMethod.post)
   public Response acceptpost() throws PragmatachException {
      try {
         if (null != feedURL) {
     
         }
         /*
          * render
          */
         return super.render();
      } catch (final Exception e) {
         throw new PragmatachException("Exception in showFeed", e);
      }
   }
}

Population of Controller fields via JSON Form POST

The JSONController is capable of accepting JSON POSTs and populating atomic controllers fields automatically. For example posting this:

{"message":"hello world"}

To this controller:


@Controller(name="index")
@View(view = "acceptPost.html")
public class AcceptPostController extends JSONController {
   /**
    * message
    */
   private String message;

   public String getMessage() {
      return message;
   }

   public void setMessage(String message) {
      this.message = message;
   }

   @Route(uri = "/acceptpost", method = HttpMethod.post)
   public Response acceptJSONpost() throws PragmatachException {
         return super.render();
      } catch (final Exception e) {
         throw new PragmatachException("Exception in showFeed", e);
      }
   }
}

Will result in the method setMessage() being called with the value "hello world".