Commits

Anonymous committed 1d912c9

Initial import

Comments (0)

Files changed (129)

+
+You need Maven2 to build from sources. To build everything:
+
+$ mvn clean javadoc:javadoc package assembly:assembly
+
+
+
+To generate Clojure documentation (assuming you are in taimen directory):
+
+$ mvn clean install
+$ cd taimen-parent/taimen-clojure
+$ mvn clojure:gendoc
+
+
+
+There is no automated way to run the Clojure demo servlet. However, there is a script provided:
+
+taimen-parent/taimen-clojure/src/script/run-demo.clj
+
+
+*** RELEASE NOTES ***
+
+::-----------------------::
+|| 0.2 Alpha-1 (Planned) ||
+::-----------------------::
+
+- File uploads (using Apache FileUpload)
+- TODO: Common actions sequence to be made explicit (consideration)
+- TODO: HTTP Caching (Integration with external caches)
+- TODO: Automatic encoding / compression support (example: gzip) using Common action
+- TODO: RESTful Authentication / Authorization (consideration)
+- TODO: RESTful Transactions (consideration)
+
+::----------------------::
+|| 0.1 Beta-2 (Planned) ||
+::----------------------::
+
+- TODO: "WaveRider" surfboard-rental application to demonstrate
+  modification of route-map to provide seasonal discounts
+- TODO: Lilliput updated to upload files using Apache FileUpload
+
+::----------------------::
+|| 0.1 Beta-1 (Planned) ||
+::----------------------::
+
+- Comprehensive documentation (probably a PDF book sort of)
+- "Hello Dolly" example app to demonstrate integration with
+    - StringTemplate
+    - Freemarker
+    - Velocity
+    - JSP
+
+::-------------::
+|| 0.1 Alpha-4 ||
+::-------------::
+
+- Name changed to Taimen
+- Clojure integration
+- Code cleanup
+
+::-------------::
+|| 0.1 Alpha-3 ||
+::-------------::
+
+- Extraction of request parameters (URI / POST)
+- Request parameters validation
+- Common pre action-sequence for all routes
+- HTTP Session based Flash messages (part of common pre action-sequence)
+- Servlet for serving static files (useful for dev/testing environment)
+- Lilliput: Personal blog app using Hibernate + JWebMVC + StringTemplate
+
+::-------------::
+|| 0.1 Alpha-2 ||
+::-------------::
+
+- Routes (PUT & POST only) can be specified with what content-type payload
+  (with request) do they consume. PUT and POST restriction is added.
+- User-assisted Content Negotiation (conneg) support. You can now specify
+  for every route what content-type, charset, encoding or language it can
+  produce response in. The route matcher will automatically (based on
+  acceptable quality rating, as per RFC 2616) choose the appropriate route.
+- Replaced the IAction methods executeError(), postExecuteError() and
+  postResultError() with a simple error(errorSource) method. This is to
+  simplify the concept of action-sequence walking for the user.
+- Action sequence walker now calls error-handler when an error occurs
+  while writing out the response.
+###########
+Known bugs:
+###########
+- RFC 2616 (section 14.1) prescribes an explicit quality rating (example:
+  "level=1" in "Accept: text/*, text/html, text/html;level=1, */*") to be
+  given preference over an implicit quality of same rating. This is not
+  honoured.
+
+::-------------::
+|| 0.1 Alpha-1 ||
+::-------------::
+
+- Routes that match URI, HTTP method, request payload content-type and
+  response media-type. It is a black or white match -- client-acceptable
+  quality rating is not considered.
+- Action sequence walker calls action methods execute(), postExecute() and
+  postResult() on respective events, and calls error handlers on error.
+- Response generator - reads the model and generates response accordingly.
+  Variety of model types, also including an arbitrary response generator.
+- Route builder, for easy route generation. Lots of helper methods.
+
+                                 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-Clojure.txt

