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:
Pragmatach is licensed under the GPL.
Pragmatach requires:
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/
The pom.xml for Pragmatach applications includes a number of maven profiles for different application servers. These include:
mvn clean package jetty:run and browse to http://localhost:8080
or
mvn -Pjetty8 clean package jetty:run and browse to http://localhost:8080
mvn -Pjetty97clean package jetty:run and browse to http://localhost:8080
Note that Jetty9 requires JDK7
mvn -Pjetty7 clean package jetty:run and browse to http://localhost:8080
mvn -Ptomcat6 clean package tomcat:run-war and browse to http://localhost:8080/<application name>
mvn -Ptomcat7 clean package tomcat:run-war and browse to http://localhost:8080/<application name>
mvn -Pjbossas7 clean package jboss-as:run and browse to http://localhost:8080/<application name>
mvn -Ptomee clean package tomee:run and browse to http://localhost:8080/<application name>
mvn -Pglassfish embedded-glassfish:run and browse to http://localhost:8080/<application name>
Pragmatach was designed based on a number of core principles:
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
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/.
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
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.
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>
Pragmatch uses REST-like route mapping. Request routes are declared via the @Route annotation, in conjunction with the signature of the annotated method
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
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 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
Pragmatach uses [LogBack] for logging. The default settings are:
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 responses return the custom header X-Pragmatach-RenderTime containing the request render time, in milliseconds.
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>
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
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
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.
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);
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);
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);
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.
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.
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
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
LogBack includes a number of improvements over log4j. The LogBack site has a page which describes the reasons to use LogBack rather than log4j.
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
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); } }
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"}
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(); } }
<!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>
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); } } }
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".