Commits

Anonymous committed 1559b10

Initial import

  • Participants

Comments (0)

Files changed (76)

+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+::--------::
+|| README ||
+::--------::
+
+Lilliput is a tiny personal blog application written to demonstrate the
+features of Bitumen Framework:
+http://code.google.com/p/bitumenframework/
+
+Lilliput is Open Source software (Apache 2.0 license). Read the LICENSE.txt
+for licensing details.
+
+For discussions, consider joining the group:
+http://groups.google.com/group/bitumenframework
+
+You can also contact me directly by writing to kumar.shantanu@gmail.com
+
+
+::------------------::
+|| ADDITIONAL NOTES ||
+::------------------::
+
+This project uses the following Open Source libraries:
+
+- Embedded H2 database for development-mode
+- Embedded Jetty servlet container for development-mode
+- Jettify as a helper to embed Jetty servlet container
+- Taimen as the Servlet controller for MVC-2 stuff
+- Hibernate + Hibernate Annotations for Object-Relational Mapping
+- StringTemplate for view layer (HTML templating)
+
+You need Maven2 to build/run the code:
+
+$ mvn clean package jetty:run-war
+
+<?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>org.bitumenframework.app.lilliput</groupId>
+  <artifactId>lilliput</artifactId>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>war</packaging>
+  <name>Lilliput - Simple blog</name>
+  
+  <repositories>
+	<repository>
+      <id>clojars.org</id>
+      <url>http://clojars.org/repo</url>
+    </repository>
+  </repositories>
+
+  <dependencies>
+	<dependency>
+		<groupId>com.h2database</groupId>
+		<artifactId>h2</artifactId>
+		<!--<version>1.2.132</version>-->
+		<version>1.1.116</version>
+	</dependency>
+    <dependency>
+	  <groupId>commons-dbcp</groupId>
+	  <artifactId>commons-dbcp</artifactId>
+	  <version>1.2.2</version>
+	</dependency>
+	<dependency>
+	  <groupId>org.hibernate</groupId>
+	  <artifactId>hibernate</artifactId>
+	  <version>3.2.6.ga</version>
+	  <exclusions>
+	    <exclusion>
+	      <groupId>javax.transaction</groupId>
+	      <artifactId>jta</artifactId>
+	    </exclusion>
+	  </exclusions>
+	</dependency>
+    
+	<dependency>
+	  <groupId>org.hibernate</groupId>
+	  <artifactId>hibernate-tools</artifactId>
+	  <version>3.2.0.ga</version>
+	</dependency>
+	<dependency>
+      <groupId>org.hibernate</groupId>
+      <artifactId>hibernate-annotations</artifactId>
+      <version>3.3.1.GA</version>
+    </dependency>
+	<dependency>
+      <groupId>org.hibernate</groupId>
+      <artifactId>hibernate-commons-annotations</artifactId>
+      <version>3.3.0.ga</version>
+    </dependency>
+	<dependency>
+      <groupId>org.hibernate</groupId>
+      <artifactId>ejb3-persistence</artifactId>
+      <version>1.0.2.GA</version>
+    </dependency>
+	
+	<dependency>
+      <groupId>org.antlr</groupId>
+      <artifactId>stringtemplate</artifactId>
+      <version>3.2.1</version>
+    </dependency>
+	
+	<dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>2.5</version>
+	  <scope>provided</scope>
+    </dependency>
+	<dependency>
+      <groupId>org.bitumenframework.taimen</groupId>
+      <artifactId>taimen-java</artifactId>
+      <version>0.1-BETA1</version>
+    </dependency>
+	<dependency>
+      <groupId>commons-fileupload</groupId>
+      <artifactId>commons-fileupload</artifactId>
+      <version>1.2.1</version>
+    </dependency>
+	<dependency>
+      <groupId>org.bitumenframework.jettify</groupId>
+      <artifactId>jettify-java</artifactId>
+      <version>0.2</version>
+	  <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>clojure-http-client</groupId>
+      <artifactId>clojure-http-client</artifactId>
+      <version>1.0.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.htmlparser</groupId>
+      <artifactId>htmlparser</artifactId>
+      <version>1.6</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>com.theoryinpractise</groupId>
+        <artifactId>clojure-maven-plugin</artifactId>
+        <!--<version>1.3</version>-->
+        <executions>
+          <execution>
+            <id>compile</id>
+            <phase>compile</phase>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>test-compile</id>
+            <phase>test-compile</phase>
+            <goals>
+              <goal>testCompile</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>test</id>
+            <phase>test</phase>
+            <goals>
+              <goal>test</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <clojureOptions>-Dbasedir=${basedir}</clojureOptions>
+          <sourceDirectories>
+            <sourceDirectory>src/main/clojure</sourceDirectory>
+          </sourceDirectories>
+          <testSourceDirectories>
+            <testSourceDirectory>src/test/clojure</testSourceDirectory>
+          </testSourceDirectories>
+          <testScript>${basedir}/src/script/run-tests.clj</testScript>
+        </configuration>
+        
+      </plugin>
+      
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.5</source>
+          <target>1.5</target>
+        </configuration>
+      </plugin>
+	  
+	  <plugin>
+        <groupId>org.mortbay.jetty</groupId>
+        <artifactId>maven-jetty-plugin</artifactId>
+        <configuration>
+		  <webApp>${basedir}/target/lilliput-0.1-SNAPSHOT.war</webApp>
+		  <contextPath>lilliput</contextPath>
+		  <!--
+          <webAppSourceDirectory>${basedir}/src/main/staticfiles</webAppSourceDirectory>
+          <webXml>${basedir}/src/main/webapp/WEB-INF/web.xml</webXml>
+		  -->
+		  <!--
+          <jettyEnvXml>${basedir}/src/over/here/jetty-env.xml</jettyEnvXml>
+		  -->
+		  <!--
+          <classesDirectory>${basedir}/somewhere/else</classesDirectory>
+		  -->
+		  <!--
+          <scanTargets>
+            <scanTarget>src/mydir</scanTarget>
+            <scanTarget>src/myfile.txt</scanTarget>
+          </scanTargets>
+          <scanTargetPatterns>
+            <scanTargetPattern>
+              <directory>src/other-resources</directory>
+              <includes>
+                <include>**/*.xml</include>
+                <include>**/*.properties</include>
+              </includes>
+              <excludes>
+                <exclude>**/myspecial.xml</exclude>
+                <exclude>**/myspecial.properties</exclude>
+              </excludes>
+            </scanTargetPattern>
+          </scanTargetPatterns>
+		  -->
+        </configuration>
+      </plugin>
+	  
+    </plugins>
+	
+	<resources>
+	  <resource>
+		<directory>src/main/resources</directory>
+	  </resource>
+	  <resource>
+		<directory>src/resources/dev</directory>
+		<includes>
+		  <include>hibernate.properties</include>
+		</includes>
+		<filtering>true</filtering>
+	  </resource>
+	  <resource>
+		<directory>src/resources/dev</directory>
+		<excludes>
+		  <exclude>hibernate.properties</exclude>
+		</excludes>
+		<filtering>false</filtering>
+	  </resource>
+	</resources>	
+	
+  </build>
+  
+  <profiles>
+	<profile>
+		<id>env-production</id>
+		<activation>
+			<property>
+				<name>env</name>
+				<value>production</value>
+			</property>
+		</activation>
+		<build>
+			<resources>
+				<resource>
+					<directory>src/resources/production</directory>
+					<includes>
+						<include>hibernate.properties</include>
+					</includes>
+					<filtering>true</filtering>
+				</resource>
+			</resources>
+		</build>
+		<!--
+		<dependencies>
+			<dependency>
+				<groupId>mysql</groupId>
+				<artifactId>mysql-connector-java</artifactId>
+				<version>5.0.5</version>
+				<scope>provided</scope>
+			</dependency>
+		</dependencies>
+		<properties>
+			<hibernate.datasource>java:comp/env/jdbc/live_datasource</hibernate.datasource>
+		</properties>
+		-->
+	</profile>
+    </profiles>
+  
+</project>