+==================
+README for Clojure
+==================
+
+This file contains only Clojure-specific details. For common information
+please refer the README.txt file.
+
+::--------------------------------------------------::
+|| HOW DO I GET STARTED WITH TAIMEN USING CLOJURE ? ||
+::--------------------------------------------------::
+
+You need to extend a Taimen Servlet. There are two ways to do so --
+Compile mode and Immediate mode. See examples in following sections.
+
+In Compile mode, a servlet is compiled as a class file. For deploying your
+web application as a WAR file (Web Archive) in JBoss, Tomcat etc. you must
+use Compile mode. In Immediate mode, servlets are dynamically instantiated
+-- this mode is useful for testing / development environment by embedding a
+servlet container such as Jetty. Here, we discuss how to write a servlet so
+that it can be defined in both Compile mode and Immediate mode.
+
+::-------------------::
+|| WRITING A SERVLET ||
+::-------------------::
+
+;; appservlet.clj
+
+(ns com.example.appservlet
+  "Application servlet. :gen-class is used to control how the compiled
+  servlet class is generated (for WAR file). Your web.xml file should refer
+  to this servlet by the fully qualified class name: com.example.appservlet
+  "
+  (:gen-class
+    :extends org.bitumenframework.taimen.servlet.DefaultFixedRouteServlet))
+
+; define routemap for servlet -- it is separate so that we can use it to
+; instantiate a servlet in Immediate mode for testing
+(def routemap {
+  ; here you would put route as keys and action-sequence as their values
+  ; the entries below are only for example, see other sections for details
+  (GET "/")       [ (redirect-in-context "/home") ]
+  (GET "/home")   [ (body "<h1>Home, sweet home</h1>") ]
+  (GET "/google") [ (redirect "http://google.com") ]
+  ; when no matching route is found, it auto-responds with 404 / 406
+  })
+
+; This function is required for Compile mode -- overrides base class method
+; (because we are extending from DefaultFixedRouteServlet)
+(defn -getRouteMap []
+  routemap)
+
+::--------------------------------------------------------------::
+|| TESTING THE SERVLET USING EMBEDDED SERVLET CONTAINER (JETTY) ||
+::--------------------------------------------------------------::
+
+Assuming we are using Jettify: http://code.google.com/p/bitumenframework/
+
+;; test_appservlet.clj
+
+(ns com.example.test_appservlet
+  (:use com.example.appservlet)
+  (:import org.bitumenframework.taimen.clojure.servlet)
+  (:import org.bitumenframework.jettify.clj_jettify))
+
+(let [server (jetty-server)
+      servlet (fixed-routemap-servlet routemap)]
+  (add-servlet server servlet "/*")
+  (start-server server)
+  ; ... after some time ...
+  (stop-server server))
+
+::-------------------------------::
+|| HOW DO I EXPRESS A ROUTEMAP ? ||
+::-------------------------------::
+
+A routemap is a map of route (key) versus action-seuqnece (value) entries.
+
+To see how a route can be expressed in various ways, please refer:
+org.bitumenframework.taimen.clojure.route
+
+An action-sequence is a simple sequence (Java Collection) of IActionFactory
+objects. To help you easily create IActionFactory objects, the following
+macros / functions are provided:
+
+*** [ MACROS ] ***
+
++-----------------------------------------------------+
+| NOTE - These variabes are available in all macros:  |
+|   1. *request*    (HttpServletRequest)              |
+|   2. *route-vars* (Map<String, String>)             |
+|   3. *model*      (Map<String, Object>)             |
++-----------------------------------------------------+
+
+| Name                |  Accepts                         | Output
+|---------------------|----------------------------------|----------------
+| action-factory      | form that returns action factory | IActionFactory
+| execute             | form that returns model          | ActionSingleton
+| redirect            | form that returns URL (String)   | ActionSingleton
+| redirect-in-context | form that returns relative URI   | ActionSingleton
+| forward             | form that returns relative URI   | ActionSingleton
+| body                | form that returns body           | ActionSingleton
+| body-and-continue   | form that returns body           | ActionSingleton
+
+*** [ FUNCTIONS ] ***
+
+| Name                |  Accepts                         | Output
+|---------------------|----------------------------------|----------------
+| action              | action functions as a map        | ActionSingleton
+
+::--------------::
+|| MORE DETAILS ||
+::--------------::
+
+- Generated documentation
+- Example applications
+- Source code
+- Unit tests in the source code
+===============
+README for Java
+===============
+
+This file contains only Java-specific details. For common information
+please refer the README.txt file.
+
+::-----------------------------------------------::
+|| HOW DO I GET STARTED WITH TAIMEN USING JAVA ? ||
+::-----------------------------------------------::
+
+You need to extend a Taimen Servlet, and just use it.
+
+::-------------------::
+|| WRITING A SERVLET ||
+::-------------------::
+
+// every action must implement the IAction interface
+// you may use PassthroughActionImpl to avoid writing too much code
+public class MyServlet extends DefaultFixedRouteServlet() {
+    @Override
+    protected Map<Route, Collection<IActionFactory>> getRouteMap() {
+        return new UnmodifiableRouteMapBuilder()
+        .GET("/",     ActionUtil.unmodifiableInContextRedirectAction("/home"))
+        .GET("/home", ActionUtil.unmodifiableBodyAction("Hello World"))
+        .POST("/login", new LoginAction(), ActionUtil.unmodifiableInContextRedirectAction("/secret"))
+        .GET("/secret", new LoginCheckAction(), new DisplaySecretAction())
+        .build();
+    }
+}
+
+
+::--------------------------------------------------------------::
+|| TESTING THE SERVLET USING EMBEDDED SERVLET CONTAINER (JETTY) ||
+::--------------------------------------------------------------::
+
+Assuming we are using Jettify: http://code.google.com/p/bitumenframework/
+
+// in-process testing using Jettify
+JettyServer server = new JettyServer();
+Servlet s = new MyServlet();   // your servlet
+server.addServlet(s, "/*");
+server.start();
+// ...and after some time...
+server.stop();
+
+::-------------------------------::
+|| HOW DO I EXPRESS A ROUTEMAP ? ||
+::-------------------------------::
+
+A routemap is a map of route (key) versus action-seuqnece (value) entries.
+
+To see how a route can be expressed in various ways, please refer:
+org.bitumenframework.taimen.route.RouteBuilder
+
+An action-sequence is a simple sequence (Java Collection) of IActionFactory
+objects. To help you easily create IActionFactory objects, helper methods
+are provided in:
+
+org.bitumenframework.taimen.impl.AbstractRouteMapBuilder
+org.bitumenframework.taimen.impl.UnmodifiableRouteMapBuilder
+org.bitumenframework.taimen.impl.SynchronizedRouteMapBuilder
+
+::--------------::
+|| MORE DETAILS ||
+::--------------::
+
+- Generated documentation
+- Example applications
+- Source code
+- Unit tests in the source code
+::========::
+|| README ||
+::========::
+
+Taimen is an Open Source (Apache 2.0 license) lightweight web framework for
+Java and other JVM languages: http://code.google.com/p/bitumenframework/
+
+::-----------------::
+|| WHAT IS TAIMEN? ||
+::-----------------::
+
+- A minimalistic, Servlet based MVC-2 layer for JVM languages
+- Pure Java, No XML, Uses Java collections for configuration
+- In-process debugging using embedded servlet container, such as Jetty
+- Reads a map of REST-compatible route versus action-sequence
+- Invokes corresponding action-sequence when the request matches a route
+- Integrates easily with view technologies: JSP, StringTemplate, FreeMarker
+
+::-----------------------------------------------------------::
+|| WHY SHOULD YOU CONSIDER TAIMEN OVER OTHER WEB FRAMEWORKS? ||
+::-----------------------------------------------------------::
+
+Consider features that are going to be useful to you, then compare
+- Features
+- Ease of use (and learning curve)
+- Compatibility with dynamic JVM languages
+- Integration with multiple (and future) view technologies
+- Footprint
+- Dependencies
+
+Taimen introduces no fundamental shift from the very familiar HTTP Servlet
+technology, and is quite easy to integrate with leading view technologies
+such as JSP, Freemarker, StringTemplate, AJAX etc and IoC frameworks such
+as Spring IoC, PicoContainer and Google Guice. The HTML or the view
+template you write can remain totally unaware of Taimen, which lets view
+designers or content developers not worry about the back end MVC framework.
+
+Taimen ensures 100% separation between model and view making it quite hard
+to write bad, spaghetti code while developing the presentation layer.
+Taimen is biased in favour of RESTful HTTP and supports developer assisted
+Content-Negotiation (conneg). It introduces some powerful concepts such as:
+- Route
+- Action
+- Action Sequence
+- Model
+- RouteMap (a map with many entries { key: Route, value: Action-sequence })
+- Transparent Interception
+
+These concepts are easy to understand and are very intuitive. You may find
+yourself using them even before you realize you did!
+
+Taimen draws inspiration (and good ideas) from
+- Restlet 2.0M4     -- http://www.restlet.org/
+- Struts 1.x        -- http://struts.apache.org/1.3.10/index.html
+- Struts 2.x        -- http://struts.apache.org/2.1.6/index.html
+- Spring 3.0M4 MVC  -- http://www.springsource.com/download/community
+- Wink 0.2 SNAPSHOT -- http://incubator.apache.org/wink/downloads.html
+- Compojure         -- http://github.com/weavejester/compojure
+
+::-----------------------------------::
+|| IS IT JAX-RS (JSR 311) COMPLIANT? ||
+::-----------------------------------::
+
+Taimen is currently *NOT* JAX-RS (JSR 311) compliant. As you would notice
+JAX-RS prescribes use of annotations, which are not directly suported by
+Clojure as of now (in Clojure version 1.0). You can find the JAX-RS spec
+here:
+
+https://jsr311.dev.java.net/nonav/releases/1.0/spec/index.html
+https://jsr311.dev.java.net/nonav/releases/1.1/spec/spec.html
+https://jsr311.dev.java.net/
+
+Taimen strives to support dynamic JVM languages as well as Java with a
+clean Collections based API. Having said that, a future support for JAX-RS
+is not at all ruled out -- I am interested in receiving contributions to
+build a JAX-RS compatible extension for Taimen.
+
+::------------------------::
+|| CONCEPTS & DEFINITIONS ||
+::------------------------::
+
+DISCLAIMER: These definitions may come across as very abstract. Do not
+            freak out! The real interesting stuff is *after* this section.
+
+*** [ ROUTE ] ***
+
+A route is a flexible bunch of matchers that besides HTTP method and URI,
+can also be associated with:
+
+- "Content-Type" for PUT and POST (ConsumesMediaType)
+- "Accept" header (ProducesMediaType)
+- "Accept-Charset"  header (ProducesCharset)
+- "Accept-Language" header (ProducesLanguage)
+- "Accept-Encoding" header (ProducesEncoding)
+
+*** [ ACTION ] ***
+
+An action is a bunch of functions (see interface IAction), viz.
+- execute
+- post-execute
+- post-result
+- error
+
+Each function is executed on a certain event (self-explanatory), and
+generates Model. Illustrated in another section (below).
+
+*** [ ACTION SEQUENCE ] ***
+
+An Action-sequence is a sequence (think Java Collection) of actions invoked
+against an HTTP request. Action sequences are of two kinds:
+
+- Common action sequence (unique across all routes)
+- Route-associated action sequence (specific for each route)
+
+When an HTTP request matches a Route, the Common Action-sequence is
+executed followed by the Route-specific Action-sequence. Traversal of the
+Action-sequence is illustrated in another section (below).
+
+*** [ MODEL ] ***
+
+Model is the data (entries in a map { key: String, value: Object })
+that is generated upon executing the Actions, and is used to render output.
+
+Some keys in the model have special meaning, which are explained in another
+section (below).
+
+*** [ TRANSPARENT INTERCEPTION ] ***
+
+By design, every Action acts as an interceptor to the next one in the
+Action-sequence. The generated Model affects how this behaviour is
+controlled (through keys that have special meaning).
+
+
+::----------------::
+|| ROUTE EXAMPLES ||
+::----------------::
+
+- GET    "/"
+- GET    "/blogentry/{entryid}"    -- entryid is in JAX-RS style
+- GET    "/blogentry/{entryid}/*"
+- PUT    "/blogentry/*/new"
+- POST   "/blogentry/{entryid}"
+- DELETE "/blogentry/{entryid}/comment/{commentid}"
+- GET    "/public/*"
+- ANY    "*"
+
+
+::--------------------------------------::
+|| HOW IS THE ACTION-SEQUENCE EXECUTED? ||
+::--------------------------------------::
+
+Every action has these methods:
+    (1) execute      -- responsible for executing action logic and generating model
+    (2) post-execute -- responsible for after-execute activities
+    (3) post-result  -- responsible for after-result activities
+    (4) error        -- called when an error occurs
+
+For a given action sequence (example: [a1 a2 a3]), the execution-flow is below:
+
+          .---------------------.---------------------.---------------------.
+          |         a1          |         a2          |         a3          |
+          |---------------------+---------------------+---------------------|
+(begin) ==|=> a1.execute =======|=> a2.execute =======|=> a3.execute =======|=.
+          |                     |                     |                     | |
+        .=|== a1.post-execute <=|== a2.post-execute <=|== a3.post-execute <=|=*
+        | |                     |                     |                     |
+        *==========================> render-result ===========================.
+          |                     |                     |                     | |
+(end) <===|== a1.post-result <==|== a2.post-result <==|== a3.post-result <==|=*
+          *---------------------+---------------------+---------------------*
+
+An action can terminate the sequence by populating the model with any of these keys:
+- "forward"
+- "redirect"
+- "body" (if "continue" key is populated with value "true", then not terminated)
+
+The postExecute() and postResult() methods for executed actions are invoked
+in all cases (like the finally {} clause in Java), even when an action
+terminates the sequence. When a sequence is terminated the subsequent
+actions are not executed.
+
+::-----------------------------::
+|| HOW ARE EXCEPTIONS HANDLED? ||
+::-----------------------------::
+
+Exceptions are trapped at each of execute, postExecute, result (sequence-walker
+invokes this function) and postResult calls, as illustrated in diagrams below:
+
+[ EXECUTE ]
+
+          .-----------------------.-----------------------.---------------------.
+          |          a1           |          a2           |         a3          |
+          |-----------------------+-----------------------+---------------------|
+(begin) ==|===> a1.execute =======|=====> a2.execute      |   a3.execute        |
+          |                       |            |          |                     |
+          |                       |            V          |                     |
+          |                       |        EXCEPTION      |                     |
+          |                       |            |          |                     |
+          |                       |            V          |                     |
+(end)   <=|== a1.error(EXECUTE)<==|== a2.error(EXECUTE)   |                     |
+          *-----------------------+-----------------------+---------------------*
+
+[ POST-EXECUTE ]
+
+          .-----------------------------.--------------------------.---------------------.
+          |             a1              |            a2            |         a3          |
+          |-----------------------------+--------------------------+---------------------|
+(begin) ==|=======> a1.execute =========|======> a2.execute =======|=> a3.execute =======|=.
+          |                             |                          |                     | |
+          |       a1.post-execute       |      a2.post-execute <===|== a3.post-execute <=|=*
+          |                             |             |            |                     |
+          |                             |             V            |                     |
+          |                             |         EXCEPTION        |                     |
+          |                             |             |            |                     |
+          |                             |             V            |                     |
+(end) <===|=== a1.error(POST_EXECUTE)<==|== a2.error(POST_EXECUTE) |                     |
+          *-----------------------------+--------------------------+---------------------*
+
+[ RESULT ]
+
+          .---------------------------.---------------------------.---------------------.
+          |             a1            |            a2             |         a3          |
+          |---------------------------+---------------------------+---------------------|
+(begin) ==|=======> a1.execute =======|=======> a2.execute        |      a3.execute     |
+          |                           |   (sequence terminated)   |                     |
+          |                           |             |             |                     |
+          |                           |             V             |                     |
+        .=|===== a1.post-execute <====|===== a2.post-execute      |   a3.post-execute   |
+        | |                           |                           |                     |
+        *===================================> render-result       |                     |
+          |                           |             |             |                     |
+          |                           |             V             |                     |
+          |                           |         EXCEPTION         |                     |
+          |                           |             |             |                     |
+          |                           |             V             |                     |
+(end) <===|===== a1.error(RESULT) <===|===== a2.error(RESULT)     |                     |
+          *---------------------------+---------------------------+---------------------*
+
+[ POST-RESULT ]
+
+          .---------------------------.---------------------------.---------------------.
+          |             a1            |            a2             |         a3          |
+          |---------------------------+---------------------------+---------------------|
+(begin) ==|=======> a1.execute =======|=======> a2.execute =======|=> a3.execute =======|=.
+          |                           |                           |                     | |
+        .=|===== a1.post-execute <====|===== a2.post-execute <====|== a3.post-execute <=|=*
+        | |                           |                           |                     |
+        *============================================================> render-result =====.
+          |                           |                           |                     | |
+          |      a1.post-result       |      a2.post-result <=====|== a3.post-result <==|=*
+          |                           |             |             |                     |
+          |                           |             V             |                     |
+          |                           |         EXCEPTION         |                     |
+          |                           |             |             |                     |
+          |                           |             V             |                     |
+(end) <===|== a1.error(POST_RESULT) <=|== a2.error(POST_RESULT)   |                     |
+          *---------------------------+---------------------------+---------------------*
+
+::-----------------------------::
+|| HOW IS THE MODEL EXPRESSED? ||
+::-----------------------------::
+
+- The model is a Map<String, Object> object. The response is generated from the value:
+
+  Key      Action
+  ---      -----
+  forward  Forwards request to the specified URL
+  redirect Redirects the browser to specified URL
+  body     Generates response from the value (any of following types):
+           String, CharSequence, char[], Character[], Reader - String content
+           InputStream, byte[], Byte[]                       - Binary content
+           File, URL                                         - Either
+           IResponseGenerator                                - Arbitrary content
+
+::---------------------------::
+|| CODE EXAMPLE (QUICKSTART) ||
+::---------------------------::
+
+See README-Java.txt and/or README-Clojure.txt files.
+
+::-------------::
+|| INTEGRATION ||
+::-------------::
+
+- Taimen can be easily integrated (by writing code) with
+  - View technologies (StringTemplate, JSP, Freemarker etc)
+  - IoC containers (Spring, Guice, Pico-container)
+  - JVM-languages (both Statically typed and Dynamically typed languages)
+  - File uploads (use Apache FileUpload)
+  - AJAX
+  - Comet support is planned (likely once Servlet 3.0 spec is final)
+- Clojure integration is supported through Taimen-Clojure extension
+
+::------::
+|| TODO ||
+::------::
+
+[ Primary ]
+- Multiple file uploads
+- GET Caching (integration with caching products / frameworks)
+- Lock-token action like Struts 2 (to avoid duplicate submissions)
+- Waiting page intercepting-action like Struts 2
+
+[ Secondary ]
+- Integrate security - authorization / rights (Acegi?)
+- Integrate menu generation (another project probably)
+
+[ Documentation / Tutorials / Examples ]
+- Docbook based manual (auto-convert to PDF, using Ant task)
+- Servlet instance discovery using
+  - Static ServletTracker.register() method
+  - Servlet context (application scope, lookup by servlet name)
+- Immutable routes using Java + Spring annotations (mention Guice and Pico)
+- Mutable routes in Java
+- Mutable routes in Clojure (modify route-map in transaction - STM)
+- Files upload
+- Example RESTful service layer implementation
+<?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.taimen</groupId>
+  <artifactId>taimen</artifactId>
+  <version>0.1-BETA1</version>
+  <packaging>pom</packaging>
+  <name>Taimen Project</name>
+  <modules>
+    <module>taimen-parent</module>
+  </modules>
+  
+  <developers>
+    <developer>
+      <id>kumarshantanu</id>
+      <name>Shantanu Kumar</name>
+      <email>kumar.shantanu@gmail.com</email>
+      <roles>
+        <role>architect</role>
+        <role>developer</role>
+      </roles>
+      <timezone>+5:30</timezone>
+      <properties>
+        <picUrl>http://www.gravatar.com/avatar/39f90a6c0ffe4995fb9dff4fb6b6bad6.png</picUrl>
+      </properties>
+    </developer>
+  </developers>
+  
+  <build>
+    <plugins>
+      
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <!--<version>2.1.1</version>-->
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <phase>verify</phase>
+            <goals>
+              <!--<goal>jar-no-fork</goal>-->
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <groupId>org.apache.maven.plugins</groupId>
+        <!--<version>2.2-beta-3</version>-->
+        <!--
+        <executions>
+          <execution>
+            <id>package</id>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <phase>package</phase>
+          </execution>
+        </executions>
+        -->
+        <configuration>
+          <!--
+          <descriptorRefs>
+            <descriptorRef>bin</descriptorRef>
+          </descriptorRefs>
+          -->
+          <descriptors>
+            <descriptor>src/assemble/bin.xml</descriptor>
+            <descriptor>src/assemble/src.xml</descriptor>
+          </descriptors>
+          <useTransitiveDependencies>false</useTransitiveDependencies>
+        </configuration>
+      </plugin>
+      
+    </plugins>
+  </build>
+  
+  <profiles>
+    <profile>
+      <id>release</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-source-plugin</artifactId>
+            <!--<version>2.1.1</version>-->
+            <executions>
+              <execution>
+                <id>attach-sources</id>
+                <goals>
+                  <!--<goal>jar-no-fork</goal>-->
+                  <goal>jar</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+  
+</project>

