{"id":20711245,"url":"https://github.com/boylesoftware/thyme","last_synced_at":"2026-04-18T21:05:03.674Z","repository":{"id":10966954,"uuid":"13281850","full_name":"boylesoftware/thyme","owner":"boylesoftware","description":"Web-application development framework for Java","archived":false,"fork":false,"pushed_at":"2015-12-24T22:39:00.000Z","size":210,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-17T21:09:49.997Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/boylesoftware.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-10-02T20:23:35.000Z","updated_at":"2015-12-24T22:27:57.000Z","dependencies_parsed_at":"2022-08-28T17:02:42.932Z","dependency_job_id":null,"html_url":"https://github.com/boylesoftware/thyme","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boylesoftware%2Fthyme","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boylesoftware%2Fthyme/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boylesoftware%2Fthyme/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boylesoftware%2Fthyme/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/boylesoftware","download_url":"https://codeload.github.com/boylesoftware/thyme/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242981930,"owners_count":20216503,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-17T02:14:37.224Z","updated_at":"2026-04-18T21:05:03.642Z","avatar_url":"https://github.com/boylesoftware.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Thyme Framework\n\n## Introduction\n\nThyme is a lightweight and, above all, practical web-application framework for Java. The framework is open source and free. It is available under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).\n\nEven though Thyme is a general purpose web-application framework, when we developed it we had a certain type of web-application in mind. Thyme is an application that faces the general public on the Internet, allows users to sign in, and provides certain services to registered, authenticated users as well as certain limited services to anonymous, unauthenticated visitors. The application data is mainly stored on the back-end in a relational database. Also, the application can connect to other third-party services on the back-end to provide some of its functionality.\n\nThe framework is not attempting to address all possible types of web-applications, so it is accepted that there are some applications out there for which the Thyme framework is not the best choice. However, a reasonable level of specialization allows the framework to be clean, lean, efficient, and easy to explore and understand.\n\n* [API Reference](http://www.boylesoftware.com/thyme/site/apidocs)\n* [Maven Generated Project Information](http://www.boylesoftware.com/thyme/site)\n* [Project on GitHub](https://github.com/boylesoftware/thyme)\n\nHere are some of the distinctive features of the framework:\n\n * **Works in a Java Servlet container**\n\n\tJava Servlet containers have had a lot of time to evolve and, even though from the developer's perspective the Servlet APIs are still rather inconvenient (hence the need for a framework), from the deployment and maintenance point of view they offer a well established, tested, mature solution.\n\n\tThe Thyme framework does not need a full-featured Java EE platform to run. It can run under a simple Servlet container, such as [Apache Tomcat](http://tomcat.apache.org/) or [Jetty](http://www.eclipse.org/jetty/).\n\n* **Provides Java API and development environment**\n\n\tThe Thyme framework is written in Java and provides Java API to custom applications. Even though there are some other excellent programming languages available for web-application development, including those based on the JVM technology, Java remains the most practical choice, especially when it comes to finding qualified developers for your project that can develop stable, high quality code.\n\n* **Minimal footprint**\n\n\tThe Thyme framework attempts to be as lean as possible. There are two aspects to the framework's minimal footprint:\n\n\t* *The framework's dependencies:* There are frameworks that pull megabytes of dependencies with them making the resulting web-applications unnecessarily huge. Thyme, on the other hand, is rather small itself and its only required dependency is the Apache Commons Logging library that it uses for debug logging. Because the resulting web-application is smaller, its deployments are faster and easier.\n\n\t* *JVM runtime memory usage:* A significant effort has been undertaken to give the framework a minimal memory footprint, thereby giving the custom application more room. Internally, instead of allocating new objects and and letting the garbage collector handle it, the framework wherever possible uses fast object instance pooling, re-using allocated objects for processing request after request. It also offers object pooling tools to the custom application, if it chooses to use them.\n\n* **Uses JPA to access the database**\n\n\tThere are several persistent storage approaches available to web-applications these days. The Thyme framework, however, is targeted at those applications that use a SQL database as the primary, persistent storage. This choice limits the number of applicable use-cases, but makes things significantly simpler to the applications that do use a SQL database for the back-end - and that is the goal. To access the database, Thyme uses JPA. There are several efficient, mature, and stable JPA implementations available, including [Hibernate](http://www.hibernate.org/), [EclipseLink](http://www.eclipse.org/eclipselink/) and [Apache OpenJPA](http://openjpa.apache.org/).\n\n* **Asynchronous request processing**\n\n\tThe Servlet API, starting with version 3.0, offers asynchronous request processing. Thyme uses it transparently for the custom application so that application developers do not have to deal with the complexities of the asynchronous Servlet API. All requests that need access to the back-end systems, including the database, are automatically processed asynchronously using a special configurable thread pool. This frees up the Servlet container's threads used to accept client connections, making the web-application more stable and scalable.\n\n* **Utilizes latest Java technologies**\n\n\tThyme is a new framework and it does not have to carry any legacy code to maintain compatibility with older versions. Thyme uses Java 7 and the Servlet API 3.1. At the moment, the latter has not yet been widely implemented. Apache offers Tomcat 8, which is still in its alpha stage and is unstable. Luckily, the framework has been tested on Tomcat 7 as well, and it works out of the box without any problems. When Tomcat 8 is finally released, it should offer significant performance improvements, particularly in the asynchronous, non-blocking request processing utilized by the framework.\n\n* **Practical in development and production environments**\n\n\tThe Thyme framework is the result of years of experience in developing Java web-applications for all kinds of industries and purposes. Boyle Software's core business is consulting, which has exposed us to many different client requirements, infrastructure set-ups, project scales, and modes of operation. Thyme's main goal is to be practical and efficient.\n\n## Project Setup\n\n### Download\n\nYou can download the Thyme framework jar from our [Maven repository](http://www.boylesoftware.com/maven/repo-os/com/boylesoftware/thyme/). It is a single jar that you need to place in your web-application's */WEB-INF/lib* directory. There are two dependencies that you need to download and place there as well: [Apache Commons Logging](http://commons.apache.org/proper/commons-logging/) and [ANTLR Java runtime binary](http://www.antlr.org/download.html).\n\n### Maven\n\nIf your project uses Maven, here is the dependency: (NOTE: Replace the version below - 1.0.0 -  with the latest available.)\n\n```xml\n\u003crepository\u003e\n    \u003cid\u003eboylesoftware-os\u003c/id\u003e\n    \u003curl\u003ehttp://www.boylesoftware.com/maven/repo-os\u003c/url\u003e\n\u003c/repository\u003e\n\n...\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.boylesoftware.thyme\u003c/groupId\u003e\n    \u003cartifactId\u003ethyme\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nYou will also have to provide your application with a JPA and Bean Validation framework implementations. Below are some examples of Maven project configurations. Note that these are only examples. Check for newer versions before use.\n\n#### JPA Implementation\n\n##### Hibernate\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.hibernate\u003c/groupId\u003e\n    \u003cartifactId\u003ehibernate-entitymanager\u003c/artifactId\u003e\n    \u003cversion\u003e4.3.0.Beta3\u003c/version\u003e\n    \u003cscope\u003eruntime\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\nNote that Hibernate supports JPA version 2.1 only starting from version 4.3.0.\n\nSee http://www.hibernate.org/.\n\n##### EclipseLink\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.eclipse.persistence\u003c/groupId\u003e\n    \u003cartifactId\u003eorg.eclipse.persistence.jpa\u003c/artifactId\u003e\n    \u003cversion\u003e2.5.0\u003c/version\u003e\n    \u003cscope\u003eruntime\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\nSee http://www.eclipse.org/eclipselink/.\n\n##### Apache OpenJPA\n\n```xml\n\u003cbuild\u003e\n\n    ...\n\n    \u003cplugins\u003e\n\n        ...\n\n        \u003cplugin\u003e\n            \u003cgroupId\u003eorg.apache.openjpa\u003c/groupId\u003e\n            \u003cartifactId\u003eopenjpa-maven-plugin\u003c/artifactId\u003e\n            \u003cversion\u003e2.2.2\u003c/version\u003e\n            \u003cconfiguration\u003e\n                \u003cincludes\u003e**/entities/*.class\u003c/includes\u003e\n            \u003c/configuration\u003e\n            \u003cexecutions\u003e\n                \u003cexecution\u003e\n                    \u003cphase\u003eprocess-classes\u003c/phase\u003e\n                    \u003cgoals\u003e\n                        \u003cgoal\u003eenhance\u003c/goal\u003e\n                    \u003c/goals\u003e\n                \u003c/execution\u003e\n            \u003c/executions\u003e\n        \u003c/plugin\u003e\n\n        ...\n\n    \u003c/plugins\u003e\n\n    ...\n\n\u003c/build\u003e\n\n...\n\n\u003cdependencies\u003e\n\n    ...\n\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.apache.openjpa\u003c/groupId\u003e\n        \u003cartifactId\u003eopenjpa\u003c/artifactId\u003e\n        \u003cversion\u003e2.2.2\u003c/version\u003e\n        \u003cscope\u003eruntime\u003c/scope\u003e\n    \u003c/dependency\u003e\n\n    ...\n\n\u003c/dependencies\u003e\n```\n\nThis example includes the build time entity enhancement (see http://openjpa.apache.org/entity-enhancement.html).\n\nSee http://openjpa.apache.org/.\n\n#### Bean Validation Implementation\n\n##### Hibernate\n\nA standard implementation is provided by Hibernate. See http://www.hibernate.org/subprojects/validator.html.\n\nHere is a Maven dependency example:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.hibernate\u003c/groupId\u003e\n    \u003cartifactId\u003ehibernate-validator\u003c/artifactId\u003e\n    \u003cversion\u003e5.0.1.Final\u003c/version\u003e\n    \u003cscope\u003eruntime\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\nThis implementation includes some useful, non-standard validation constraints. If your beans use those, change the dependency's scope from \"runtime\" to \"compile.\"\n\n### Servlet Container\n\nA web-application that uses Thyme runs under a Servlet container. Thyme encourages writing your application in such a way that a single, compiled binary of your web-application can be deployed in different environments, such as development, QA, production, etc. This means all the environment-specific configurations must be provided to the application by the container. In the Servlet container's world, the most fitting approach to providing an environment-specific configuration is via JNDI.\n\nThe framework itself uses several configuration entries, some of which are required.\n\n#### Ports\n\nThe framework needs to know to which port(s) the application is listening for requests. The ports are used to generate appropriate URLs for the application pages.\n\n* **httpPort** *(required)*\n\n\tThis is the port through which the application accepts plain HTTP requests.\n\n* **httpsPort** *(required)*\n\n\tThis is the port through which the application accepts secure HTTPS requests.\n\nFirst, the environment's dependencies must be declared in the application's deployment descriptor *web.xml*. For example:\n\n```xml\n\u003cweb-app xmlns=\"http://xmlns.jcp.org/xml/ns/javaee\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd\"\n    version=\"3.1\"\u003e\n\n    ...\n\n    \u003cenv-entry\u003e\n        \u003cenv-entry-name\u003ehttpPort\u003c/env-entry-name\u003e\n        \u003cenv-entry-type\u003ejava.lang.Integer\u003c/env-entry-type\u003e\n        \u003cenv-entry-value\u003eMUST BE SET\u003c/env-entry-value\u003e\n    \u003c/env-entry\u003e\n    \u003cenv-entry\u003e\n        \u003cenv-entry-name\u003ehttpsPort\u003c/env-entry-name\u003e\n        \u003cenv-entry-type\u003ejava.lang.Integer\u003c/env-entry-type\u003e\n        \u003cenv-entry-value\u003eMUST BE SET\u003c/env-entry-value\u003e\n    \u003c/env-entry\u003e\n\n    ...\n\n\u003c/web-app\u003e\n```\n\nNote that we intentionally use invalid values in the *web.xml* to force the deployer to provide the values in the container's configuration.\n\nDifferent containers use different ways to bind values to JNDI environment entries. For example, if you use [Apache Tomcat](http://tomcat.apache.org/), the values can be specified in the application's context XML file:\n\n```xml\n\u003cContext\u003e\n\n    ...\n\n    \u003cEnvironment name=\"httpPort\"\n        value=\"80\"\n        type=\"java.lang.Integer\"\n        override=\"false\"/\u003e\n    \u003cEnvironment name=\"httpsPort\"\n        value=\"443\"\n        type=\"java.lang.Integer\"\n        override=\"false\"/\u003e\n\n    ...\n\n\u003c/Context\u003e\n```\n\nSee http://tomcat.apache.org/tomcat-8.0-doc/config/context.html#Environment_Entries for details.\n\nHere is a similar example for [Jetty](http://www.eclipse.org/jetty/):\n\n```xml\n\u003cConfigure id=\"wac\" class=\"org.eclipse.jetty.webapp.WebAppContext\"\u003e\n\n    ...\n\n    \u003cNew class=\"org.eclipse.jetty.plus.jndi.EnvEntry\"\u003e\n        \u003cArg\u003e\u003cRef refid=\"wac\"/\u003e\u003c/Arg\u003e\n        \u003cArg\u003ehttpPort\u003c/Arg\u003e\n        \u003cArg type=\"java.lang.Integer\"\u003e80\u003c/Arg\u003e\n        \u003cArg type=\"boolean\"\u003etrue\u003c/Arg\u003e\n    \u003c/New\u003e\n    \u003cNew class=\"org.eclipse.jetty.plus.jndi.EnvEntry\"\u003e\n        \u003cArg\u003e\u003cRef refid=\"wac\"/\u003e\u003c/Arg\u003e\n        \u003cArg\u003ehttpsPort\u003c/Arg\u003e\n        \u003cArg type=\"java.lang.Integer\"\u003e443\u003c/Arg\u003e\n        \u003cArg type=\"boolean\"\u003etrue\u003c/Arg\u003e\n    \u003c/New\u003e\n\n    ...\n\n\u003c/Configure\u003e\n```\n\nSee http://www.eclipse.org/jetty/documentation/current/jndi-configuration.html#configuring-jndi-env-entries for details.\n\n#### The Database\n\nThyme uses [JPA 2.1](http://jcp.org/en/jsr/detail?id=338) (currently, [JPA 2.0](http://jcp.org/en/jsr/detail?id=317) is also supported) for back-end database access, which needs to be configured for your application. First, you need to configure the persistence unit by placing the *persistence.xml* file in your application's *META-INF* directory. Here is an example:\n\n```xml\n\u003cpersistence xmlns=\"http://xmlns.jcp.org/xml/ns/persistence\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd\"\n    version=\"2.1\"\u003e\n    \u003cpersistence-unit name=\"pu\" transaction-type=\"RESOURCE_LOCAL\"\u003e\n        \u003cnon-jta-data-source\u003ejava:comp/env/jdbc/myDS\u003c/non-jta-data-source\u003e\n\n        ...\n\n    \u003c/persistence-unit\u003e\n\u003c/persistence\u003e\n```\n\nNote, that the framework manages database transactions on its own and does not use [JTA](http://jcp.org/en/jsr/detail?id=907).\n\nThe example above refers to a JNDI datasource, which also needs to be provided. In the application's *web.xml* we declare the required reference:\n\n```xml\n\u003cresource-ref\u003e\n    \u003cres-ref-name\u003ejdbc/myDS\u003c/res-ref-name\u003e\n    \u003cres-type\u003ejavax.sql.DataSource\u003c/res-type\u003e\n    \u003cres-auth\u003eContainer\u003c/res-auth\u003e\n    \u003cres-sharing-scope\u003eShareable\u003c/res-sharing-scope\u003e\n\u003c/resource-ref\u003e\n```\n\nAnd then the datasource needs to be provided by the container. See http://tomcat.apache.org/tomcat-8.0-doc/jndi-resources-howto.html#JDBC_Data_Sources and http://tomcat.apache.org/tomcat-8.0-doc/jndi-datasource-examples-howto.html for Apache Tomcat configuration and http://www.eclipse.org/jetty/documentation/current/jndi-configuration.html#configuring-datasources for Jetty.\n\n#### JavaMail\n\nIf your application sends e-mails, Thyme can provide it with a [JavaMail](http://jcp.org/en/jsr/detail?id=919) session. This dependency is optional. If your application does not need it, Thyme will work without a JavaMail session configured in the JNDI. Otherwise, first you declare the dependency in the *web.xml*:\n\n```xml\n\u003cresource-ref\u003e\n    \u003cres-ref-name\u003email/session\u003c/res-ref-name\u003e\n    \u003cres-type\u003ejavax.mail.Session\u003c/res-type\u003e\n    \u003cres-auth\u003eContainer\u003c/res-auth\u003e\n\u003c/resource-ref\u003e\n```\n\nAnd then you configure the session in your container. See http://tomcat.apache.org/tomcat-8.0-doc/jndi-resources-howto.html#JavaMail_Sessions for Apache Tomcat and http://www.eclipse.org/jetty/documentation/current/jndi-configuration.html#configuring-mail-with-jndi for Jetty.\n\n#### Development Environment\n\nDuring application development it is important to make it easy to view and test modifications quickly. In the past, Servlet containers were not very good at this. However, these days those issues have been addressed. All modern Servlet container implementations support automatic and manual application reload upon changes in the classes, libraries, and configuration. The JSPs, if used, are recompiled on the fly. In addition to this, containers include some features and tools that allow running your application from your project source tree without fully assembling it each time you change the source. For Apache Tomcat see http://tomcat.apache.org/tomcat-8.0-doc/config/resources.html (or http://tomcat.apache.org/tomcat-7.0-doc/config/context.html#Virtual_webapp for version 7). For Jetty, see the Jetty Maven plugin: http://www.eclipse.org/jetty/documentation/current/maven-and-jetty.html.\n\n## Application Development\n\nIn Thyme, there are four major types of objects that your custom application needs to implement its logic:\n\n* The application class, which represents your custom web-application and is responsible for the application start up, shut down, configuration, and access to the used APIs and services.\n* The JPA entities, which represent the persistent entities your application stores in its databases.\n* User input beans, which represent data entered by users and passed to the controllers for processing. Normally, the data is entered using HTML forms.\n* The controllers, which provide custom application logic behind the resources made available by your application at various URIs. This is where the most of the application logic is implemented.\n\nIn addition to these four objects, the application also includes various configuration files (such as *web.xml* and *persistence.xml*), message resources (such as *ValidationMessages.properties*), and view templates. The view templates may be JSPs, but the framework allows using other view templating technologies, such as [FreeMarker](http://freemarker.org/).\n\n### The Application\n\nThe first step when you develop a Thyme application is to define your custom application class. You do so by extending the abstract `com.boylesoftware.web.AbstractWebApplication` class provided by the framework. The class must be registered as a Servlet context listener either in the *web.xml* or using `@WebListener` annotation:\n\n```java\npackage my.app.web;\n\nimport javax.servlet.annotation.WebListener;\nimport com.boylesoftware.web.AbstractWebApplication;\n\n@WebListener\npublic class MyApplication extends AbstractWebApplication {\n\n    ...\n}\n```\n\nOr in *web.xml*:\n\n```xml\n\u003clistener\u003e\n    \u003clistener-class\u003emy.app.web.MyApplication\u003c/listener-class\u003e\n\u003c/listener\u003e\n```\n\nBeing a Servlet context listener allows the application object to perform the application initialization and shutdown according to the deployed web-application life-cycle. It also allows it to use `@Resource` annotated members to easily get access to any custom configuration objects from the JNDI.\n\nBe aware that Jetty has an issue with injecting resources into listeners that are installed using the `@WebListener` annotation. Unless the listener is installed using *web.xml*, Jetty does not inject resources in the fields annotated with `@Resource`. Our interpretation of this behavior is that it is a bug, so it may be fixed in future versions of Jetty.\n\n#### Initialization and Shutdown\n\n`AbstractWebApplication` offers two extension points to give the application a chance to perform custom initialization and shutdown logic:\n\n* `init()` is called immediately after the framework initialization and before the application starts responding to requests.\n* `destroy()` is called after the framework stops responding to new requests and all pending requests have been processed, but before the framework executes its own shutdown logic.\n\n#### Configuration\n\nThe application object provides configuration for the rest of the application. There are two types of configuration: First, there is configuration used by the application custom code. This configuration is part of the API that the application provides to its controllers. Second, there are configuration properties used to customize the behavior of the framework components. Those properties are part of the framework's SPI for various framework component implementations.\n\n##### Custom Configuration Elements for the Application\n\nSince the application object is easily available anywhere in the application code (such as in the controllers), the best way to provide the application code with additional configuration is to define corresponding \"get\" methods on the application class itself. The initialization of the values behind those \"get\" methods can be performed in the overridden `init()` method.\n\nFor example, let's consider a case where your application needs a secret key for the symmetric encryption of data and the key must be provided by the application's environment. The key can be a JNDI environment entry, represented by a string in hexadecimal encoding. Your custom application class may look like this:\n\n```java\n@WebListener\npublic class MyApplication extends AbstractWebApplication {\n\n    ...\n\n    // the secret key as a hexadecimal string from the JNDI\n    @Resource(name=\"secretKey\")\n    private String secretKeyStr;\n\n    // the secret key\n    private Key secretKey;\n\n    ...\n\n    @Override\n    protected void init() {\n\n        this.secretKey = new SecretKeySpec(Hex.decode(this.secretKeyStr), \"AES\");\n    }\n\n    ...\n\n    /**\n     * Get application secret key for symmetrical cryptography.\n     *\n     * @return The secret key.\n     */\n    public Key getSecretKey() {\n\n        return this.secretKey;\n    }\n\n    ...\n}\n```\n\nThe JNDI environment entry needs to be declared in the application's *web.xml*:\n\n```xml\n\u003cenv-entry\u003e\n    \u003cenv-entry-name\u003esecretKey\u003c/env-entry-name\u003e\n    \u003cenv-entry-type\u003ejava.lang.String\u003c/env-entry-type\u003e\n    \u003cenv-entry-value\u003eMUST BE SET\u003c/env-entry-value\u003e\n\u003c/env-entry\u003e\n```\n\nThis way the `getSecretKey()` method can be called on the `MyApplication` object anywhere that the key is needed.\n\n##### Configuration Properties for the Framework Components\n\nThe configuration for the framework's components must be provided in a different way since the components do not know anything about your custom application subclass. The configuration properties for the components are made available via the `AbstractWebApplication`'s `getConfigProperty()` method. The method is actually a part of the `com.boylesoftware.web.ApplicationConfiguration` interface, which `AbstractWebApplication` implements.\n\nThe configuration properties are identified by property names. Framework components use their own specific property names. Some of the standard property names, however, can be found among string constants declared in the `ApplicationConfiguration` interface. For other properties, you must see the specific components' documentation.\n\nThe place to customize application configuration properties is the `AbstractWebApplication`'s `configure()` method, which can be overridden in the custom subclass. For example, the default implementation of the `AbstractWebApplication`'s `getEntityManagerFactory()` method, which provides access to the JPA persistence unit, uses the `ApplicationConfiguration.PU_NAME` configuration property for the persistence unit name. There is a default value for the persistence unit name, but if the application needs a different name, it can override the `configure()` method this way:\n\n```java\n@WebListener\npublic class MyApplication extends AbstractWebApplication {\n\n    ...\n\n    @Override\n    protected void configure(Map\u003cString, Object\u003e config) {\n\n        config.put(ApplicationConfiguration.PU_NAME, \"MyPersistenceUnit\");\n    }\n\n    ...\n}\n```\n\nThe overridden `configure()` method can also read the configuration from an external file and load it into the provided configuration map.\n\n#### APIs and Services\n\nThe same way the application object provides configuration, it manages and provides access to other APIs and services used by the framework components and application custom code. If the application uses a service, which is not provided by the framework out of the box, it can perform service initialization in the overridden `init()` method, service shutdown in the `destroy()` method, and it can define a public method or methods that give the application code access to the service.\n\nThe application object is also responsible for providing a number of standard services used by the framework. Each such service is initialized by a protected \"get\" method on the `AbstractWebApplication` class. The methods are called during the application initialization before the custom `init()` method is called. Each method has a default implementation, but can be overridden in the custom application subclass. See examples below.\n\n##### Custom Validation Messages Resource Bundle\n\nThe `AbstractWebApplication`'s `getValidatorFactory()` method is responsible for creating the validator factory used, in particular, for user input beans validation. By default, according to the Bean Validation specification, the validation error messages are taken from the *ValidationMessages* resource bundle in the root of the application's classpath. Often, that location is inconvenient and needs to be customized.\n\nThe application can override the `getValidatorFactory()` method and perform the complete validation framework initialization and configuration on its own. However, if only the location of the messages resource bundle needs to be changed, the `getValidatorMessageInterpolator()` method, which is called from the default implementation of the `getValidatorFactory()` method, can be overridden instead. For an application using Hibernate Validator, below is an exzample of how to customize the location of the messages resource bundle:\n\n```java\nimport org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;\nimport org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;\n\n@WebListener\npublic class MyApplication extends AbstractWebApplication {\n\n    ...\n\n    @Override\n    protected MessageInterpolator getValidatorMessageInterpolator(ServletContext sc,\n            ApplicationConfiguration config) {\n\n        return new ResourceBundleMessageInterpolator(\n            new PlatformResourceBundleLocator(\"com.boylesoftware.talkbuilder.resources.Messages\"));\n    }\n\n    ...\n}\n```\n\nThe `ResourceBundleMessageInterpolator` and `PlatformResourceBundleLocator` classes are specific to the Hibernate implementation.\n\n##### Custom User Locale Finder\n\nTo create an internationalized application, you must associate a specific locale with each request so that you can serve the view correctly localized. The framework's `com.boylesoftware.web.spi.UserLocaleFinder` component is responsible for finding the locale for each request.\n\nThe Servlet API gives us the `ServletRequest.getLocale()` method, which determines the locale based on the meta-data provided with the request by the user's browser. The default `UserLocaleFinder` simply uses this method. But what if the user's language is part of the user profile? In that case, your application must provide a custom `UserLocaleFinder` implementation. Here is an example:\n\n```java\n@WebListener\npublic class MyApplication extends AbstractWebApplication {\n\n    ...\n\n    @Override\n    protected UserLocaleFinder\u003cUser\u003e getUserLocaleFinder(ServletContext sc,\n            ApplicationConfiguration config) {\n\n        return new UserLocaleFinder\u003cUser\u003e() {\n\n            @Override\n            public Locale getLocale(HttpServletRequest request, User user) {\n\n                if (user != null) {\n                    String lang = user.getLanguage();\n                    if (lang != null)\n                        return Locale.forLanguageTag(lang);\n                }\n\n                return request.getLocale();\n            }\n        };\n    }\n\n    ...\n}\n```\n\nThe code above assumes that the application uses the entity class `User` to represent user profiles and that the class has the `getLanguage()` method that returns an optional user-preferred language from the profile.\n\n#### User Authentication Service\n\nUser authentication service is used to associate a certain registered user with a request. It is also used to establish and break authenticated sessions that attribute all requests to the same user between the user login and either logout or session expiration. Usually, such association is implemented using HTTP cookies.\n\nThe default implementation of the `AbstractWebApplication`'s `getAuthenticationService()` method returns a `com.boylesoftware.web.impl.auth.SessionlessAuthenticationService` instance, which is the most commonly used authentication service implementation provided by the framework. This implementation does not rely on the Servlet specification's `HttpSession` functionality and is completely stateless, which makes applications that use it able to work in clusters of servers without any special setup such as \"sticky\" sessions. The special encrypted HTTP cookie contains the authenticated user's id, which allows the framework to find the user record each time it receives a request.\n\nThe `SessionlessAuthenticationService` is an authentication service implementation that assumes that the application keeps registered user account records in its database. This type of authentication service implementation is characterized by the `getAuthenticator()` method returning an implementation of the `com.boylesoftware.web.api.UserRecordAuthenticator` interface, which is an extension of the basic `com.boylesoftware.web.api.Authenticator` interface.\n\nUser records are specific to the application, so to access them the user record authentication service uses a custom `com.boylesoftware.web.spi.UserRecordHandler` implementation provided by the application. The `AbstractWebApplication`'s `getUserRecordHandler()` method must be overridden for this to work correctly. Here is an example:\n\n```java\n@WebListener\npublic class MyApplication extends AbstractWebApplication {\n \n    ...\n\n    @Override\n    protected UserRecordHandler\u003cUser\u003e getUserRecordHandler(ServletContext sc,\n            ApplicationConfiguration config) {\n\n        return new AbstractUserRecordHandler\u003cUser\u003e(User.class) {\n\n            @Override\n            public User getUserByLoginNameAndPassword(EntityManager em, String loginName, String password) {\n\n                try {\n\n                    return em\n                        .createNamedQuery(\"User.findByEmailAndPasswordDigest\", User.class)\n                        .setParameter(\"email\", loginName.toLowerCase())\n                        .setParameter(\"passwordDigest\", this.digestPassword(password, \"SHA-1\"))\n                        .getSingleResult();\n\n                } catch (NoResultException e) {\n                    return null;\n                }\n            }\n\n            @Override\n            public int getUserId(User user) {\n\n                return user.getId();\n            }\n\n            @Override\n            public int getUserSalt(User user) {\n\n                return user.getSalt();\n            }\n        };\n    }\n\n    ...\n}\n```\n\nThe example above uses a convenient `AbstractUserRecordHandler` provided by the framework as the base class for the custom user record handler implementation.\n\nThe corresponding user account record class might look like the following:\n\n```java\n@Entity\n@NamedQueries({\n    @NamedQuery(name=\"User.findByEmailAndPasswordDigest\",\n        query=\"SELECT u FROM User u WHERE u.email = :email\" +\n                    \" AND u.passwordDigest = :passwordDigest\")\n})\npublic class User {\n\n\t// user id\n\t@Id\n\t@GeneratedValue\n\tprivate int id;\n\n\t// user secret \"salt\"\n\t@Column(nullable=false)\n\tprivate int salt;\n\n\t// user e-mail address\n\t@Column(length=50, nullable=false, unique=true, updatable=false)\n\tprivate String email;\n\n\t// SHA-1 digest of the user password as a hexadecimal string\n\t@Column(length=40, nullable=false)\n\tprivate String passwordDigest;\n\n\t// other properties, getters and setters\n\t...\n}\n```\n\nThe class above uses user's e-mail address as the login name. The secret \"salt\" field is used for additional security of the encrypted authentication cookie. It is a random number, unknown to the user, associated once with the user account and included in the authentication cookie. Each time the cookie is decrypted, the \"salt\" value is matched against the one associated with the user account.\n\nNote, that `SessionlessAuthenticationService` requires a 128-bit AES secret key in the JNDI under \"java:comp/env/secretKey\". The value must be a string in hexadecimal encoding.\n\nIf the application does not require user authentication, there is a special NOP authentication service implementation included in the framework. It can be used this way:\n\n```java\n@WebListener\npublic class MyApplication extends AbstractWebApplication {\n \n    ...\n\n    @Override\n    protected AuthenticationService\u003c?\u003e getAuthenticationService(ServletContext sc, ApplicationConfiguration config) {\n\n        return new NopAuthenticationService();\n    }\n\n    ...\n}\n```\n\nThis authentication service reports to the rest of the framework that all requests are unauthenticated.\n\n#### User Records Cache\n\nIf the application uses a user record based authentication service, such as the default `SessionlessAuthenticationService`, each time a new request is received the authentication service must look up the corresponding user record in the database. To improve performance, the authentication service can use a cache. The default implementation of `AbstractWebApplication`'s `getUserRecordsCache()` method returns a stub cache implementation that does not do any caching. This is the safest \"cache\" implementation and that is why it is used as the default. There are several other implementations available in the `com.boylesoftware.web.impl.auth` package. Note that as soon as the application moves to a clustered environment, special care must be taken about persistent records caching. Not all cache implementations are suitable for distributed environments since not all implementations provide functionality for synchronizing cache instances.\n\nBecause of the user record caching, the `com.boylesoftware.web.api.Authenticator` API includes methods that invalidate cached user records. Controllers that modify user records, especially data that affects authentication and authorization, must use those methods to notify the cache about the changes.\n\n### Router Configuration\n\nThe router is the core component of Thyme framework: it maps incoming request URIs to the corresponding request processing logic. The idea behind the framework is that there is a *resource* behind each URI and the application provides up to three HTTP methods to work with the resource:\n\n* The \"GET\" method requests a representation of the resource, such as an HTML view.\n* The \"POST\" allows modification of the resource. Normally, there is special object called *user input bean* attached to the \"POST\" request. The user input bean encapsulates data entered by the user, usually using an HTML form on the resource's view. The user input bean is a subject to validation before it can be processed by the controller.\n* The \"DELETE\" method requests to delete the resource. Since HTML forms, unfortunately, do not support the \"DELETE\" method, some applications may choose to consider resource deletion a \"modification\" and use the \"POST\" method. Otherwise, a \"DELETE\" request can be sent from the browser using the `XMLHttpRequest` object.\n\n#### Router Filter\n\nThe router is represented by the `com.boylesoftware.web.RouterFilter` class and installed in the web-application as a filter. Normally, unless your web-application's *web.xml* has the `metadata-complete=\"true\"` attribute on the `web-app` element, the filter is installed automatically and applied to all incoming requests thanks to the `@WebFilter` annotation in the class:\n\n```java\n@WebFilter(\n    filterName=\"RouterFilter\",\n    asyncSupported=true,\n    dispatcherTypes={ DispatcherType.REQUEST, DispatcherType.ASYNC },\n    urlPatterns={ \"/*\" }\n)\npublic class RouterFilter implements Filter {\n\n    ...\n}\n```\n\nSometimes, the custom application may use some other filters and it becomes important in what order those filters are invoked. The annotations do not allow such ordering, so in that case the application needs to define the filter chain in its *web.xml*, which overrides the annotations:\n\n```xml\n\u003cfilter\u003e\n    \u003cfilter-name\u003eRouterFilter\u003c/filter-name\u003e\n    \u003cfilter-class\u003ecom.boylesoftware.web.RouterFilter\u003c/filter-class\u003e\n    \u003casync-supported\u003etrue\u003c/async-supported\u003e\n\u003c/filter\u003e\n\u003cfilter-mapping\u003e\n    \u003cfilter-name\u003eRouterFilter\u003c/filter-name\u003e\n    \u003curl-pattern\u003e/*\u003c/url-pattern\u003e\n    \u003cdispatcher\u003eREQUEST\u003c/dispatcher\u003e\n    \u003cdispatcher\u003eASYNC\u003c/dispatcher\u003e\n\u003c/filter-mapping\u003e\n```\n\nNOTE: It is important that the filter is defined to support asynchronous mode (because it uses it) and that it is associated with two dispatchers: \"REQUEST\" and \"ASYNC.\"\n\nIf the application does not configure a route for a certain URI, the router filter simply passes the request down the chain, which allows it to be transparent while being mapped to all URIs. The filter intercepts only those requests for which it can find a configured route.\n\n#### Route Mappings\n\nA route mapping is a configuration component that maps a specified URI pattern to the request processing logic. The logic is defined as a collection of components of several types which are used during various phases of the request processing. The components include:\n\n* The optional *route script* is a piece of logic executed by the framework each time it receives a matching request. The script is executed in a separate thread used to process the request, and in a JPA transaction which spans the whole request processing iteration. The purpose of the script is to preload referred entities from the database and save them in the request attributes for further use in the controller and the view. This script can also have user permission verification logic.\n* The optional *controller* contains the main request processing logic for those of the three HTTP methods that are applicable. The controller is also executed in the asynchronous request processing thread and within the request processing JPA transaction. The controller may also have an optional view preparation method, which is called whenever the view is about to be sent back to the client. The method may prepare some data needed by the view and place it in the request attributes.\n* The optional *view script* is a piece of logic executed by the framework before it sends the view to the client as a response to a request. The script can be used to prepare any objects used by the view. It is executed in the same asynchronous thread and is included in the transaction.\n* The *view* is used to represent the resource. Normally, the view is a template, such as a JSP. In the Thyme framework, every mapped resource must have a view. The assumption is made that every resource can be displayed this or that way, even if the application does not include the resource's page in its regular collection of pages.\n\nIn addition to the URI pattern and the logic components listed above, a route mapping has a *route id* that identifies the route in the `com.boylesoftware.web.api.Routes` API and allows building URLs for the route.\n\nIt also has a *security mode* that tells if access to the mapped resource requires a secure HTTPS connection or an authenticated user. If it requires an authenticated user and an anonymous request is received for the resource, the framework will automatically redirect the user to the user login page.\n\n#### Router Configuration File\n\nThe router configuration, which consists mostly of route definitions, is provided by `AbstractWebApplication`'s `getRouterConfiguration()` method. The default implementation returns the configuration loaded from the */WEB-INF/routes* file in the web-application. The */WEB-INF/routes* file is a text file that has a special format described here.\n\nThe file contains two types of statements: declarations and route mapping definitions.\n\n##### Minimal Mapping Definition\n\nThe simplest route mapping definition maps a URI pattern to the view:\n\n```\n/home.html =\u003e /WEB-INF/jsp/html/home.jsp\n```\n\nThis mapping instructs the router that if a request is received for URI \"/home.html\" (context-relative), then send the view provided by the JSP in */WEB-INF/jsp/html/home.jsp*.\n\n##### View ID\n\nThe string \"/WEB-INF/jsp/html/home.jsp\" in the example above is actually a view id, interpreted by the framework component called *view sender*, an implementation of the `com.boylesoftware.web.spi.ViewSender` interface. The default router configuration implementation uses view sender returned by `AbstractWebApplication`'s `getViewSender()` method. The `com.boylesoftware.web.impl.view.MultiplexViewSender` returned by default by the `getViewSender()` method is configured to recognize view technology by the view id pattern. It knows that if the view id ends with \".jsp\", it should use `com.boylesoftware.web.impl.view.DispatchViewSender`, which handles JSPs.\n\nOften, view ids start with the same prefix. For example, all application JSPs may reside under */WEB-INF/jsp/html*. Instead of typing the prefix for each mapping, there is a statement that declares a view id prefix:\n\n```\nviewsBase: /WEB-INF/jsp/html/\n\n/home.html =\u003e home.jsp\n```\n\nThe prefix is applied to all subsequent route mappings until another declaration changes it or the end of file is reached.\n\n##### Explicit Route ID\n\nFor the example above, the route id will be automatically generated from the URI pattern. The id will be \"/home.html\". If the URI pattern changes in the future versions of the application, the route id changes too. And if some parts of the application use the route id via the `Routes` API to build URLs to the mapped page, those parts will have to be changed as well. To avoid that, the application can give the route an explicit id:\n\n```\n@homePage\n/home.html =\u003e home.jsp\n```\n\nNow, the home page's route id is \"homePage\" and it will not change if the URI pattern changes.\n\n##### Controller\n\nThe example home page mapping does not associate a controller with the resource. Here is an example of a mapping with a controller:\n\n```\n/password.html\n    com.mycompany.myapp.controllers.PasswordResetRequestController\n    =\u003e password.jsp\n```\n\nAs with the view ids, often controllers reside in a single Java package. Instead of typing it each time, there is a declaration that applies to all subsequent mapping definitions:\n\n```\ncontrollerPackages: com.mycompany.myapp.controllers\n\n/password.html\n    PasswordResetRequestController =\u003e password.jsp\n```\n\nIt is possible to declare multiple controller packages as well:\n\n```\ncontrollerPackages:\n    com.mycompany.myapp.controllers,\n    com.boylesoftware.web.stk\n\n/password.html\n    PasswordResetRequestController =\u003e password.jsp\n```\n\nIn the most cases the whitespace in the routes configuration file includes new lines and is either ignored or used to separate elements in a statement. So, it is not important if elements of a statement are all on one line or on multiple lines. One exception is multi-line declaration statements. The line following a declaration line is attributed to the same declaration only if it starts with some whitespace.\n\n##### Security Mode\n\nThe example mapping above is for a page that lets users request a password reset. The page most likely asks the user to enter sensitive information, such as an e-mail address. The page, therefore, must only be accessible via HTTPS. To declare this, we must add a flag to the mapping:\n\n```\n/password.html +S\n    PasswordResetRequestController =\u003e password.jsp\n```\n\nIf an insecure HTTP request is received for this page, the framework will redirect the client browser to the HTTPS URL.\n\nAnother security mode flag is useful when the mapping requires an authenticated user. For example:\n\n```\n/secure/profile.html +U\n    =\u003e profile.jsp\n```\n\nIf an unauthenticated request is received, the framework will redirect the client browser to the user login page.\n\n##### Protected and Public Pages\n\nUsefully, a group of URIs can be identified as requiring an authenticated user by the URI prefix. For example, in our application we could have all such pages under \"/secure/\". Instead of adding \"+U\" flag to all such mappings, we can use a blanket declaration:\n\n```\nprotectedPages:\n    /secure/.*\n\n/secure/profile.html\n    =\u003e profile.jsp\n```\n\nThe `protectedPages` declaration takes a regular expression for the URIs in question. As opposed to the previously seen `viewsBase` and `controllerPackages` declarations, the `protectedPages` declaration can appear in the configuration file only once and it must come before any mapping definitions.\n\nThe `protectedPages` declaration has a counterpart: the `publicPages` declaration. If both are present, pages matching the protected pages pattern are considered protected unless they also match the public pages pattern. If only the protected pages pattern is specified, all pages are public except those matching the pattern. If only public pages pattern is specified, all pages are protected except those matching the pattern. If neither is specified, all pages are public. The \"+U\" flag specified on a mapping overrides the applicable blanket patterns.\n\n##### Controller Parameters\n\nIn the password reset page example above, the controller instance is created using the default constructor of the `com.mycompany.myapp.controllers.PasswordResetRequestController` class. But sometimes a generic controller needs to be customized. The Thyme Framework allows the passing of certain simple, literal parameters to the controller's constructor. For example, the framework's STK includes a standard implementation of a controller for a user logout page. After successfully logging out, the browser is redirected to a certain application page. Since the controller is generic, it does not know which page the application uses for that purpose. It needs a parameter with the corresponding view id:\n\n```\n/secure/logout.html\n    LogoutController(\"/home.html\") =\u003e logout.jsp\n```\n\nOr we can use the explicit route id for the home page:\n\n```\n/secure/logout.html\n    LogoutController(\"homePage\") =\u003e logout.jsp\n```\n\n##### Login Page\n\nAs mentioned above, if an unauthenticated request is received for a protected page, Thyme responds with a redirect to the user login page. So the framework needs to know the URL of the login page. If the login page is part of the application and there is a route mapping for it, the route mapping can be marked with a flag:\n\n```\n/login.html +L\n    LoginController =\u003e login.jsp\n```\n\nOnly one mapping can be marked with \"+L\" flag.\n\nAlternatively, a declaration can be used:\n\n```\nloginPage: /myapp/login.html\n```\n\nThe declaration can appear in the file only once and it must be before any mapping definition. The \"+L\" flag cannot be used if the declaration is used. The declaration allows the login page to exist outside the application.\n\n##### URI Parameters\n\nMapping URI patterns can have placeholders for the URI parameters. Each parameter is extracted from the URI path and converted to a request parameter. Each parameter placeholder is surrounded with curly braces and contains the name for the corresponding request parameter. Optionally, it can also contain a regular expression for the parameter values. If a regular expression is present, it must follow a colon after the parameter name. The expression must not contain any capturing groups and any curly brace character in it must be strictly balanced. For example:\n\n```\n/secure/posts/{postId}.html\n    =\u003e posts.jsp\n```\n\nA request URI \"/secure/posts/123.html\" will make a request parameter named \"postId\" available with a value of \"123.\"\n\nTo add a regular expression:\n\n```\n/secure/posts/{postId:[1-9][0-9]*|new}.html\n    =\u003e posts.jsp\n```\n\nThe mapping above will match only if the post id is a positive integer number or word \"new.\"\n\n##### Route Script\n\nIt is possible to include certain logic right in the mapping definition. The route script, associated with a mapping, is executed each time the mapping is invoked. It is a good place to verify user permissions and to fetch the referred entities from the database.\n\nHere is an example of a mapping for a blog post page:\n\n```\nentityPackages: com.mycompany.myapp.entities\n\n/secure/posts/{postId:[1-9][0-9]*|new}.html\n    PostController {\n        if (postId == \"new\") {\n            abort if (DELETE)\n            post = new Post\n        } else {\n            post = Post(postId)\n            forbid if (DELETE \u0026 post.author.id != authedUser.id)\n        }\n    } =\u003e post.jsp\n```\n\nThe route script comes after the controller, if any, and before the \"=\u003e\" pointing at the view id. The script is enclosed in curly braces.\n\nThere are three types of statements used in route scripts. All are represented in the example above.\n\n* *Conditional statement:* This allows conditional logic in the script and can have an optional \"else\" clause.\n* *Permission statement:* This stops request processing in certain conditions.\n* *Assignment statement:* This allows the storing of objects in request attributes to make them available for the controller and the view.\n\nLet's have a look at the example above. The first line in the script begins a conditional statement and checks if the URI parameter \"postId\", defined in the mapping's URI pattern, equals \"new\", which means a page for creating a new post is being requested.\n\nThe conditional statement form is:\n\n```\nif (\u003cconditional expression\u003e) { \u003cscript\u003e }\n```\n\nOr:\n\n```\nif (\u003cconditional expression\u003e) { \u003cscript\u003e } else { \u003cscript\u003e }\n```\n\nIf creating a new post, on the second line of the script we check if the request HTTP method is \"DELETE\". We cannot delete a nonexistent post, so the \"abort if\" statement returns the HTTP error code 400: \"Bad Request.\" The \"abort\" statement form is:\n\n```\nabort if (\u003cconditional expression\u003e)\n```\n\nOr, alternatively:\n\n```\nabort unless (\u003cconditional expression\u003e)\n```\n\nThe conditional expressions can use operators \"!\", \"\u0026\" (or \"\u0026\u0026\"), \"|\" (or \"||\"), \"==\", \"!=\", use value expressions described below, or use HTTP method tests \"GET\", \"POST\" or \"DELETE\".\n\nContinuing the script, if the method is not \"DELETE\", we create a new instance of the entity class `Post` and save it in the request attribute named \"post.\" The package for the entity `Post` is taken from a previous `entityPackages` declaration. Alternatively, a fully qualified entity class name can be used.\n\nThis is an assignment statement, which takes the form:\n\n```\n\u003crequest attribute name\u003e = \u003cvalue expression\u003e\n```\n\nThe \"else\" clause that starts on line 4 of the script corresponds to the case when the \"postId\" is an existing post's id, so the page allows for the viewing and editing of an existing post.\n\nFirst, we try to fetch the entity with the provided id and store it in the request attribute named \"post.\" If the entity does not exist, this statement will result in the HTTP error code 404: \"Not Found.\"\n\nSecond, the \"forbid if\" statement makes sure that if the HTTP method is \"DELETE\", the currently authenticated user is the post's author, so nobody else can delete the post but its author. If the method is \"DELETE\" and the user is not the author, the \"forbid if\" statement results in the HTTP error code 403: \"Forbidden.\"\n\nThe \"forbid if\" statement's syntax is similar to that of the \"abort if\" statement:\n\n```\nforbid if (\u003cconditional expression\u003e)\n```\n\nOr:\n\n```\nforbid unless (\u003cconditional expression\u003e)\n```\n\nThe value expressions can refer to request parameters and request attributes by name. For request attributes, nested properties can be accessed using the dot notation. Simple string, number, and Boolean literals can be used. Also, entity expressions can be used. The entity expressions are:\n\n* **New entity**\n\n\t`new \u003centity class\u003e`\n\n\tCreates a new instance of the entity class using the default constructor.\n\n* **Entity by id**\n\n\t`\u003centity class\u003e(\u003cvalue expr\u003e)`\n\n\tFetches the specified entity from the database using the provided value expression's result as the entity id. If entity does not exist, fail with the HTTP error code 404: \"Not Found.\"\n\n* **Entity reference**\n\n\t`ref \u003centity class\u003e(\u003cvalue expr\u003e)`\n\n\tGets entity reference by id.\n\n* **Single entity query**\n\n\t`\u003centity class\u003e:\u003cquery name\u003e(\u003cquery parameters\u003e)`\n\n\tExecutes the specified named query and returns a single entity. If not found, fail with the HTTP error code 404: \"Not Found.\" Query parameters are a comma-separated list of value expressions. If named parameters are used instead of ordinal parameters, each parameter expression can be prefixed with the query parameter name followed by a colon. If query does not have any parameters, the list is empty.\n\n* **Entity list query**\n\n\t```\n\t\u003centity class\u003e:\u003cquery name\u003e(\u003cquery parameters\u003e).list\n\t\u003centity class\u003e:\u003cquery name\u003e(\u003cquery parameters\u003e).firstResult(\u003cvalue expr\u003e).list\n\t\u003centity class\u003e:\u003cquery name\u003e(\u003cquery parameters\u003e).maxResults(\u003cvalue expr\u003e).list\n\t\u003centity class\u003e:\u003cquery name\u003e(\u003cquery parameters\u003e).firstResult(\u003cvalue expr\u003e).maxResults(\u003cvalue expr\u003e).list\n\t```\n\n\tExecutes the query and returns the list of results, which can be empty.\n\nSee the example in the next paragraph.\n\n##### View Script\n\nThe view script is executed each time the view is sent to the client. It is used to fetch additional data used by the view. The view script is specified in the mapping after the view id and has the same syntax as the route script, except the view script does not allow permission statements.\n\nIn the edit post page mapping example from the previous section, what if the page also needs to display the list of the most recent posts from the same author? Here is an example mapping definition:\n\n```\n/secure/posts/{postId:[1-9][0-9]*|new}.html\n    PostController {\n        if (postId == \"new\") {\n            abort if (DELETE)\n            post = new Post\n        } else {\n            post = Post(postId)\n            forbid if (DELETE \u0026 post.author.id != authedUser.id)\n        }\n    }\n    =\u003e post.jsp {\n        posts = Post:Post.findForAuthor(authorId: authedUser.id).maxResults(20).list\n    }\n```\n\nThe script uses a query named \"Post.findForAuthor\". It sets a named query parameter \"authorId\" to the id of the currently authenticated user, made available by the framework in the \"authedUser\" request attribute. It also sets the maximum returned results to 20. The query will return posts, ordered by post date, in descending order.\n\nNote how in this mapping definition the fetching of referred objects is split into two scripts: the route script and the view script. Normally, when a \"POST\" is processed successfully, the user receives a redirect response so that refreshing the page does not cause the transaction resubmission. The route script is executed to obtain the selected post, but there is no need to execute the view script if the response is a redirect. However, if the request is a \"GET\", or if it is a \"POST\" but the submitted form data failed to validate, the page needs to be displayed, and on the page we need to show the list of recent posts. Fetching the posts list is therefore placed in the view script and is executed only if the view is displayed as a result of the request.\n\n### JPA Entities\n\nThe framework does not impose any special requirements on JPA entities used by the application. Thyme uses only standard JPA APIs and does not require any specific JPA implementation. Also, the framework encourages application developers to use JPA entity beans only for a single purpose, making them more straightforward.\n\n### User Input Beans\n\nUser input beans are used to encapsulate data entered by a user and attached to a \"POST\" request. Usually the data comes from an HTML form. The user input bean also specifies the [Bean Validation](http://jcp.org/en/jsr/detail?id=349) constraints used to validate the user input before starting an expensive database transaction and passing the bean to the controller.\n\nEven though it is painful to write Java beans - a clear shortcoming of Java as a language - and every Java web-application developer has felt the temptation to combine entity beans and the beans representing HTML forms, in the Thyme framework the decision has been made to use separate user input beans. Sorry, blame Java and type those silly getters and setters.\n\nFor a simple HTML form like this:\n\n```html\n\u003cform method=\"post\"\u003e\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003clabel for=\"emailInput\"\u003eE-mail:\u003c/label\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003cinput type=\"email\" name=\"email\" id=\"emailInput\"/\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003clabel for=\"passwordInput\"\u003ePassword:\u003c/label\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003cinput type=\"password\" name=\"password\" id=\"passwordInput\"/\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003clabel for=\"rememberMeCheckbox\"\u003eRemember Me?\u003c/label\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003cinput type=\"checkbox\" name=\"rememberMe\" id=\"rememberMeCheckbox\"/\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd colspan=\"2\"\u003e\u003cbutton type=\"submit\"\u003eSubmit\u003c/button\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\u003c/form\u003e\n```\n\nWe could define the following user input bean:\n\n```java\npublic class LoginData {\n\n    // the e-mail address\n    @NotNull(message=\"{error.email.empty}\")\n    @Email(message=\"{error.email.invalid}\")\n    private String email;\n\n    // the password\n    @NoTrim\n    @NotNull(message=\"{error.password.empty}\")\n    private String password;\n\n    // \"remember me\" flag\n    private boolean rememberMe;\n\n    // getters and setters\n    ...\n}\n```\n\nThe framework set user input bean properties from the request parameters with the same names. The framework automatically performs the conversion of string values of the request parameters to the target property types using so called *binders*, which are implementations of the `com.boylesoftware.web.input.Binder` interface. There is a collection of standard binders in the `com.boylesoftware.web.input.binders` package. A custom binder can be applied to a field using the `com.boylesoftware.web.input.Bind` annotation.\n\nSo there are two types of annotations used in user input beans: the validation constraints and annotations related to the binding process. Together with the constraints provided by the validation framework implementation, Thyme adds several frequently used constraints in the `com.boylesoftware.web.input.validation.constraints` package. An example of a binding process related annotation is `com.boylesoftware.web.input.NoTrim` annotation as used in the example above on the \"password\" field. Normally, any input value going to a string user input bean property is trimmed (leading and trailing whitespace characters are removed) and if the resulting string is empty, the property is set to `null`. The password needs to be processed \"as is\" so we mark it with a `@NoTrim` annotation so that the trimming logic is not applied to it.\n\nAdditional user input bean validation can be performed in the controller. However, it is better to perform as much validation as possible using bean validation constraints, because the automatic validation is performed before the control is passed to the controller. If the route does not have either the route or the view script, and the user input is invalid, then there is no need for the framework to start an expensive JPA transaction, because the controller does not get called before the view is re-displayed with the appropriate validation error messages. There are cases, however, when user input validation requires access to the database. Those checks must be made in the controller.\n\n### Controllers\n\nControllers are where the main request processing logic for a resource happens. Any class can be a controller. For Thyme to use it as a controller, it does not have to extend or implement anything.\n\n#### Controller Methods\n\nFrom the framework's point of view, a controller can define up to four methods: three for the three HTTP methods - \"GET\", \"POST\", and \"DELETE\" - and one for additional view preparation logic. The methods are discovered and called by the framework via reflection and require certain names and return types to be properly identified. Here are the methods:\n\nMethod Name     | Method Return Type | Description\n----------------|--------------------|------------\n**get**         | void               | \u003cp\u003eProcesses an HTTP \"GET\" request. After the method successfully returns, the view associated with the route is sent back to the client in a 200 \"OK\" HTTP response body. This method rarely needs to be implemented, as all the view preparation logic can be defined in the route and view scripts as well as in the `prepareView` controller method (see below). If the method is undefined in a controller, \"GET\" requests are processed without calling the controller.\u003c/p\u003e\n**post**        | java.lang.String   | \u003cp\u003eProcesses an HTTP \"POST\" request. The framework assumes that if the \"POST\" was processed successfully, the response sent back to the client is a redirect response (the framework sends a 303 \"See Other\" response). The method must return the redirect URI for the response's \"Location\" header. It can be a server-root relative URL starting with a \"/\" or an absolute URL.\u003c/p\u003e\u003cp\u003eThe `post` controller method is the only method that allows a user input bean as one of its arguments (see about controller method arguments below). If the method needs to perform an in-transaction user input validation and the user input is invalid, the method can return `null` instead of a redirect URL and the framework will re-display the route's view in response (with the HTTP status code 400: \"Bad Request\").\u003c/p\u003e\u003cp\u003eIf the method is undefined, any \"POST\" request to the mapped resource will result in a 405 \"Method Not Allowed\" response.\u003c/p\u003e\n**delete**      | java.lang.String   | \u003cp\u003eProcesses an HTTP \"DELETE\" request. As with the `post` method, this method returns a redirect URL upon success, or `null` to re-display the view with a 400 \"Bad Request\" response. Unlike the `post` method, `delete` does not allow a user input bean to be used as an argument.\u003c/p\u003e\u003cp\u003eAs with `post`, if the method is undefined, any \"DELETE\" request to the mapped resource will result in a 405 \"Method Not Allowed\" response.\u003c/p\u003e\n**prepareView** | void               | \u003cp\u003eContains additional view preparation logic that could not be expressed in the view script. The method is called each time before the route's view needs to be sent back to the client (with either 200 or 400 HTTP response). If both the view script is defined for the route and the route's controller has a `prepareView` method, the `prepareView` method is called after the view script.\u003c/p\u003e\n\nEach method is called inside the request processing JPA transaction and is handled by an asynchronous request processing thread. The transaction and the thread are the same one used for the scripts.\n\n#### Controller Method Arguments\n\nAny controller method can have a number of arguments created and passed to it by the framework. Which arguments - and their order - is determined by the controller's needs and is irrelevant from the framework's perspective. The framework determines the meaning of each controller argument using the argument's type and/or using special annotations. The configured `com.boylesoftware.web.spi.ControllerMethodArgHandlerProvider` is responsible for this logic. Out of the box, as implemented by `com.boylesoftware.web.impl.StandardControllerMethodArgHandlerProvider`, the following argument types are supported:\n\n* **The HTTP Request**\n\n\tThis argument's type must be `javax.servlet.http.HttpServletRequest`.\n\n* **The HTTP Response**\n\n\tThis argument's type must be `javax.servlet.http.HttpServletResponse`.\n\n* **The Application Object**\n\n\tThis argument's type must be `com.boylesoftware.web.AbstractWebApplication` or its subclass. This argument is used, for example, to make additional application-specific configurations and services available to the controller. The configuration and services access methods can be defined in the custom application extension class.\n\n* **JPA Entity Manager**\n\n\tThis argument's type must be `javax.persistence.EntityManager`; it is the entity manager used to access the database. The transaction is already made active by the time the controller is called. The transaction is automatically committed by the framework if the controller method successfully returns, or rolled back if the method throws an exception.\n\n* **The Authenticator**\n\n\tThis argument's type must be `com.boylesoftware.web.api.Authenticator` or its implementation. This is the user authentication API for the controller. Controllers dealing with user authentication sessions, such as processing user login and logout, need this API.\n\n* **User Locale**\n\n\tThis argument's type must be `java.util.Locale`; it is the locale as determined by the configured `com.boylesoftware.web.spi.UserLocaleFinder`. Used for localization and internationalization purposes.\n\n* **Flash Attributes**\n\n\tThis argument's type must be `com.boylesoftware.web.api.FlashAttributes`; it is the API for \"flash\" attributes. The controller may set attributes in this object and the attributes will be automatically converted to request attributes with the same names and values for the next HTTP request from the same client. This is useful when the controller method causes a redirect response, but the target page needs to know about the results of the just completed transaction - for example, to display a message to the user.\n\n\tThe flash attribute values must be as short as possible since all the names and values of all the flash attributes are temporarily stored on the client side as an HTTP cookie. In the example of a message, it is better to set a short code as the flash attribute value and decode it to the corresponding message in the next request's view.\n\n* **Routes API**\n\n\tThis argument's type must be `com.boylesoftware.web.api.Routes`. This API allows the controller to lookup specific route URIs, so that it can send them back for the redirect response. This API is rarely used directly. Usually an argument with `@RouteURI` annotation is used for that purpose (see next item).\n\n* **Route URI**\n\n\tThis argument's type must be either `java.lang.String` or `com.boylesoftware.web.api.RouteURIBuilder` and the argument must have a `com.boylesoftware.web.api.RouteURI` annotation. This is a convenient way to have the framework lookup a route URI for the controller using the `Routes` API. The `String` argument is used for routes without URI parameters, and a `RouteURIBuilder` argument allows for working with parameterized route URIs.\n\n* **JavaMail Session**\n\n\tThis argument's type must be `javax.mail.Session`. It is used by a controller if it needs to send e-mails. Note that the session must be configured in the JNDI.\n\n* **Request Parameter**\n\n\tThis argument's type must be either `java.lang.String` or an array of `java.lang.String`s. The argument must have a `com.boylesoftware.web.api.RequestParam` annotation.\n\n* **Model Component**\n\n\tThe type of this argument can be any but it must have a `com.boylesoftware.web.api.Model` annotation. The value for the argument is taken from the corresponding request attribute. Normally, the model component has been put into a request attribute by the route script. This is the way to fetch and pass referred objects to the controller and the view.\n\n* **User Input Bean**\n\n\tThe type of this argument can be any but it must have a `com.boylesoftware.web.api.UserInput` annotation. Only the `post` method is allowed to have a user input bean argument. The bean is validated by Thyme before passing it to the controller. If the bean is invalid, the controller is not called and the framework re-sends the route's view with a 400 \"Bad Request\" HTTP response automatically. The controller may perform additional in-transaction validation, or any other type of validation that cannot be expressed via validation constraint annotations, and return `null` if the bean is invalid. NOTE: Only one user input bean argument is allowed.\n\n* **User Input Validation Errors**\n\n\tThis argument's type must be `com.boylesoftware.web.api.UserInputErrors`; it is useful as a `post` method that performs in-transaction user input bean validation. It allows for the addition of error messages to the view, displayed as a result of the controller method's returning `null`. For the view, the user input validation errors object is made available in the `Attributes.USER_INPUT_ERRORS`, or \"userInputErrors\" request attribute.\n\nNOTE: It is an error to specify an unsupported argument for a controller method.\n\n#### Throwing Exceptions\n\nA controller method can throw an exception. Usually this results in a 500 \"Internal Server Error\" response being sent back to the client. However, Thyme provides a collection of exceptions that extend the `com.boylesoftware.web.RequestedResourceException` abstract class. These exceptions allow for the sending of other HTTP error codes in case of errors. The codes include 400: \"Bad Request,\" 403: \"Forbidden,\" 405: \"Method Not Allowed,\" 404: \"Not Found,\" and 503: \"Service Unavailable.\"\n\n#### Controller Example\n\nLet's have a look at a controller behind a blog post page. The controller allows posting a new post, editing an existing post, and deleting a post. First, here is the mapping:\n\n```java\nthis.addRoute(sc,\n    \"postDetails\",\n    \"/secure/posts/{postId:[1-9][0-9]*|new}.html\",\n    SecurityMode.DEFAULT,\n    new Script() {        // route script, fetch the referred post from the database\n        @Override\n        public void execute(HttpServletRequest request, EntityManager em) {\n\n            String postId = request.getParameter(\"postId\");\n            Post post = (postId.equals(\"new\") ? new Post() :\n                    em.find(Post.class, Integer.valueOf(postId)));\n            request.setAttribute(\"post\", post);\n        }\n    },\n    new PostController(), // associate the controller with the route\n    \"/WEB-INF/jsp/html/post.jsp\",\n    null\n);\n\n// the posts list page mapping, the post controller sometimes redirects to it\nthis.addRoute(sc,\n    \"postsList\",\n    ...\n);\n```\n\nThe route script prepares the post entity bean by either fetching it from the database or by creating a new bean, and it puts in the request attribute named \"post.\" This is the model component.\n\nThe post user input bean could look this way:\n\n```java\npublic class PostData {\n\n    // post message.\n    @NotNull(message=\"{error.message.empty}\")\n    private String message;\n\n    // getters and setters\n    ...\n}\n```\n\nAnd the post entity bean might look like this:\n\n```java\n@Entity\npublic class Post {\n\n    // post id\n    @Id\n    @GeneratedValue\n    private int id;\n\n    // author of the post\n    @ManyToOne\n    @JoinColumn(nullable=false, updatable=false)\n    private User author;\n\n    // date when the post was created\n    @Temporal(TemporalType.TIMESTAMP)\n    @Column(nullable=false, updatable=false)\n    private Date postedOn;\n\n    // the message\n    @Lob\n    @Column(nullable=false)\n    private String message;\n\n    // getters and setters\n    ...\n}\n```\n\nNow, let's have a look at the post controller:\n\n```java\npublic class PostController {\n\n    /**\n     * Process POST. Update existing or save new post.\n     *\n     * @param postData Post data.\n     * @param em Entity manager.\n     * @param post The post.\n     * @param user Authenticated user.\n     * @param nextURI Posts page URI.\n     *\n     * @return URI of the page to redirect upon successful submission.\n     */\n    String post(\n        @UserInput PostData postData,\n        EntityManager em,\n        @Model(\"post\") Post post,\n        @Model(Attributes.AUTHED_USER) User user,\n        @RouteURI(\"postsList\") String nextURI) {\n\n        post.setMessage(postData.getMessage());\n\n        if (post.getId() == 0) { // new unsaved post has id 0\n            post.setAuthor(em.getReference(User.class, Integer.valueOf(user.getId())));\n            post.setPostedOn(new Date());\n            em.persist(post);\n        }\n\n        return nextURI;\n    }\n\n    /**\n     * Process DELETE. Delete the post.\n     *\n     * @param em Entity manager.\n     * @param post The post.\n     * @param flash Flash attributes.\n     * @param nextURI Posts page URI.\n     *\n     * @return URI of the page to redirect upon successful submission.\n     */\n    String delete(\n        EntityManager em,\n        @Model(\"post\") Post post,\n        FlashAttributes flash,\n        @RouteURI(\"postsList\") String nextURI) {\n\n        em.remove(post);\n\n        flash.setAttribute(\"message\", \"{message.post.deleted}\");\n\n        return nextURI;\n    }\n}\n```\n\nThe controller does not need to define a `get` method. There is no special logic for processing a \"GET\" and the view can display the post data using the request attribute \"post\" stored in the request by the route script.\n\nNotice how the `post` method refers to a model component named `Attributes.AUTHED_USER` (or \"authedUser\"). For every request that has an authenticated user, Thyme puts the authenticated user object in this request attribute. The object is returned by the authentication service and, even when represented by an entity bean, it is not associated with the entity manager and the transaction is passed to the controller. This is because the authentication service may have fetched the user record from a cache instead of loading it from the database. Whenever the authentication service needs to load a user record from the database, it is performed in a separate transaction and on a separate thread before proceeding with the rest of the request processing logic. If a controller needs an in-transaction, currently-authenticated user record, it can be re-fetched in the route script.\n\n### Standard Toolkit (STK)\n\nThe `com.boylesoftware.web.stk` package contains a collection of controllers and user input bean implementations for typical use-cases. The application can use them instead of defining its own custom versions.\n\n### Views\n\nThe `com.boylesoftware.web.impl.view.DispatchViewSenderProvider` can be used for JSP-based views. The framework provides a simple JSP tag library to assist in working with HTML forms and accessing other relevant functionality.\n\n#### HTML Form JSP Tags\n\nThe tag library's URI is \"http://www.boylesoftware.com/jsp/thyme\". Here is an example of a simple user profile form:\n\n```jsp\n...\n\n\u003c!-- Import tab libraries --\u003e\n\u003c%@taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\" %\u003e\n\u003c%@taglib prefix=\"t\" uri=\"http://www.boylesoftware.com/jsp/thyme\" %\u003e\n\n...\n\n\u003c!-- Display user input validation errors --\u003e\n\u003cc:if test=\"${!empty userInputErrors}\"\u003e\n\u003cul\u003e\n    \u003cc:forEach items=\"${userInputErrors}\" var=\"error\"\u003e\n    \u003cli\u003e\u003cc:out value=\"${error.fieldName}: ${error.message}\"/\u003e\u003c/li\u003e\n    \u003c/c:forEach\u003e\n\u003c/ul\u003e\n\u003c/c:if\u003e\n\n...\n\n\u003c!-- The form --\u003e\n\u003ct:form id=\"profileForm\" bean=\"${user}\" focus=\"firstName\"\u003e\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003ct:label name=\"firstName\"\u003eFirst Name:\u003c/t:label\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ct:input type=\"text\" name=\"firstName\"/\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003ct:label name=\"lastName\"\u003eLast Name:\u003c/t:label\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ct:input type=\"text\" name=\"lastName\"/\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003ct:label name=\"password\"\u003eNew Password:\u003c/t:label\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ct:input type=\"password\" name=\"password\" bean=\"none\"/\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003ct:label name=\"password2\"\u003eConfirm New Password:\u003c/t:label\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ct:input type=\"password\" name=\"password2\" bean=\"none\"/\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd colspan=\"2\"\u003e\u003cbutton type=\"submit\"\u003eSubmit\u003c/button\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\u003c/t:form\u003e\n\n...\n```\n\nThe `bean` attribute on the form allows it to specify an entity bean which is ultimately - through the user input bean and the controller - behind the form. This helps the JSP tags to use the correct value restricting attributes, such as `maxlength` and `required` on the generated HTML `input` elements. The `t:input` tag can override the form's `bean` attribute and can have its own, plus a `beanField` attribute to associate it with a bean field, which has a name different from the input field's name.\n\nThe `bean` attribute can take the bean, or it can be a `java.lang.String`, in which case it is interpreted as the bean class name. For an input field, it also can have a special value of \"none,\" which disassociates the field from any entity bean property. This is used in the example above for the password inputs; the user bean does not have a password property - it has a password digest property.\n\nThe names of the input fields must be the same as the corresponding property names of the user input bean.\n\nThe `focus` attribute of the form tag allows the framework to generate HTML so that the focus is automatically set to the specified input field when the form is displayed. In case there are user input validation errors, the focus will be set to the first invalid field instead the one specified by the `focus` attribute.\n\n#### Page Links\n\nThe tag library also provides functions used to generate links to other application pages using route ids. These functions are a facade for the `com.boylesoftware.web.api.Routes` API. See `com.boylesoftware.web.jsp.Functions` for the function definitions.\n\n## Additional Documentation\n\n* [API Reference](http://www.boylesoftware.com/thyme/site/apidocs)\n* [Maven Generated Project Information](http://www.boylesoftware.com/thyme/site)\n* [Project on GitHub](https://github.com/boylesoftware/thyme)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboylesoftware%2Fthyme","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fboylesoftware%2Fthyme","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboylesoftware%2Fthyme/lists"}