src/main/java/org/bitumenframework/app/lilliput/hibernate/DefaultHibernateTaskExecutor.java

+package org.bitumenframework.app.lilliput.hibernate;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.Transaction;
+
+public class DefaultHibernateTaskExecutor implements IHibernateTaskExecutor {
+    
+    private final SessionFactory sessionFactory;
+    
+    public DefaultHibernateTaskExecutor(final SessionFactory hibernateSessionFactory) {
+        this.sessionFactory = hibernateSessionFactory;
+    }
+    
+    public void executeInSession(final IHibernateTask task) {
+        final Session session = sessionFactory.openSession();
+        try {
+            task.execute(session);
+        } finally {
+            session.close();
+        }
+    }
+    
+    public void executeInTransaction(final IHibernateTask task) {
+        final Session session = sessionFactory.openSession();
+        final Transaction tx = session.beginTransaction();
+        try {
+            task.execute(session);
+            tx.commit();
+        } catch (final RuntimeException e) {
+            try {
+                tx.rollback();
+            } catch (Exception e2) { /* ignore */ }
+            throw e; // re-throw
+        } finally {
+            session.close();
+        }
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/hibernate/HibernateUtil.java

+package org.bitumenframework.app.lilliput.hibernate;
+
+import java.io.Serializable;
+import java.util.List;
+
+
+import org.bitumenframework.app.lilliput.model.ICriteriaSpecifier;
+import org.hibernate.Criteria;
+import org.hibernate.Session;
+
+public class HibernateUtil {
+    
+    @SuppressWarnings("unchecked")
+    public static <T>List<T> findAll(final Session hibernateSession,
+            final Class<T> clazz,
+            final ICriteriaSpecifier criteriaSpecifier) {
+        final Criteria cr = hibernateSession.createCriteria(clazz);
+        criteriaSpecifier.specify(cr);
+        return cr.list();
+    }
+    
+    public static <T>List<T> findAll(final Session hibernateSession,
+            final Class<T> clazz) {
+        return findAll(hibernateSession, clazz, new ICriteriaSpecifier() {
+            public void specify(final Criteria c) { /* do nothing */ }}
+        );
+    }
+    
+    @SuppressWarnings("unchecked")
+    public static <T>T findById(final Session hibernateSession,
+            final Class<T> clazz, final Serializable id) {
+        return (T) hibernateSession.get(clazz, id);
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/hibernate/IHibernateTask.java

+package org.bitumenframework.app.lilliput.hibernate;
+
+import org.hibernate.Session;
+
+public interface IHibernateTask {
+    
+    public abstract void execute(Session hibernateSession);
+    
+}

src/main/java/org/bitumenframework/app/lilliput/hibernate/IHibernateTaskExecutor.java

+package org.bitumenframework.app.lilliput.hibernate;
+
+public interface IHibernateTaskExecutor {
+    
+    public abstract void executeInSession(IHibernateTask task);
+    
+    public abstract void executeInTransaction(IHibernateTask task);
+    
+}

src/main/java/org/bitumenframework/app/lilliput/model/AbstractHibernateStruct.java

+package org.bitumenframework.app.lilliput.model;
+
+import java.lang.reflect.Field;
+import org.hibernate.Session;
+
+
+public abstract class AbstractHibernateStruct {
+    
+    // ---------- Hibernate utility methods ----------
+    
+    public AbstractHibernateStruct save(final Session hibernateSession) {
+        hibernateSession.save(this);
+        return this;
+    }
+    public AbstractHibernateStruct delete(final Session hibernateSession) {
+        hibernateSession.delete(this);
+        return this;
+    }
+    
+    // ---------- End of Hibernate utility methods ----------
+    
+    private abstract class AbstractStringBuilderProcessor implements ITask {
+        protected final StringBuilder sb;
+        public AbstractStringBuilderProcessor(final StringBuilder sb) {
+            this.sb = sb;
+        }
+        public void execute() { }  // dummy implementation
+    }
+    
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null || !(obj instanceof AbstractHibernateStruct)) {
+            return false;
+        }
+        final AbstractHibernateStruct other = (AbstractHibernateStruct) obj;
+        return toString().equals(other.toString());
+    }
+    
+    @Override
+    public int hashCode() {
+        return toString().hashCode();
+    }
+    
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("");
+        final ITask beforeEach = new AbstractStringBuilderProcessor(sb) { };
+        final ITask afterEach  = new AbstractStringBuilderProcessor(sb) {
+            @Override
+            public void execute() { sb.append('\n'); }
+        };
+        buildFormattedString(beforeEach, sb, afterEach);
+        return sb.toString();
+    }
+    
+    public String toHtmlString() {
+        final StringBuilder sb = new StringBuilder("");
+        sb.append("<pre>");
+        final ITask beforeEach = new AbstractStringBuilderProcessor(sb) { };
+        final ITask afterEach = new AbstractStringBuilderProcessor(sb) {
+            @Override
+            public void execute() { sb.append("<br/>\n"); }
+        };
+        buildFormattedString(beforeEach, sb, afterEach);
+        sb.append("</pre>");
+        return sb.toString();
+    }
+    
+    private void buildFormattedString(final ITask beforeEach,
+            final StringBuilder duringEach, final ITask afterEach) {
+        final Field[] fields = this.getClass().getDeclaredFields();
+        for (Field each: fields) {
+            beforeEach.execute();
+            duringEach.append(each.getName());
+            duringEach.append('=');
+            try {
+                duringEach.append(each.get(this));
+            } catch (final IllegalAccessException e) {
+                duringEach.append("(ERROR: Cannot read value)");
+            }
+            afterEach.execute();
+        }
+    }
+    
+    public interface ITask {
+        
+        public abstract void execute() throws RuntimeException;
+        
+    }
+}

src/main/java/org/bitumenframework/app/lilliput/model/BlogEntry.java

+package org.bitumenframework.app.lilliput.model;
+
+import java.io.Serializable;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+
+@SuppressWarnings("serial")
+@Entity
+public class BlogEntry extends AbstractHibernateStruct implements Serializable {
+    @Id @GeneratedValue  public Long      id;
+    @Column(length=100)  public String    title;
+    @Column(length=3000) public String    content;
+                         public Timestamp when_posted;
+                         public Boolean   is_deleted;
+    @OneToMany(cascade={CascadeType.ALL})
+    @JoinColumn(name="ENTRY_ID", nullable=false)
+                         public Collection<UserComment> comments = new ArrayList<UserComment>();
+    
+    // -------- Setters (required for access from Clojure) -------
+    
+    public void setId(Long id)                { this.id = id; }
+    public void setTitle(String title)        { this.title = title; }
+    public void setContent(String content)    { this.content = content; }
+    public void setWhen_posted(Timestamp w_p) { this.when_posted = w_p; }
+    public void setIs_deleted(Boolean i_d)    { this.is_deleted = i_d; }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/model/ICriteriaSpecifier.java

+package org.bitumenframework.app.lilliput.model;
+
+import org.hibernate.Criteria;
+
+public interface ICriteriaSpecifier {
+    
+    public abstract void specify(final Criteria c);
+    
+}

src/main/java/org/bitumenframework/app/lilliput/model/UserComment.java

+package org.bitumenframework.app.lilliput.model;
+
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+
+@SuppressWarnings("serial")
+@Entity
+public class UserComment extends AbstractHibernateStruct implements Serializable {
+    @Id @GeneratedValue  public Long      id;
+    @ManyToOne @JoinColumn(name="ENTRY_ID", nullable=false, updatable=false, insertable=false)
+                         public BlogEntry blog_entry;
+    @Column(length=1000) public String    content;
+                         public Timestamp when_posted;
+                         public Boolean   is_deleted;
+    @Column(length=100)  public String    name;
+    @Column(length=100)  public String    email;
+    @Column(length=100)  public String    url;
+    
+    // -------- Setters (required for access from Clojure) -------
+    
+    public void setId(Long id)                { this.id = id; }
+    public void setBlog_entry(BlogEntry b_e)  { this.blog_entry = b_e; }
+    public void setContent(String content)    { this.content = content; }
+    public void setWhen_posted(Timestamp w_p) { this.when_posted = w_p;  }
+    public void setIs_deleted(Boolean i_d)    { this.is_deleted = i_d;   }
+    public void setName(String name)          { this.name = name; }
+    public void setEmail(String email)        { this.email = email; }
+    public void setUrl(String url)            { this.url = url; }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/BlogServlet.java

+package org.bitumenframework.app.lilliput.mvc;
+
+import java.util.Collection;
+import java.util.Map;
+
+
+import org.bitumenframework.app.lilliput.hibernate.DefaultHibernateTaskExecutor;
+import org.bitumenframework.app.lilliput.hibernate.IHibernateTaskExecutor;
+import org.bitumenframework.app.lilliput.model.BlogEntry;
+import org.bitumenframework.app.lilliput.model.UserComment;
+import org.bitumenframework.taimen.IAction;
+import org.bitumenframework.taimen.IActionFactory;
+import org.bitumenframework.taimen.impl.UnmodifiableRouteMapBuilder;
+import org.bitumenframework.taimen.impl.action.ActionUtil;
+import org.bitumenframework.taimen.impl.action.UnmodifiableActionSequenceBuilder;
+import org.bitumenframework.taimen.route.IRoute;
+import org.bitumenframework.taimen.route.RouteBuilder;
+import org.bitumenframework.taimen.servlet.DefaultFixedRouteServlet;
+import org.hibernate.SessionFactory;
+import org.hibernate.cfg.AnnotationConfiguration;
+import org.hibernate.cfg.ImprovedNamingStrategy;
+
+/**
+ * Blog servlet, which is the entry point for this application -- it is
+ * instantiated only once by the servlet container and reused for serving
+ * all requests.
+ * 
+ * Feel free to do initialization in the constructor or in the init() method
+ * body. However, you CANNOT override the service() method because it is used
+ * by the framework.
+ * 
+ * @author Shantanu Kumar (kumar.shantanu@gmail.com)
+ *
+ */
+public class BlogServlet extends DefaultFixedRouteServlet {
+    
+    /**
+     * Default value.
+     */
+    private static final long serialVersionUID = 1L;
+    
+    @Override
+    protected Map<? extends IRoute, ? extends Collection<? extends IActionFactory>> getRouteMap() {
+        // hibernate configuration
+        final AnnotationConfiguration hConfig = new AnnotationConfiguration()
+        .setNamingStrategy(new ImprovedNamingStrategy()) // camelCase to underscored_col_names
+        .addAnnotatedClass(BlogEntry.class)  // add domain model POJOs
+        .addAnnotatedClass(UserComment.class);
+        
+        final SessionFactory sessionFactory = hConfig.buildSessionFactory();
+        final IHibernateTaskExecutor executor = new DefaultHibernateTaskExecutor(sessionFactory);
+        
+        // initialize for actions
+        final IAction redirectToHomepage = ActionUtil.inContextRedirectAction("/blog/front");
+        final IRoute getBlogEntry = RouteBuilder.GET("/blog/entry/{id}");
+        final IRoute logout = RouteBuilder.GETBuilder("/blog/logout", "Logout")
+        .addMethod("POST").build();
+        
+        // Recommended: Finish all initialization before returning the route map
+        return new UnmodifiableRouteMapBuilder()
+        .GET ("/",      redirectToHomepage)
+        .GET ("/blog",  redirectToHomepage)
+        .GET ("/blog/", redirectToHomepage)
+        .GET ("/blog/404",      new FourOhFourAction())
+        .GET ("/blog/front",    new FrontAction(executor))
+        .POST("/blog/login",    new LoginAction(),  redirectToHomepage)
+        .add (logout,           new LogoutAction(), redirectToHomepage)
+        .POST("/blog/newentry",
+                new LoginCheckAction(),
+                new NewEntryAction(executor), redirectToHomepage)
+        .add (getBlogEntry,     new EntryAction(executor))
+        // in the route below, {id} is a route variable
+        .POST("/blog/entry/{id}/newcomment",
+                new UnmodifiableActionSequenceBuilder()
+                .addActions(new NewCommentAction(executor))
+                .addActionFactories(new RedirectToEntryActionFactory(getBlogEntry)))
+        .GET ("/blog/atom", ActionUtil.bodyAction("<h1>TODO: Atom feeds :-)</h1>"))
+        // in the end we call build(), which returns the route-map
+        .build();
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/EntryAction.java

+package org.bitumenframework.app.lilliput.mvc;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+import org.antlr.stringtemplate.StringTemplate;
+import org.bitumenframework.app.lilliput.hibernate.HibernateUtil;
+import org.bitumenframework.app.lilliput.hibernate.IHibernateTask;
+import org.bitumenframework.app.lilliput.hibernate.IHibernateTaskExecutor;
+import org.bitumenframework.app.lilliput.model.BlogEntry;
+import org.bitumenframework.app.lilliput.model.UserComment;
+import org.bitumenframework.app.lilliput.mvc.util.StringTemplateUtil;
+import org.bitumenframework.taimen.impl.ModelUtil;
+import org.bitumenframework.taimen.impl.action.PassthroughAction;
+import org.bitumenframework.taimen.util.ArgsUtil;
+import org.bitumenframework.taimen.util.EncodeMarkupEntities;
+import org.hibernate.Session;
+
+public class EntryAction extends PassthroughAction {
+    
+    private final IHibernateTaskExecutor executor;
+    
+    public EntryAction(final IHibernateTaskExecutor executor) {
+        this.executor = executor;
+    }
+    
+    @SuppressWarnings("serial")
+    @Override
+    public Map<String, Object> execute(final HttpServletRequest request,
+            final Map<String, String> routeVariables, final Map<String, Object> model) {
+        final String idStr = routeVariables.get("id");
+        final Long id = Long.parseLong(idStr);
+        
+        final StringTemplate template = StringTemplateUtil.getTemplate(
+                "view_entry", request.getContextPath(), request.getSession(true), model);
+        
+        final class MutableReturnValue {
+            public Map<String, Object> model = null;
+            public boolean isSet() {
+                return !(model == null);
+            }
+        }
+        final MutableReturnValue returnValue = new MutableReturnValue();
+        
+        executor.executeInSession(new IHibernateTask() {
+            public void execute(final Session hibernateSession) {
+                final BlogEntry entry = HibernateUtil.findById(hibernateSession, BlogEntry.class, id);
+                if (entry == null) {
+                    returnValue.model = ModelUtil.createInContextRedirectModel(request, "/blog/404");
+                    return;
+                }
+                if (entry.comments.size() == 1) {
+                    template.setAttribute("one_comment", true);
+                } else if (entry.comments.size() > 1) {
+                    template.setAttribute("many_comments", true);
+                }
+                
+                // set comments
+                for (final UserComment comment: entry.comments) {
+                    template.setAttribute("comments", new LinkedHashMap<String, Object>() {
+                        public LinkedHashMap<String, Object> initNow() {
+                            put("id",          comment.id);
+                            put("content",     EncodeMarkupEntities.asHtmlString(comment.content));
+                            put("when_posted", comment.when_posted);
+                            put("is_deleted",  comment.is_deleted);
+                            put("name",        EncodeMarkupEntities.asHtmlString(comment.name));
+                            put("email",       comment.email);
+                            put("url",         EncodeMarkupEntities.asHtmlString(comment.url));
+                            put("url_is_null", ArgsUtil.isNullOrEmpty(comment.url));
+                            return this;
+                        }
+                    }.initNow());
+                }
+                
+                // set blog entry
+                template.setAttribute("blog_entry", new LinkedHashMap<String, Object>() {
+                    public LinkedHashMap<String, Object> initNow() {
+                        put("id",          entry.id);
+                        put("title",       EncodeMarkupEntities.asHtmlString(entry.title));
+                        put("when_posted", entry.when_posted);
+                        put("content",     EncodeMarkupEntities.asHtmlString(entry.content));
+                        return this;
+                    }
+                }.initNow());
+            }
+        });
+        
+        if (returnValue.isSet()) {
+            return returnValue.model;
+        }
+        
+        return ModelUtil.createHtmlModel(template.toString());
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/FourOhFourAction.java

+package org.bitumenframework.app.lilliput.mvc;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.bitumenframework.taimen.impl.ModelUtil;
+import org.bitumenframework.taimen.impl.action.PassthroughAction;
+
+public class FourOhFourAction extends PassthroughAction {
+    
+    @Override
+    public Map<String, Object> execute(final HttpServletRequest request,
+            final Map<String, String> routeVariables, final Map<String, Object> model) {
+        return ModelUtil.createHtmlModel("<h1>404 Not Found. Go back and try something else.</h1>");
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/FrontAction.java

+package org.bitumenframework.app.lilliput.mvc;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+import org.antlr.stringtemplate.StringTemplate;
+import org.bitumenframework.app.lilliput.hibernate.HibernateUtil;
+import org.bitumenframework.app.lilliput.hibernate.IHibernateTask;
+import org.bitumenframework.app.lilliput.hibernate.IHibernateTaskExecutor;
+import org.bitumenframework.app.lilliput.model.BlogEntry;
+import org.bitumenframework.app.lilliput.mvc.util.StringTemplateUtil;
+import org.bitumenframework.taimen.impl.ModelUtil;
+import org.bitumenframework.taimen.impl.action.PassthroughAction;
+import org.bitumenframework.taimen.util.EncodeMarkupEntities;
+import org.hibernate.Session;
+
+public class FrontAction extends PassthroughAction {
+    
+    private final IHibernateTaskExecutor executor;
+    
+    public FrontAction(final IHibernateTaskExecutor executor) {
+        this.executor = executor;
+    }
+    
+    @SuppressWarnings("serial")
+    @Override
+    public Map<String, Object> execute(final HttpServletRequest request,
+            final Map<String, String> routeVariables, final Map<String, Object> model) {
+        final StringTemplate template = StringTemplateUtil.getTemplate(
+                "view_front", request.getContextPath(), request.getSession(true), model);
+        
+        executor.executeInSession(new IHibernateTask() {
+            public void execute(final Session hibernateSession) {
+                final List<BlogEntry> entries = HibernateUtil.findAll(hibernateSession, BlogEntry.class);
+                
+                if (entries.size() == 1) {
+                    template.setAttribute("one_blog_entry", true);
+                } else if (entries.size() > 1) {
+                    template.setAttribute("many_blog_entries", true);
+                }
+                
+                for (final BlogEntry each: entries) {
+                    template.setAttribute("blog_entries", new LinkedHashMap<String, Object>(){
+                        public LinkedHashMap<String, Object> initNow() {
+                            put("id",    each.id);
+                            put("title", EncodeMarkupEntities.asHtmlString(each.title));
+                            put("when_posted",    each.when_posted);
+                            put("content",        EncodeMarkupEntities.asHtmlString(each.content));
+                            put("comments_count", each.comments.size()==0? "No":
+                                "" + each.comments.size());
+                            return this;
+                        }
+                    }.initNow());
+                }
+            }
+        });
+        
+        
+        return ModelUtil.createHtmlModel(template.toString());
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/LoginAction.java

+package org.bitumenframework.app.lilliput.mvc;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+import org.bitumenframework.app.lilliput.mvc.util.MvcUtil;
+import org.bitumenframework.taimen.impl.action.PassthroughAction;
+import org.bitumenframework.taimen.util.FlashMessagesAccessor;
+import org.bitumenframework.taimen.util.RequestUtil;
+import org.bitumenframework.taimen.util.MapUtil;
+
+public class LoginAction extends PassthroughAction {
+    
+    public class LoginForm {
+        public String username;
+        public String password;
+    }
+    
+    @Override
+    public Map<String, Object> execute(final HttpServletRequest request,
+            final Map<String, String> uriTokenValues, final Map<String, Object> model) {
+        final LoginForm bean = RequestUtil.extractParamValuesAsBean(request, new LoginForm());
+        
+        final FlashMessagesAccessor flash = FlashMessagesAccessor.from(model, true);
+        if ("Username".equals(bean.username) && "Password".equals(bean.password)) {
+            MvcUtil.addSuccessMessage(flash, "Login successful");
+            MvcUtil.addNoticeMessage (flash, "You can post new entries");
+            MvcUtil.setLoggedIn(request.getSession(true));
+        } else {
+            MvcUtil.addErrorMessage(flash, "Login failed");
+            MvcUtil.unsetLoggedIn(request.getSession(true));
+        }
+        return MapUtil.assoc(model, FlashMessagesAccessor.getModelKey(), flash);
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/LoginCheckAction.java

+package org.bitumenframework.app.lilliput.mvc;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+import org.bitumenframework.app.lilliput.mvc.util.MvcUtil;
+import org.bitumenframework.taimen.impl.ModelUtil;
+import org.bitumenframework.taimen.impl.action.PassthroughAction;
+import org.bitumenframework.taimen.util.FlashMessagesAccessor;
+import org.bitumenframework.taimen.util.MapUtil;
+
+public class LoginCheckAction extends PassthroughAction {
+    
+    @Override
+    public Map<String, Object> execute(final HttpServletRequest request,
+            final Map<String, String> routeVariables, final Map<String, Object> model) {
+        if (!MvcUtil.isLoggedIn(request.getSession(true))) {
+            final FlashMessagesAccessor flash = FlashMessagesAccessor.from(model, true);
+            MvcUtil.addErrorMessage(flash, "You are not logged in. You must be logged in to post new entry.");
+            
+            return MapUtil.assoc(
+                    ModelUtil.createInContextRedirectModel(request, "/blog/front"),
+                    FlashMessagesAccessor.getModelKey(), flash);
+        }
+        
+        return model;
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/LogoutAction.java

+package org.bitumenframework.app.lilliput.mvc;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+import org.bitumenframework.app.lilliput.mvc.util.MvcUtil;
+import org.bitumenframework.taimen.impl.action.PassthroughAction;
+import org.bitumenframework.taimen.util.FlashMessagesAccessor;
+import org.bitumenframework.taimen.util.MapUtil;
+
+public class LogoutAction extends PassthroughAction {
+    
+    @Override
+    public Map<String, Object> execute(final HttpServletRequest request,
+            final Map<String, String> uriTokenValues, final Map<String, Object> model) {
+        MvcUtil.unsetLoggedIn(request.getSession(true));
+        
+        final FlashMessagesAccessor flash = FlashMessagesAccessor.from(model, true);
+        MvcUtil.addNoticeMessage(flash, "You are logged out");
+        
+        return MapUtil.assoc(model, FlashMessagesAccessor.getModelKey(), flash);
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/NewCommentAction.java

+package org.bitumenframework.app.lilliput.mvc;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+import org.bitumenframework.app.lilliput.hibernate.HibernateUtil;
+import org.bitumenframework.app.lilliput.hibernate.IHibernateTask;
+import org.bitumenframework.app.lilliput.hibernate.IHibernateTaskExecutor;
+import org.bitumenframework.app.lilliput.model.BlogEntry;
+import org.bitumenframework.app.lilliput.model.UserComment;
+import org.bitumenframework.app.lilliput.mvc.util.MvcUtil;
+import org.bitumenframework.taimen.impl.ModelUtil;
+import org.bitumenframework.taimen.impl.action.PassthroughAction;
+import org.bitumenframework.taimen.util.ArgsUtil;
+import org.bitumenframework.taimen.util.FlashMessagesAccessor;
+import org.bitumenframework.taimen.util.RequestUtil;
+import org.bitumenframework.taimen.util.MapUtil;
+import org.hibernate.Session;
+
+public class NewCommentAction extends PassthroughAction {
+    
+    public class NewCommentForm {
+        public String content;
+        public String name;
+        public String email;
+        public String url;
+        public boolean isValid() {
+            return !(ArgsUtil.isNullOrEmpty(content) || ArgsUtil.isNullOrEmpty(name)
+            || ArgsUtil.isNullOrEmpty(email));
+        }
+    }
+    
+    private final IHibernateTaskExecutor executor;
+    
+    public NewCommentAction(final IHibernateTaskExecutor executor) {
+        this.executor = executor;
+    }
+    
+    @Override
+    public Map<String, Object> execute(final HttpServletRequest request,
+            final Map<String, String> routeVariables, final Map<String, Object> model) {
+        final String idStr = routeVariables.get("id");
+        final Long id = Long.parseLong(idStr);
+        
+        final FlashMessagesAccessor flash = FlashMessagesAccessor.from(model, true);
+        
+        final class MutableReturnValue {
+            public Map<String, Object> model = null;
+            public boolean isSet() {
+                return !(model == null);
+            }
+        }
+        final MutableReturnValue returnValue = new MutableReturnValue();
+        
+        final NewCommentForm bean = RequestUtil.extractParamValuesAsBean(request, new NewCommentForm());
+        if (bean.isValid()) {
+            // persist
+            try {
+                executor.executeInTransaction(new IHibernateTask() {
+                    public void execute(Session hibernateSession) {
+                        final BlogEntry entry = HibernateUtil.findById(hibernateSession, BlogEntry.class, id);
+                        if (entry == null) {
+                            returnValue.model = ModelUtil.createInContextRedirectModel(request, "/blog/404");
+                            return;
+                        }
+                        final UserComment newComment = new UserComment();
+                        newComment.setBlog_entry(entry);
+                        newComment.setContent(bean.content);
+                        newComment.setWhen_posted(
+                                new java.sql.Timestamp(new java.util.Date().getTime()));
+                        newComment.setIs_deleted (false);
+                        newComment.setName (bean.name);
+                        newComment.setEmail(bean.email);
+                        newComment.setUrl  (bean.url);
+                        entry.comments.add(newComment);
+                        // save now
+                        entry.save(hibernateSession);
+                        // report success
+                        MvcUtil.addSuccessMessage(flash, "New comment saved succesfully.");
+                    }
+                });
+            } catch (final RuntimeException e) {
+                // report failure
+                MvcUtil.addErrorMessage(flash, "Error saving comment: " + e.getMessage());
+            }
+        } else {
+            MvcUtil.addErrorMessage(flash, "Invalid / incomplete user comment.");
+        }
+        
+        if (returnValue.isSet()) {
+            return returnValue.model;
+        }
+        
+        return MapUtil.assoc(model, FlashMessagesAccessor.getModelKey(), flash);
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/NewEntryAction.java

+package org.bitumenframework.app.lilliput.mvc;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+import org.bitumenframework.app.lilliput.hibernate.IHibernateTask;
+import org.bitumenframework.app.lilliput.hibernate.IHibernateTaskExecutor;
+import org.bitumenframework.app.lilliput.model.BlogEntry;
+import org.bitumenframework.app.lilliput.mvc.util.MvcUtil;
+import org.bitumenframework.taimen.impl.action.PassthroughAction;
+import org.bitumenframework.taimen.util.ArgsUtil;
+import org.bitumenframework.taimen.util.FlashMessagesAccessor;
+import org.bitumenframework.taimen.util.RequestUtil;
+import org.bitumenframework.taimen.util.MapUtil;
+import org.hibernate.Session;
+
+public class NewEntryAction extends PassthroughAction {
+    
+    public class NewEntryForm {
+        public String title;
+        public String content;
+        public boolean isValid() {
+            return !ArgsUtil.isNullOrEmpty(title) && !ArgsUtil.isNullOrEmpty(content);
+        }
+    }
+    
+    private final IHibernateTaskExecutor executor;
+    
+    public NewEntryAction(final IHibernateTaskExecutor executor) {
+        this.executor = executor;
+    }
+    
+    @Override
+    public Map<String, Object> execute(final HttpServletRequest request,
+            final Map<String, String> routeVariables, final Map<String, Object> model) {
+        final NewEntryForm bean = RequestUtil.extractParamValuesAsBean(request, new NewEntryForm());
+        
+        final FlashMessagesAccessor flash = FlashMessagesAccessor.from(model, true);
+        if (bean.isValid()) {
+            // persist
+            try {
+                executor.executeInTransaction(new IHibernateTask() {
+                    public void execute(final Session hibernateSession) {
+                        final BlogEntry newEntry = new BlogEntry();
+                        newEntry.setTitle(bean.title);
+                        newEntry.setContent(bean.content);
+                        newEntry.setWhen_posted(
+                                new java.sql.Timestamp(new java.util.Date().getTime()));
+                        newEntry.setIs_deleted(false);
+                        newEntry.save(hibernateSession);
+                    }
+                });
+                // report success
+                MvcUtil.addSuccessMessage(flash, "New blog entry posted succesfully.");
+            } catch (Exception e) {
+                // report failure
+                MvcUtil.addErrorMessage(flash, "Error saving entry: " + e.getMessage());
+            }
+        } else {
+            // tell the user form entry is invalid
+            MvcUtil.addErrorMessage(flash, "Invalid / incomplete blog entry.");
+        }
+        
+        return MapUtil.assoc(model, FlashMessagesAccessor.getModelKey(), flash);
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/RedirectToEntryActionFactory.java

+package org.bitumenframework.app.lilliput.mvc;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.bitumenframework.taimen.IAction;
+import org.bitumenframework.taimen.IActionFactory;
+import org.bitumenframework.taimen.impl.action.ActionUtil;
+import org.bitumenframework.taimen.route.IRoute;
+import org.bitumenframework.taimen.route.IUriMatcher;
+
+/**
+ * This is an action factory. It produces an action based on the request passed.
+ * 
+ * @author Shantanu Kumar (kumar.shantanu@gmail.com)
+ *
+ */
+public class RedirectToEntryActionFactory implements IActionFactory {
+    
+    private final IUriMatcher uriMatcher;
+    
+    public RedirectToEntryActionFactory(final IRoute entryRoute) {
+        this.uriMatcher = entryRoute.getUriMatcher();
+    }
+    
+    public String getUniqueName() {
+        return this.getClass().toString();
+    }
+    
+    public IAction getAction(final HttpServletRequest request,
+            final Map<String, String> routeVariables, final Map<String, Object> model) {
+        final String id = routeVariables.get("id");
+        return ActionUtil.redirectAction(
+                uriMatcher.expand(request.getContextPath(), id).toASCIIString());
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/util/MvcUtil.java

+package org.bitumenframework.app.lilliput.mvc.util;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.http.HttpSession;
+
+import org.bitumenframework.taimen.util.FlashMessagesAccessor;
+
+public class MvcUtil {
+    
+    public static Boolean isLoggedIn(final HttpSession session) {
+        return session.getAttribute("login") != null;
+    }
+    
+    public static void setLoggedIn(final HttpSession session) {
+        session.setAttribute("login", true);
+    }
+    
+    public static void unsetLoggedIn(final HttpSession session) {
+        session.removeAttribute("login");
+    }
+    
+    public static FlashMessagesAccessor addSuccessMessage(
+            final FlashMessagesAccessor flash, final String message) {
+        flash.add("success", message);
+        return flash;
+    }
+    
+    public static FlashMessagesAccessor addNoticeMessage(
+            final FlashMessagesAccessor flash, final String message) {
+        flash.add("notice", message);
+        return flash;
+    }
+    
+    public static FlashMessagesAccessor addErrorMessage(
+            final FlashMessagesAccessor flash, final String message) {
+        flash.add("error", message);
+        return flash;
+    }
+    
+    public static String flashMessages(final FlashMessagesAccessor flashAccessor) {
+        final StringBuilder sb = new StringBuilder();
+        final ConcurrentHashMap<String, List<String>> messages = flashAccessor.getFlashMessages();
+        for (final String each: messages.keySet()) {
+            for (final String eachMsg: messages.get(each)) {
+                final String htmlFrag = "<div class='" + each + "'>" + eachMsg + "</div>\n";
+                sb.append(htmlFrag);
+            }
+        }
+        return sb.toString();
+    }
+    
+}

src/main/java/org/bitumenframework/app/lilliput/mvc/util/StringTemplateUtil.java

+package org.bitumenframework.app.lilliput.mvc.util;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+
+import org.antlr.stringtemplate.StringTemplate;
+import org.antlr.stringtemplate.StringTemplateGroup;
+import org.bitumenframework.taimen.util.FlashMessagesAccessor;
+
+public class StringTemplateUtil {
+    
+    public static StringTemplate getTemplate(final String viewName,
+            final String contextPath, final HttpSession session,
+            final Map<String, Object> model) {
+        final StringTemplate template =
+            new StringTemplateGroup("default").getInstanceOf(viewName);
+        template.setAttribute("context_path", contextPath);
+        
+        final boolean loggedIn = MvcUtil.isLoggedIn(session);
+        template.setAttribute("logged_in", loggedIn);
+        template.setAttribute("not_logged_in", !loggedIn);
+        
+        final FlashMessagesAccessor flashAccessor = FlashMessagesAccessor.from(model, true);
+        template.setAttribute("flash_messages", MvcUtil.flashMessages(flashAccessor));
+        
+        return template;
+    }
+    
+}

src/main/java/st/cfrag_blog_header.st

+<h1><a class='title' href='$context_path$/'>Lilliput</a><h1>
+<h2 class='alt'>The personal blog</h2>
+
+$flash_messages$
+
+$!---------------------------------------!$
+$! Extra notice for not-logged-in author !$
+$!---------------------------------------!$
+$if(not_logged_in)$
+  <div class='notice'>Authentication is not implemented yet -
+    just click on Login button to login. Modifying username/password
+    will cause the login to fail.
+  </div>
+$endif$
+
+
+$!---------------------------------------!$
+$! Display <div> as per logged-in status !$
+$!---------------------------------------!$
+<div class='loginbar' align='right'>
+  $if(not_logged_in)$
+    <form name='loginform' method='POST' action='$context_path$/blog/login' autocomplete='off'>
+      <input type='text' name='username' value='Username' size='16'
+        onFocus="if(this.value=='Username') this.value='';"
+        onBlur="if(this.value=='') this.value='Username';" />
+        
+      <input type='password' name='password' value='Password' size='14'
+        onFocus="if(this.value=='Password') this.value='';"
+        onBlur="if(this.value=='') this.value='Password';" />
+      
+      <input type='submit' value='Login' />
+    </form>
+  $else$
+    <a href='#' class='loginbar' onClick="
+      var ele = document.getElementById('newentry');
+      if(ele.className == 'show') {
+        ele.className='hide';
+      }
+      else if(ele.className == 'hide') {
+        ele.className='show';
+      }
+      ">Post new entry</a>
+    &nbsp;|&nbsp;&nbsp;
+    <a href='$context_path$/blog/logout' class='loginbar' onClick="
+      var ele = document.getElementById('newentry');
+      ele.className='hide';
+      ">Logout</a>
+    &nbsp;&nbsp;
+  $endif$
+</div>
+
+$!------------------------------!$
+$! Special for logged-in author !$
+$!------------------------------!$
+$if(logged_in)$
+  <div id='newentry' class='hide' align='center'>
+    <form name='newentry' method='POST' action='$context_path$/blog/newentry'>
+      Enter the new blog entry
+      <br/>
+      <input type='text' name='title' value='Title' onClick=''
+        onFocus="if(this.value=='Title') this.value='';"
+        onBlur="if(this.value=='') this.value='Title';"
+        />
+      <br/>
+      <textarea name='content' rows='40' cols='80' ></textarea>
+      <br/>
+      <div align='center'>
+      <input type='submit' value='Post new entry' />
+      <input type='button' value='Cancel' onClick="
+        var ele = document.getElementById('newentry');
+        ele.className='hide';
+        " />
+      </div>
+    </form>
+  </div>
+$endif$

src/main/java/st/cfrag_css_includes.st

+  <!-- Framework CSS -->
+  <link rel='stylesheet' href='$context_path$/blueprint/screen.css' type='text/css' media='screen, projection'> 
+  <link rel='stylesheet' href='$context_path$/blueprint/print.css' type='text/css' media='print'> 
+  <!--[if lt IE 8]><link rel='stylesheet' href='$context_path$/blueprint/ie.css' type='text/css' media='screen, projection'><![endif]--> 
+  
+  <!-- Import fancy-type plugin for the sample page. --> 
+  <link rel='stylesheet' href='$context_path$/blueprint/plugins/fancy-type/screen.css' type='text/css' media='screen, projection'>
+  
+  <!-- CSS -->
+  <link rel='stylesheet' href='$context_path$/css/main.css' type='text/css' media='screen, projection'>

src/main/java/st/frag_blogentry.st

+    <hr class='space'>
+    <div class='title'><h3><a href='entry/$blog_entry.("id")$'>$blog_entry.("title")$</a></h3></div>
+    <div class='whenposted'>Posted at $blog_entry.("when_posted")$</div>
+    <div class='commentcount'>$blog_entry.("comments_count")$ comment(s)</div><br/>
+    <div class='colborder'>$blog_entry.("content")$</div>

src/main/java/st/frag_usercomment.st

+    <div id='comment_by'><b
+        >$if(!user_comment.("url_is_null"))$<a href='$user_comment.("url")$'
+        >$endif$ $user_comment.("name")$ $if(!user_comment.("url_is_null"))$</a
+        >$endif$</b></div>
+    <div class='whenposted'>at $user_comment.("when_posted")$</div>
+    <br/><br/>
+    <div id='comment_content'>$user_comment.("content")$</div>
+    <br/>

src/main/java/st/html_template.st

+<!DOCTYPE html>
+<html>
+  <head>
+    <title>$page_title$</title>
+    $css_includes$
+  </head>
+  <body>
+    <div class='container'>
+    $blog_header$
+    $html_body$
+    </div>
+  </body>
+</html>

src/main/java/st/view_entry.st

+$!!$
+$html_template(
+    page_title=blog_entry.("title"),
+    css_includes=cfrag_css_includes(),
+    blog_header=cfrag_blog_header(),
+    html_body={
+        <br/>
+        <div class='blogentry colborder'>
+        <div id='title'><h3><a href='entry/$blog_entry.("id")$'>$blog_entry.("title")$</a></h3></div>
+        <div class='whenposted'>Posted at $blog_entry.("when_posted")$</div>
+        <br/><br/>
+        <div id='content'>$blog_entry.("content")$</div>
+        </div>
+        <hr/>
+        
+        $if(many_comments)$
+          $comments:{ each |
+            $frag_usercomment(user_comment=each)$
+          }; separator="\n"$
+        $elseif(one_comment)$
+          $frag_usercomment(user_comment=comments)$
+        $endif$
+        
+        <div>
+          <form method='POST' name='comment' action='$context_path$/blog/entry/$blog_entry.("id")$/newcomment'>
+            Name   <input type='text' name='name' /> (required)<br/>
+            E-mail <input type='text' name='email' /> (required, will not be published)<br/>
+            URL    <input type='text' name='url' /> (optional)<br/>
+            <textarea name='content' rows='10' cols='30'></textarea><br/>
+            <input type='submit' name='comment' value='Post comment' />
+          </form>
+        </div>
+    }
+)$

src/main/java/st/view_front.st

+$html_template(
+    page_title="Home",
+    css_includes=cfrag_css_includes(),
+    blog_header=cfrag_blog_header(),
+    html_body={
+        $if(many_blog_entries)$
+          $blog_entries:{ each |
+            $frag_blogentry(blog_entry=each)$
+          }; separator="\n"$
+        $elseif(one_blog_entry)$
+          $frag_blogentry(blog_entry=blog_entries)$
+        $endif$
+    }
+)$

src/main/resources/cfrag_blog_header.st

+<h1><a class='title' href='$context_path$/'>Lilliput</a></h1>
+<h2 class='alt'>The personal blog</h2>
+
+$flash_messages$
+
+$!---------------------------------------!$
+$! Extra notice for not-logged-in author !$
+$!---------------------------------------!$
+$if(not_logged_in)$
+  <div class='notice'>Authentication is not implemented yet -
+    just click on Login button to login. Modifying username/password
+    will cause the login to fail.
+  </div>
+$endif$
+
+
+$!---------------------------------------!$
+$! Display <div> as per logged-in status !$
+$!---------------------------------------!$
+<div class='loginbar' align='right'>
+  $if(not_logged_in)$
+    <form name='loginform' method='POST' action='$context_path$/blog/login' autocomplete='off'>
+      <input type='text' name='username' value='Username' size='16'
+        onFocus="if(this.value=='Username') this.value='';"
+        onBlur="if(this.value=='') this.value='Username';" />
+        
+      <input type='password' name='password' value='Password' size='14'
+        onFocus="if(this.value=='Password') this.value='';"
+        onBlur="if(this.value=='') this.value='Password';" />
+      
+      <input type='submit' value='Login' />
+    </form>
+  $else$
+    <a href='#' class='loginbar' onClick="
+      var ele = document.getElementById('newentry');
+      if(ele.className == 'show') {
+        ele.className='hide';
+      }
+      else if(ele.className == 'hide') {
+        ele.className='show';
+      }
+      ">Post new entry</a>
+    &nbsp;|&nbsp;&nbsp;
+    <a href='$context_path$/blog/logout' class='loginbar' onClick="
+      var ele = document.getElementById('newentry');
+      ele.className='hide';
+      ">Logout</a>
+    &nbsp;&nbsp;
+  $endif$
+</div>
+
+$!------------------------------!$
+$! Special for logged-in author !$
+$!------------------------------!$
+$if(logged_in)$
+  <div id='newentry' class='hide' align='center'>
+    <form name='newentry' method='POST' action='$context_path$/blog/newentry'>
+      Enter the new blog entry
+      <br/>
+      <input type='text' name='title' value='Title' onClick=''
+        onFocus="if(this.value=='Title') this.value='';"
+        onBlur="if(this.value=='') this.value='Title';"
+        />
+      <br/>
+      <textarea name='content' rows='40' cols='80' ></textarea>
+      <br/>
+      <div align='center'>
+      <input type='submit' value='Post new entry' />
+      <input type='button' value='Cancel' onClick="
+        var ele = document.getElementById('newentry');
+        ele.className='hide';
+        " />
+      </div>
+    </form>
+  </div>
+$endif$

src/main/resources/cfrag_css_includes.st

+  <!-- Framework CSS -->
+  <link rel='stylesheet' href='$context_path$/blueprint/screen.css' type='text/css' media='screen, projection'> 
+  <link rel='stylesheet' href='$context_path$/blueprint/print.css' type='text/css' media='print'> 
+  <!--[if lt IE 8]><link rel='stylesheet' href='$context_path$/blueprint/ie.css' type='text/css' media='screen, projection'><![endif]--> 
+  
+  <!-- Import fancy-type plugin for the sample page. --> 
+  <link rel='stylesheet' href='$context_path$/blueprint/plugins/fancy-type/screen.css' type='text/css' media='screen, projection'>
+  
+  <!-- CSS -->
+  <link rel='stylesheet' href='$context_path$/css/main.css' type='text/css' media='screen, projection'>

src/main/resources/frag_blogentry.st

+    <hr class='space'>
+    <div class='title'><h3><a href='entry/$blog_entry.("id")$'>$blog_entry.("title")$</a></h3></div>
+    <div class='whenposted'>Posted at $blog_entry.("when_posted")$</div>
+    <div class='commentcount'>$blog_entry.("comments_count")$ comment(s)</div><br/>
+    <div class='colborder'>$blog_entry.("content")$</div>

src/main/resources/frag_usercomment.st

+    <div id='comment_by'><b
+        >$if(!user_comment.("url_is_null"))$<a href='$user_comment.("url")$'
+        >$endif$ $user_comment.("name")$ $if(!user_comment.("url_is_null"))$</a
+        >$endif$</b></div>
+    <div class='whenposted'>at $user_comment.("when_posted")$</div>
+    <br/><br/>
+    <div id='comment_content'>$user_comment.("content")$</div>
+    <br/>

src/main/resources/html_template.st

+<!DOCTYPE html>
+<html>
+  <head>
+    <title>$page_title$</title>
+    $css_includes$
+  </head>
+  <body>
+    <div class='container'>
+    $blog_header$
+    $html_body$
+    </div>
+  </body>