src/assemble/bin.xml

+<assembly>
+  <id>bin</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <files>
+    <file>
+      <source>README.txt</source>
+      <outputDirectory>/</outputDirectory>
+      <filtered>true</filtered>
+    </file>
+    <file>
+      <source>LICENSE.txt</source>
+      <outputDirectory>/</outputDirectory>
+    </file>
+    <!--
+    <file>
+      <source>NOTICE.txt</source>
+      <outputDirectory>/</outputDirectory>
+      <filtered>true</filtered>
+    </file>
+    -->
+  </files>
+  <moduleSets>
+    <moduleSet>
+      <includes>
+        <include>org.bitumenframework.taimen:taimen-java</include>
+        <include>org.bitumenframework.taimen:taimen-clojure</include>
+      </includes>
+      <binaries>
+        <!--<outputDirectory>modules/${artifactId}</outputDirectory>-->
+        <includeDependencies>true</includeDependencies>
+        <unpack>false</unpack>
+        <useDefaultExcludes>true</useDefaultExcludes>
+        <dependencySets>
+          <dependencySet>
+            <outputDirectory>lib</outputDirectory>
+            <useTransitiveDependencies/>
+            <useTransitiveFiltering>true</useTransitiveFiltering>
+          </dependencySet>
+        </dependencySets>
+      </binaries>
+    </moduleSet>
+  </moduleSets>
+</assembly>

src/assemble/src.xml

+<assembly>
+  <id>src</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <!--<includeBaseDirectory>false</includeBaseDirectory>-->
+<files>
+    <file>
+      <source>README.txt</source>
+      <outputDirectory>/</outputDirectory>
+      <filtered>true</filtered>
+    </file>
+    <file>
+      <source>LICENSE.txt</source>
+      <outputDirectory>/</outputDirectory>
+    </file>
+    <file>
+      <source>BUILDING.txt</source>
+      <outputDirectory>/</outputDirectory>
+      <filtered>true</filtered>
+    </file>
+  </files>
+  <moduleSets>
+    <moduleSet>
+      <includes>
+        <include>org.bitumenframework.taimen:taimen-java</include>
+        <include>org.bitumenframework.taimen:taimen-clojure</include>
+      </includes>
+      <sources>
+        <includeModuleDirectory>false</includeModuleDirectory>
+        <fileSets>
+          <fileSet><directory>src/main/java</directory></fileSet>
+          <fileSet><directory>src/main/clojure</directory></fileSet>
+        </fileSets>
+        <outputDirectory>sources/${artifactId}</outputDirectory>
+        <useDefaultExcludes>true</useDefaultExcludes>
+      </sources>
+    </moduleSet>
+  </moduleSets>
+</assembly>

taimen-parent/pom.xml

+<?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>
+  <parent>
+    <groupId>org.bitumenframework.taimen</groupId>
+    <artifactId>taimen</artifactId>
+    <version>0.1-BETA1</version>
+  </parent>
+  <groupId>org.bitumenframework.taimen</groupId>
+  <artifactId>taimen-parent</artifactId>
+  <packaging>pom</packaging>
+  <version>0.1-BETA1</version>
+  <name>Taimen Parent</name>
+  
+  <modules>
+    <module>taimen-java</module>
+    <module>taimen-java-test</module>
+    <module>taimen-clojure</module>
+  </modules>
+  
+  <build>
+    <pluginManagement>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.5</source>
+          <target>1.5</target>
+        </configuration>
+      </plugin>
+    </plugins>
+    </pluginManagement>
+  </build>
+  
+  <dependencies>
+    <!--
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    -->
+  </dependencies>
+</project>

taimen-parent/taimen-clojure/pom.xml

+<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>
+  <parent>
+    <groupId>org.bitumenframework.taimen</groupId>
+    <artifactId>taimen-parent</artifactId>
+    <version>0.1-BETA1</version>
+  </parent>
+  <groupId>org.bitumenframework.taimen</groupId>
+  <artifactId>taimen-clojure</artifactId>
+  <packaging>jar</packaging>
+  
+  <version>0.1-BETA1</version>
+  
+  <name>taimen-clojure</name>
+  
+  <url>http://code.google.com/p/bitumenframework/</url>
+  
+  <repositories>
+    <repository>
+      <id>build.clojure.org</id>
+      <url>http://build.clojure.org/snapshots/</url>
+      <snapshots>
+        <enabled>true</enabled>
+      </snapshots>
+    </repository>
+  </repositories>
+  
+  <dependencies>
+    <dependency>
+      <groupId>org.bitumenframework.taimen</groupId>
+      <artifactId>taimen-java</artifactId>
+      <version>0.1-BETA1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.bitumenframework.taimen</groupId>
+      <artifactId>taimen-java-test</artifactId>
+      <version>0.1-BETA1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.clojure</groupId>
+      <artifactId>clojure</artifactId>
+      <version>1.1.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.clojure</groupId>
+      <artifactId>clojure-contrib</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <version>4.0.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>2.5</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <!--
+    <resources>
+      <resource>
+        <directory>src/main/clojure</directory>
+      </resource>
+    </resources>
+    -->
+    
+    <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>
+    </plugins>
+  </build>
+</project>

taimen-parent/taimen-clojure/src/main/clojure/org/bitumenframework/taimen/clojure/action.clj

+;; action.clj
+;;
+;; Author: Shantanu Kumar (kumar.shantanu@gmail.com)
+
+(ns org.bitumenframework.taimen.clojure.action
+  "Action and action-factory related stuff. Most of these return action factory
+  instance because that is what is used in an action sequence."
+  (:import java.lang.Integer)
+  (:import java.lang.RuntimeException)
+  (: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.PassthroughAction)
+  (:import org.bitumenframework.taimen.impl.action.ActionSingleton)
+  (:import org.bitumenframework.taimen.impl.action.ActionUtil)
+  (:use    org.bitumenframework.taimen.clojure.model)
+  (:import clojure.lang.IFn))
+
+
+(set! *warn-on-reflection* true)
+
+(defmacro #^IActionFactory action-factory
+  "Creates an action factory from the passed function (the function must return
+  an IAction object).
+  Usage: (action-factory factory-func)"
+  [& factory-func]
+  `(proxy [IActionFactory] []
+    (#^IAction getAction [#^HttpServletRequest ~'*request*
+                #^Map ~'*route-vars*
+                #^Map ~'*model*]
+      ~@factory-func)))
+
+(defmacro #^IFn to-execute-fn
+  "Returns a function [identical in signature to IAction.execute()] containing
+  the supplied body, which must return a valid model."
+  [& body]
+  `(fn [#^HttpServletRequest ~'request
+        #^Map ~'route-vars
+        #^Map ~'model]
+    (let [~'*request*    ~'request
+          ~'*route-vars* (merge {} ~'route-vars) ; convert to persistent map
+          ~'*model*      (merge {} ~'model)]
+      ~@body)))
+
+(defmacro #^IFn to-error-handler-fn
+  "Returns a function [identical in signature to IAction.error()] containing
+  the supplied body, which must return a valid model."
+  [& body]
+  `(fn [#^HttpServletRequest ~'request
+        #^Map ~'route-vars
+        #^Map ~'model
+        #^IAction ~'error-action
+        #^Integer ~'error-source
+        #^RuntimeException ~'error-that-occurred]
+    (let [~'*request*    ~'request
+          ~'*route-vars* (merge {} ~'route-vars) ; convert to persistent map
+          ~'*model*      (merge {} ~'model)
+          ~'*error-action*        ~'error-action
+          ~'*error-source*        ~'error-source
+          ~'*error-that-occurred* ~'error-that-occurred]
+    ~@body)))
+
+(defn #^Map dummy-execute
+  "Dummy execute function - simply returns the model."
+  [#^HttpServletRequest request
+   #^Map route-vars
+   #^Map model]
+  model)
+
+(defn #^Map dummy-error-handler
+  "Dummy error-handler - simply prints the stack trace and returns the model."
+  [#^HttpServletRequest request
+   #^Map route-vars
+   #^Map model
+   #^IAction error-action
+   #^Integer error-source
+   #^RuntimeException error-that-occured]
+  (.printStackTrace error-that-occured)
+  model)
+
+(defn #^ActionSingleton action
+  "Creates an action singleton from specified action functions -- as a map with
+  following optional key-values:
+  1. :execute-fn       execute-func
+  2. :post-execute-fn  post-execute-func
+  3. :post-result-fn   post-result-func
+  4. :error-handler-fn error-handler-func
+  
+  Example usage:
+    (action
+      { :execute-fn       execute-func
+        :post-execute-fn  post-execute-func
+        :post-result-fn   post-result-func
+        :error-handler-fn error-handler-func
+      })
+  
+  Reference: http://blog.thinkrelevance.com/2008/9/16/pcl-clojure-chapter-5
+  "
+  [{ :keys [ execute-fn post-execute-fn post-result-fn error-handler-fn ]
+     :or { execute-fn       dummy-execute
+           post-execute-fn  dummy-execute
+           post-result-fn   dummy-execute
+           error-handler-fn dummy-error-handler } }]
+  (new ActionSingleton
+    (proxy [PassthroughAction] []
+      (#^Map execute     [#^HttpServletRequest request #^Map route-vars #^Map model]
+        (execute-fn request route-vars model))
+      (#^Map postExecute [#^HttpServletRequest request #^Map route-vars #^Map model]
+        (post-execute-fn request route-vars model))
+      (#^Map postResult  [#^HttpServletRequest request #^Map route-vars #^Map model]
+        (post-result-fn request route-vars model))
+      (#^Map error [#^HttpServletRequest request #^Map route-vars #^Map model
+                    #^IAction error-action #^Integer error-source
+                    #^RuntimeException error-that-occured]
+        (error-handler-fn
+          request route-vars model
+          error-action error-source error-that-occured)))))
+
+
+(defmacro #^ActionSingleton execute
+  "Creates an action singleton from specified execute-function, which must
+  return a valid model.
+  Usage: (execute
+           (do-something)
+           (func-that-returns-model))"
+  [& execute-fn-body]
+  `(action { :execute-fn (to-execute-fn ~@execute-fn-body) }))
+
+(defmacro #^ActionSingleton redirect
+  "Creates an action singleton that redirects to the specified URL."
+  [& url]
+  `(action {
+    :execute-fn (to-execute-fn (merge ~'*model*
+                  (org.bitumenframework.taimen.impl.ModelUtil/createRedirectModel
+                    (str ~@url))))
+    }))
+
+(defmacro #^ActionSingleton redirect-in-context
+  "Creates an action singleton that redirects to the specified URI in the
+  servlet context."
+  [& in-context-uri]
+  `(action {
+    :execute-fn (to-execute-fn (merge ~'*model*
+                  (org.bitumenframework.taimen.impl.ModelUtil/createInContextRedirectModel
+                    ~'*request* (str ~@in-context-uri))))
+    }))
+
+(defmacro #^ActionSingleton forward
+  "Creates an action singleton that forwards to the specified URI in the
+  context."
+  [& url]
+  `(action {
+    :execute-fn (to-execute-fn (merge ~'*model*
+                  (org.bitumenframework.taimen.impl.ModelUtil/createForwardModel
+                    (str ~@url))))
+    }))
+
+(defmacro #^ActionSingleton body
+  "Creates an action singleton that produces body."
+  [& payload]
+  `(action {
+    :execute-fn (to-execute-fn (create-model ~'*model* ~@payload))
+    }))
+
+(defmacro #^ActionSingleton body-and-continue
+  "Creates an action singleton that produces body and continues to the next
+  action in sequence."
+  [& payload]
+  `(action {
+    :execute-fn (to-execute-fn (create-model
+                  (assoc ~'*model*
+                    org.bitumenframework.taimen.impl.ModelKey/CONTINUE true)
+                  ~@payload))
+    }))

taimen-parent/taimen-clojure/src/main/clojure/org/bitumenframework/taimen/clojure/model.clj

+;; model.clj
+;;
+;; Author: Shantanu Kumar (kumar.shantanu@gmail.com)
+
+(ns org.bitumenframework.taimen.clojure.model
+  "Model related stuff"
+  (:import java.util.Map)
+  (:import java.io.InputStream)
+  (:import java.io.Reader)
+  (:import java.io.File)
+  (:import java.net.URL)
+  (:import clojure.lang.IFn)
+  (:import clojure.lang.IPersistentVector)
+  (:import clojure.lang.ISeq)
+  (:import clojure.lang.Keyword)
+  (:import org.bitumenframework.taimen.IResponseGenerator)
+  (:import org.bitumenframework.taimen.impl.ModelKey))
+
+
+(set! *warn-on-reflection* true)
+
+(defmulti update-model
+  "Multimethod that branches on type"
+  (fn [#^Map old-model new-model]
+    (class new-model)))
+
+(defn- to-map [#^Map old-map] (merge {} old-map))
+
+(defmethod update-model Integer
+  ; Treat Integer value as HTTP Response status code
+  [#^Map old-model #^Integer status-code]
+  (assoc (to-map old-model) ModelKey/STATUS status-code))
+
+(defmethod update-model nil
+  ; Return original model if nil.
+  [#^Map old-model _]
+  old-model)
+
+;; ===== Java compatible (except Array) types =====
+
+(defmethod update-model String
+  ; Pass on as body.
+  [#^Map old-model #^String payload]
+  (assoc (to-map old-model) ModelKey/BODY payload))
+
+(defmethod update-model CharSequence
+  ; Pass on as body.
+  [#^Map old-model #^String payload]
+  (assoc (to-map old-model) ModelKey/BODY payload))
+
+(defmethod update-model Reader
+  ; Pass on as body.
+  [#^Map old-model #^Reader reader]
+  (assoc (to-map old-model) ModelKey/BODY reader))
+
+(defmethod update-model File
+  ; Pass on as body without the assumed content-type (see create-model).
+  [#^Map old-model #^File file]
+  (assoc (to-map old-model) ModelKey/BODY file))
+
+(defmethod update-model URL
+  ; Pass on as body.
+  [#^Map old-model #^URL url]
+  (assoc (to-map old-model) ModelKey/BODY url))
+
+(defmethod update-model IResponseGenerator
+  ; Pass on as body.
+  [#^Map old-model #^IResponseGenerator response-generator]
+  (assoc (to-map old-model) ModelKey/BODY response-generator))
+
+
+;; ===== Clojure specific types =====
+
+(prefer-method
+  update-model Map IFn)
+
+(defmethod update-model Map
+  ; Treat as String response body (even though it may be wrong to assume so).
+  ; TODO: Create IResponseGenerator object rather than String for lazy rendering.
+  [#^Map old-model #^Map map-entries]
+  (merge old-model map-entries))
+
+(defmethod update-model IFn
+  ; Execute the function to render result
+  [#^Map old-model #^IFn func]
+  (update-model old-model (func)))
+
+;; -- coerced-as-String types --
+
+(defmethod update-model ISeq
+  ; Treat as String response body (even though it may be wrong to assume so).
+  ; TODO: Create IResponseGenerator object rather than String for lazy rendering.
+  [#^Map old-model #^ISeq iseq]
+  (update-model old-model (str iseq)))
+
+(prefer-method
+  update-model IPersistentVector IFn)
+
+(defmethod update-model IPersistentVector
+  ; Treat as String response body (even though it may be wrong to assume so).
+  ; TODO: Create IResponseGenerator object rather than String for lazy rendering.
+  [#^Map old-model #^IPersistentVector pvector]
+  (update-model old-model (str pvector)))
+
+(defmethod update-model Keyword
+  ; Treat as String response body (even though it may be wrong to assume so).
+  ; TODO: Create IResponseGenerator object rather than String for lazy rendering.
+  [#^Map old-model #^Keyword keywd]
+  (update-model old-model (str keywd)))
+
+;; ===== Model utility =====
+
+(def default-model { ModelKey/STATUS       200
+                     ModelKey/CONTENT_TYPE "text/html" })
+
+(def status-model  { ModelKey/STATUS       200 })
+
+(defn create-model
+  "Create model from passed old-model and object."
+  [#^Map old-model payload]
+  (update-model
+    (merge (if (instance? File payload) status-model default-model) old-model)
+    payload))

taimen-parent/taimen-clojure/src/main/clojure/org/bitumenframework/taimen/clojure/route.clj

+;; route.clj
+;;
+;; Author: Shantanu Kumar (kumar.shantanu@gmail.com)
+
+(ns org.bitumenframework.taimen.clojure.route
+  "Route related stuff"
+  (:import java.lang.String)
+  (:import java.util.Collection)
+  (:import java.util.Map)
+  (:import org.bitumenframework.taimen.route.HttpMethodRegex)
+  (:import org.bitumenframework.taimen.route.RouteBuilder))
+
+
+(set! *warn-on-reflection* true)
+
+(defn route
+  "Create a route with specified HTTP methods, request URI and optional
+  parameters. Replace single-quotes with double-quotes while using.
+  Example: (route ['GET'] '/books/author/{author-id}/list')
+  Example: (route ['PUT'] '/books/isbn/{isbn}' { :consumes-media-type 'text/xml' })
+  Example below:
+  (route [ 'GET' 'POST' ] 'uri/template/{id}/*'
+    ; all parameters are optional
+    { :consumes-media-type [ 'text/xml' ]
+      :produces-media-type [ 'text/xml' 'application/pdf' ]
+      :produces-charset    [ 'utf-8' 'iso-8859-1' ]
+      :produces-encoding   [ 'gzip' ]
+      :produces-language   [ 'en' 'fr' ]
+    })
+  "
+  ([#^Collection http-methods #^String uri-template]
+    (route http-methods uri-template {} ))
+  ([#^Collection http-methods #^String uri-template { :keys [ consumes-media-types
+                                         produces-media-types
+                                         produces-charsets
+                                         produces-encodings
+                                         produces-languages ] }]
+    (let [route-description (str "Clojure-" http-methods uri-template)
+          route-builder (new RouteBuilder uri-template route-description)
+          apply-param (fn [#^Collection tokens fn-apply] (if-not (nil? tokens)
+                        (dorun (for [each tokens] (fn-apply each)))))
+         ]
+      (apply str (for [each http-methods] (do
+        (.addMethod route-builder each))))
+      (apply-param consumes-media-types (fn [#^String each] (.consumesMediaType route-builder each)))
+      (apply-param produces-media-types (fn [#^String each] (.producesMediaType route-builder each)))
+      (apply-param produces-charsets    (fn [#^String each] (.producesCharset   route-builder each)))
+      (apply-param produces-encodings   (fn [#^String each] (.producesEncoding  route-builder each)))
+      (apply-param produces-languages   (fn [#^String each] (.producesLanguage  route-builder each)))
+      (.build route-builder))))
+
+;; Shortcut functions
+;; Example: (GET "/hotel/room/{room-id}")
+
+(defn ANY     ([#^String uri-template] (route [HttpMethodRegex/ANY    ] uri-template))
+              ([#^String uri-template #^Map conneg-map]
+                                       (route [HttpMethodRegex/ANY    ] uri-template conneg-map)))
+
+(defn GET     ([#^String uri-template] (route [HttpMethodRegex/GET    ] uri-template))
+              ([#^String uri-template #^Map conneg-map]
+                                       (route [HttpMethodRegex/GET    ] uri-template conneg-map)))
+
+(defn PUT     ([#^String uri-template] (route [HttpMethodRegex/PUT    ] uri-template))
+              ([#^String uri-template #^Map conneg-map]
+                                       (route [HttpMethodRegex/PUT    ] uri-template conneg-map)))
+
+(defn POST    ([#^String uri-template] (route [HttpMethodRegex/POST   ] uri-template))
+              ([#^String uri-template #^Map conneg-map]
+                                       (route [HttpMethodRegex/POST   ] uri-template conneg-map)))
+
+(defn DELETE  ([#^String uri-template] (route [HttpMethodRegex/DELETE ] uri-template))
+              ([#^String uri-template #^Map conneg-map]
+                                       (route [HttpMethodRegex/DELETE ] uri-template conneg-map)))
+
+(defn CONNECT ([#^String uri-template] (route [HttpMethodRegex/CONNECT] uri-template))
+              ([#^String uri-template #^Map conneg-map]
+                                       (route [HttpMethodRegex/CONNECT] uri-template conneg-map)))
+
+(defn HEAD    ([#^String uri-template] (route [HttpMethodRegex/HEAD   ] uri-template))
+              ([#^String uri-template #^Map conneg-map]
+                                       (route [HttpMethodRegex/HEAD   ] uri-template conneg-map)))
+
+(defn OPTIONS ([#^String uri-template] (route [HttpMethodRegex/OPTIONS] uri-template))
+              ([#^String uri-template #^Map conneg-map]
+                                       (route [HttpMethodRegex/OPTIONS] uri-template conneg-map)))
+
+(defn TRACE   ([#^String uri-template] (route [HttpMethodRegex/TRACE  ] uri-template))
+              ([#^String uri-template #^Map conneg-map]
+                                       (route [HttpMethodRegex/TRACE  ] uri-template conneg-map)))

taimen-parent/taimen-clojure/src/main/clojure/org/bitumenframework/taimen/clojure/routemap.clj

+;; routemap.clj
+;;
+;; Author: Shantanu Kumar (kumar.shantanu@gmail.com)
+
+(ns org.bitumenframework.taimen.clojure.routemap
+  "Routemap and Routemap-provider related stuff"
+  (:import java.util.Map)
+  (:import org.bitumenframework.taimen.IRouteMapProvider)
+  (:import org.bitumenframework.taimen.impl.PassthroughRouteMapProvider))
+
+
+(set! *warn-on-reflection* true)
+
+(defn fixed-routemap-provider
+  "Creates and returns a fixed routemap provider. Typical usage may look like
+  the following:
+    (fixed-route-map-provider
+      { route1 [ action-factory1 action-factory2]
+        route2 [ action-factory3 ]
+        route3 [ action-factory4 action-factory5 ]
+      })
+  "
+  [#^Map routemap]
+  (new PassthroughRouteMapProvider routemap))
+
+(defn routemap-provider-from-ref
+  "Creates and returns a routemap-provider from a Clojure ref. This is useful if
+  you intend to change the routemap at runtime. Typical usage may look like the
+  following:
+    (def routemap-ref (ref
+      { route1 [ action-factory1 action-factory2]
+        route2 [ action-factory3 ]
+        route3 [ action-factory4 action-factory5 ]
+      }))
+    (route-map-provider-from-ref route-mapref)
+  "
+  [routemap-ref]
+  (proxy [ IRouteMapProvider ] []
+    (getRouteMap []
+      (deref routemap-ref))))
+
+(defn routemap-provider-from-fn
+  "Creates and returns a routemap-provider from a function that accepts no
+  arguments. This is useful if you want to do some processing before routemap
+  is read for route-matching.
+  BEWARE: The function will be called for each request -- exercise caution and
+          do as less as possible in the function.
+  Typical usage may look like the following:
+    (routemap-provider-from-fn (func-that-returns-routemap))
+  "
+  [#^clojure.lang.Ifn routemap-fn]
+  (proxy [ IRouteMapProvider ] []
+    (getRouteMap []
+      (routemap-fn))))

taimen-parent/taimen-clojure/src/main/clojure/org/bitumenframework/taimen/clojure/servlet.clj

+;; servlet.clj
+;;
+;; Author: Shantanu Kumar (kumar.shantanu@gmail.com)
+
+(ns org.bitumenframework.taimen.clojure.servlet
+  "Servlet related stuff -- you may need these only for tests. For using in
+  production (as a WAR) you should use :gen-class, for example:
+    (ns com.example.appservlet
+      (:gen-class
+        :extends org.bitumenframework.taimen.servlet.DefaultFixedRouteServlet))
+  
+  See example applications and documentation for more on this.
+  "
+  (:import java.util.Collection)
+  (:import java.util.Map)
+  (:import org.bitumenframework.taimen.servlet.DefaultAbstractRouteServlet)
+  (:import org.bitumenframework.taimen.servlet.DefaultFixedRouteServlet)
+  (:import org.bitumenframework.taimen.servlet.StaticFileServlet)
+  (:import org.bitumenframework.taimen.IRouteMapProvider)
+  (:import org.bitumenframework.taimen.IActionSequenceProvider))
+
+
+(set! *warn-on-reflection* true)
+
+(defn fixed-routemap-servlet
+  "Creates a servlet with fixed (immutable) routemap. Example usage may look
+  like the following:
+    (fixed-routemap-servlet routemap)
+    (fixed-routemap-servlet routemap common-action-sequence)
+  "
+  ([#^Map routemap]
+    (proxy [DefaultFixedRouteServlet] []
+      (getRouteMap [] routemap)))
+  ([#^Map routemap
+    #^Collection common-action-sequence]
+    (proxy [DefaultFixedRouteServlet] []
+      (getRouteMap [] routemap)
+      (getCommonActionSequence [] common-action-sequence))))
+
+(defn servlet-from-routemap-provider
+  "Creates a servlet with specified routemap (and common-action-sequence)
+  provider.  Example usage may look like the following:
+    (servlet-from-routemap-provider routemap-provider)
+    (servlet-from-routemap-provider routemap-provider common-action-sequence-provider)
+  "
+  ([#^IRouteMapProvider routemap-provider]
+    (proxy [DefaultAbstractRouteServlet] []
+      (getRouteMapProvider [] routemap-provider)))
+  ([#^IRouteMapProvider routemap-provider
+    #^IActionSequenceProvider common-action-sequence-provider]
+    (proxy [DefaultAbstractRouteServlet] []
+      (getRouteMapProvider [] routemap-provider)
+      (getCommonActionSequenceProvider [] common-action-sequence-provider))))

taimen-parent/taimen-clojure/src/script/run-demo.clj

+;; run_demo.clj
+;;
+;; Author: Shantanu Kumar (kumar.shantanu@gmail.com)
+
+(ns org.bitumenframework.taimen.clojure.test.environment.run_demo
+  "Run demo"
+  (:use org.bitumenframework.taimen.clojure.test.support.demoservlet)
+  (:use org.bitumenframework.taimen.clojure.servlet)
+  (:use org.bitumenframework.jettify.jettyserver))
+
+(let [server (jetty-server)
+      servlet (fixed-routemap-servlet route-map)]
+  (add-servlet server servlet "/*")
+  (start-server server)
+  ; ... after some time ...
+  ;(stop-server server)
+  )

taimen-parent/taimen-clojure/src/script/run-tests.clj

+;; run_tests.clj
+;;
+;; Author: Shantanu Kumar (kumar.shantanu@gmail.com)
+
+(ns org.bitumenframework.taimen.clojure.test.environment.run_tests
+  "Run all tests"
+  (:use clojure.contrib.test-is)
+  (:use org.bitumenframework.taimen.clojure.test.testroute)
+  (:use org.bitumenframework.taimen.clojure.test.testmodel)
+  (:use org.bitumenframework.taimen.clojure.test.testaction)
+  (:use org.bitumenframework.taimen.clojure.test.testroutemap)
+  (:use org.bitumenframework.taimen.clojure.test.testservlet))
+
+
+(set! *warn-on-reflection* true)
+
+(run-tests
+  'org.bitumenframework.taimen.clojure.test.testroute
+  'org.bitumenframework.taimen.clojure.test.testmodel
+  'org.bitumenframework.taimen.clojure.test.testaction
+  'org.bitumenframework.taimen.clojure.test.testroutemap
+  'org.bitumenframework.taimen.clojure.test.testservlet)

taimen-parent/taimen-clojure/src/test/clojure/org/bitumenframework/taimen/clojure/test/support/demoservlet.clj

+;; demo_servlet.clj
+;;
+;; Author: Shantanu Kumar (kumar.shantanu@gmail.com)
+
+(ns org.bitumenframework.taimen.clojure.test.support.demoservlet
+  "This is a demo servlet"
+  (:gen-class
+    :extends org.bitumenframework.taimen.servlet.DefaultFixedRouteServlet)
+  (:use org.bitumenframework.taimen.clojure.route)
+  (:use org.bitumenframework.taimen.clojure.model)
+  (:use org.bitumenframework.taimen.clojure.action)
+  (:use org.bitumenframework.taimen.clojure.routemap))
+
+
+(set! *warn-on-reflection* true)