Anonymous avatar Anonymous committed 3363ade

Version 0.1

Basic integration between grails-activiti-plugin 5.7 and shiro-grails 1.1.3
Service to attach username to session (required to activiti)

Comments (0)

Files changed (30)

+\.orig$
+\.orig\..*$
+\.chg\..*$
+\.rej$
+\.conflict\~$
+^target$

ActivitiShiroGrailsPlugin.groovy

+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH
+
+/**
+ *
+ * @author nickman
+ */
+class ActivitiShiroGrailsPlugin {
+
+    def version = 0.1
+    def grailsVersion = "1.3.5  > *"
+    // the other plugins this plugin depends on
+    def dependsOn = [shiro: '1.1.3 > *', activiti: '5.5 > *']
+    def author = "Nicolas Bohorquez Gutierrez (@nickmancoi)"
+    def authorEmail = "nicolas.bg@gmail.com"
+    def title = "This plugin integrates Shiro Security to Activiti."
+    def description = '''
+        Activiti plugin brings the power of bpmn to grails, Shiro security enables an elegant way to perform 
+authentication and authorization in your app, this plugin provides the basic glue necessary between them
+'''
+    def documentation = "https://bitbucket.org/Nickmancol/grails-activiti-shiro-security-plugin"
+    
+    def doWithSpring = {
+		def disabledActiviti = System.getProperty("disabledActiviti")
+		
+		if (!disabledActiviti && !CH.config.activiti.disabled) {
+			println "Activiti Process Engine with Shiro Security Initialization ..."
+			userManagerFactory(org.grails.activiti.shirosecurity.ShiroSecurityUserManagerFactory)
+			groupManagerFactory(org.grails.activiti.shirosecurity.ShiroSecurityGroupManagerFactory)
+			processEngineConfiguration(org.activiti.spring.SpringProcessEngineConfiguration) {
+				processEngineName = CH.config.activiti.processEngineName?:org.grails.activiti.ActivitiConstants.DEFAULT_PROCESS_ENGINE_NAME
+				databaseType = CH.config.activiti.databaseType?:org.grails.activiti.ActivitiConstants.DEFAULT_DATABASE_TYPE
+				databaseSchemaUpdate = CH.config.activiti.databaseSchemaUpdate ? CH.config.activiti.databaseSchemaUpdate.toString() : org.grails.activiti.ActivitiConstants.DEFAULT_DATABASE_SCHEMA_UPDATE
+				deploymentName = CH.config.activiti.deploymentName?:org.grails.activiti.ActivitiConstants.DEFAULT_DEPLOYMENT_NAME
+				deploymentResources = CH.config.activiti.deploymentResources?:org.grails.activiti.ActivitiConstants.DEFAULT_DEPLOYMENT_RESOURCES
+				jobExecutorActivate = CH.config.activiti.jobExecutorActivate?:org.grails.activiti.ActivitiConstants.DEFAULT_JOB_EXECUTOR_ACTIVATE
+				history = CH.config.activiti.history?:org.grails.activiti.ActivitiConstants.DEFAULT_HISTORY
+				mailServerHost = CH.config.activiti.mailServerHost?:org.grails.activiti.ActivitiConstants.DEFAULT_MAIL_SERVER_HOST
+				mailServerPort = CH.config.activiti.mailServerPort?:org.grails.activiti.ActivitiConstants.DEFAULT_MAIL_SERVER_PORT
+				mailServerUsername = CH.config.activiti.mailServerUsername
+				mailServerPassword = CH.config.activiti.mailServerPassword
+				mailServerDefaultFrom = CH.config.activiti.mailServerDefaultFrom?:org.grails.activiti.ActivitiConstants.DEFAULT_MAIL_SERVER_FROM
+				customSessionFactories = [ref("userManagerFactory"), ref("groupManagerFactory")]
+				dataSource = ref("dataSource")
+				transactionManager = ref("transactionManager")
+			}
+			
+			processEngine(org.activiti.spring.ProcessEngineFactoryBean) { processEngineConfiguration = ref("processEngineConfiguration") }
+			
+			runtimeService(processEngine:"getRuntimeService")
+			repositoryService(processEngine:"getRepositoryService")
+			taskService(processEngine:"getTaskService")
+			managementService(processEngine:"getManagementService")
+			identityService(processEngine:"getIdentityService")
+			historyService(processEngine:"getHistoryService")
+			formService(processEngine:"getFormService")
+			
+			activitiService(org.grails.activiti.ActivitiService) {
+				runtimeService = ref("runtimeService")
+				taskService = ref("taskService")
+				identityService = ref("identityService")
+				formService = ref("formService")
+			}
+		}
+	}
+    
+}
+

application.properties

+#Grails Metadata file
+#Tue Nov 29 07:28:27 COT 2011
+app.grails.version=1.3.7
+app.name=activiti-shiro-plugin
+app.servlet.version=2.4
+app.version=0.1
+plugins.activiti=5.7
+plugins.hibernate=1.3.7
+plugins.shiro=1.1.3
+plugins.tomcat=1.3.7
Add a comment to this file

grails-activiti-shiro-0.1.zip

Binary file added.

grails-app/conf/BootStrap.groovy

+import org.apache.shiro.SecurityUtils
+
+class BootStrap {
+
+    def shiroAutenticationSuccessListener
+
+    def init = { servletContext ->
+    }
+    def destroy = {
+    }
+}

grails-app/conf/BuildConfig.groovy

+grails.project.class.dir = "target/classes"
+grails.project.test.class.dir = "target/test-classes"
+grails.project.test.reports.dir = "target/test-reports"
+//grails.project.war.file = "target/${appName}-${appVersion}.war"
+grails.project.dependency.resolution = {
+    // inherit Grails' default dependencies
+    inherits("global") {
+        // uncomment to disable ehcache
+        // excludes 'ehcache'
+    }
+    log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
+    repositories {
+        grailsPlugins()
+        grailsHome()
+        grailsCentral()
+
+        // uncomment the below to enable remote dependency resolution
+        // from public Maven repositories
+        //mavenLocal()
+        //mavenCentral()
+        //mavenRepo "http://snapshots.repository.codehaus.org"
+        //mavenRepo "http://repository.codehaus.org"
+        //mavenRepo "http://download.java.net/maven/2/"
+        //mavenRepo "http://repository.jboss.com/maven2/"
+    }
+    dependencies {
+        // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
+
+        // runtime 'mysql:mysql-connector-java:5.1.13'
+    }
+}

grails-app/conf/Config.groovy

+// locations to search for config files that get merged into the main config
+// config files can either be Java properties files or ConfigSlurper scripts
+
+// grails.config.locations = [ "classpath:${appName}-config.properties",
+//                             "classpath:${appName}-config.groovy",
+//                             "file:${userHome}/.grails/${appName}-config.properties",
+//                             "file:${userHome}/.grails/${appName}-config.groovy"]
+
+// if(System.properties["${appName}.config.location"]) {
+//    grails.config.locations << "file:" + System.properties["${appName}.config.location"]
+// }
+
+/*
+securityConfig.userLookup.usernamePropertyName = 'username'
+userLookup.userDomainClassName = 'ShiroUser'
+userLookup.authorityJoinClassName = 'UserRole'
+securityConfig.authority.className = 'ShiroRole'
+*/
+
+grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination
+grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format
+grails.mime.use.accept.header = false
+grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
+                      xml: ['text/xml', 'application/xml'],
+                      text: 'text/plain',
+                      js: 'text/javascript',
+                      rss: 'application/rss+xml',
+                      atom: 'application/atom+xml',
+                      css: 'text/css',
+                      csv: 'text/csv',
+                      all: '*/*',
+                      json: ['application/json','text/json'],
+                      form: 'application/x-www-form-urlencoded',
+                      multipartForm: 'multipart/form-data'
+                    ]
+
+// URL Mapping Cache Max Size, defaults to 5000
+//grails.urlmapping.cache.maxsize = 1000
+
+// The default codec used to encode data with ${}
+grails.views.default.codec = "none" // none, html, base64
+grails.views.gsp.encoding = "UTF-8"
+grails.converters.encoding = "UTF-8"
+// enable Sitemesh preprocessing of GSP pages
+grails.views.gsp.sitemesh.preprocess = true
+// scaffolding templates configuration
+grails.scaffolding.templates.domainSuffix = 'Instance'
+
+// Set to false to use the new Grails 1.2 JSONBuilder in the render method
+grails.json.legacy.builder = false
+// enabled native2ascii conversion of i18n properties files
+grails.enable.native2ascii = true
+// whether to install the java.util.logging bridge for sl4j. Disable for AppEngine!
+grails.logging.jul.usebridge = true
+// packages to include in Spring bean scanning
+grails.spring.bean.packages = []
+
+// request parameters to mask when logging exceptions
+grails.exceptionresolver.params.exclude = ['password']
+
+// set per-environment serverURL stem for creating absolute links
+environments {
+    production {
+        grails.serverURL = "http://www.changeme.com"
+    }
+    development {
+        grails.serverURL = "http://localhost:8080/${appName}"
+    }
+    test {
+        grails.serverURL = "http://localhost:8080/${appName}"
+    }
+
+}
+
+// log4j configuration
+log4j = {
+    // Example of changing the log pattern for the default console
+    // appender:
+    //
+    //appenders {
+    //    console name:'stdout', layout:pattern(conversionPattern: '%c{2} %m%n')
+    //}
+
+    error  'org.codehaus.groovy.grails.web.servlet',  //  controllers
+           'org.codehaus.groovy.grails.web.pages', //  GSP
+           'org.codehaus.groovy.grails.web.sitemesh', //  layouts
+           'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
+           'org.codehaus.groovy.grails.web.mapping', // URL mapping
+           'org.codehaus.groovy.grails.commons', // core / classloading
+           'org.codehaus.groovy.grails.plugins', // plugins
+           'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
+           'org.springframework',
+           'org.hibernate',
+           'net.sf.ehcache.hibernate'
+
+    warn   'org.mortbay.log'
+}
+
+// Added by the Grails Activiti plugin:
+activiti {
+    processEngineName = "activiti-engine-default"
+	  databaseType = "h2" 
+	  deploymentName = appName
+	  deploymentResources = ["file:./grails-app/conf/**/*.bpmn*.xml", 
+	                         "file:./grails-app/conf/**/*.png", 
+	                         "file:./src/taskforms/**/*.form"]
+	  jobExecutorActivate = false
+	  mailServerHost = "smtp.yourserver.com"
+	  mailServerPort = "25"
+	  mailServerUsername = ""
+	  mailServerPassword = ""
+	  mailServerDefaultFrom = "username@yourserver.com"
+	  history = "audit" // "none", "activity", "audit" or "full"
+	  sessionUsernameKey = "username"
+	  useFormKey = true
+}
+
+environments {
+    development {
+        activiti {
+			  processEngineName = "activiti-engine-dev"
+			  databaseSchemaUpdate = true // true, false or "create-drop"	  
+        }
+    }
+    test {
+        activiti {
+			  processEngineName = "activiti-engine-test"
+			  databaseSchemaUpdate = true
+	      mailServerPort = "5025"			  
+        }
+    }	
+    production {
+        activiti {
+			  processEngineName = "activiti-engine-prod"
+			  databaseSchemaUpdate = false
+			  jobExecutorActivate = true
+        }
+    }
+}	
+

grails-app/conf/DataSource.groovy

+dataSource {
+    pooled = true
+    driverClassName = "org.hsqldb.jdbcDriver"
+    username = "sa"
+    password = ""
+}
+hibernate {
+    cache.use_second_level_cache = true
+    cache.use_query_cache = true
+    cache.provider_class = 'net.sf.ehcache.hibernate.EhCacheProvider'
+}
+// environment specific settings
+environments {
+    development {
+        dataSource {
+            dbCreate = "create-drop" // one of 'create', 'create-drop','update'
+            url = "jdbc:hsqldb:mem:devDB"
+        }
+    }
+    test {
+        dataSource {
+            dbCreate = "update"
+            url = "jdbc:hsqldb:mem:testDb"
+        }
+    }
+    production {
+        dataSource {
+            dbCreate = "update"
+            url = "jdbc:hsqldb:file:prodDb;shutdown=true"
+        }
+    }
+}

grails-app/conf/UrlMappings.groovy

+class UrlMappings {
+
+	static mappings = {
+		"/$controller/$action?/$id?"{
+			constraints {
+				// apply constraints here
+			}
+		}
+
+		"/"(view:"/index")
+		"500"(view:'/error')
+	}
+}

grails-app/conf/org/grails/activiti/shirosecurity/SetAuthenticatedUsernameFilter.groovy

+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.grails.activiti.shirosecurity
+
+import org.apache.shiro.SecurityUtils
+
+/**
+ *
+ * @author nickman
+ */
+class SetAuthenticatedUsernameFilter {
+	def identityService
+	
+	def filters = {
+		all(controller:'*', action:'*') {
+			before = {
+				if (SecurityUtils.subject && identityService) {
+					identityService.authenticatedUserId = SecurityUtils.subject.principal
+				}
+			}
+			after = { 
+				identityService?.authenticatedUserId = null 
+			}
+			afterView = {
+			}
+		}
+	}
+}
+

grails-app/conf/spring/resources.groovy

+// Place your Spring DSL code here
+beans = {
+}

grails-app/i18n/shiro.properties

+login.failed = Invalid username and/or password

grails-app/services/org/grails/activiti/shirosecurity/ShiroActivitiSessionService.groovy

+package org.grails.activiti.shirosecurity
+
+import org.apache.shiro.SecurityUtils
+import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH
+
+class ShiroActivitiSessionService {
+
+    /**
+     * Attaches the username of the logged user into the session
+     **/
+    def attachUsername2Session() {
+        def sessionUsernameKey = CH.config.activiti.sessionUsernameKey?:org.grails.activiti.ActivitiConstants.DEFAULT_SESSION_USERNAME_KEY
+         SecurityUtils.subject.session.setAttribute(sessionUsernameKey , SecurityUtils.subject?.principal)
+    }
+}

grails-app/views/auth/login.gsp

+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta name="layout" content="main" />
+  <title>Login</title>
+</head>
+<body>
+  <g:if test="${flash.message}">
+    <div class="message">${flash.message}</div>
+  </g:if>
+  <g:form action="signIn">
+    <input type="hidden" name="targetUri" value="${targetUri}" />
+    <table>
+      <tbody>
+        <tr>
+          <td>Username:</td>
+          <td><input type="text" name="username" value="${username}" /></td>
+        </tr>
+        <tr>
+          <td>Password:</td>
+          <td><input type="password" name="password" value="" /></td>
+        </tr>
+        <tr>
+          <td>Remember me?:</td>
+          <td><g:checkBox name="rememberMe" value="${rememberMe}" /></td>
+        </tr>
+        <tr>
+          <td />
+          <td><input type="submit" value="Sign in" /></td>
+        </tr>
+      </tbody>
+    </table>
+  </g:form>
+</body>
+</html>

grails-app/views/error.gsp

+<html>
+  <head>
+	  <title>Grails Runtime Exception</title>
+	  <style type="text/css">
+	  		.message {
+	  			border: 1px solid black;
+	  			padding: 5px;
+	  			background-color:#E9E9E9;
+	  		}
+	  		.stack {
+	  			border: 1px solid black;
+	  			padding: 5px;
+	  			overflow:auto;
+	  			height: 300px;
+	  		}
+	  		.snippet {
+	  			padding: 5px;
+	  			background-color:white;
+	  			border:1px solid black;
+	  			margin:3px;
+	  			font-family:courier;
+	  		}
+	  </style>
+  </head>
+
+  <body>
+    <h1>Grails Runtime Exception</h1>
+    <h2>Error Details</h2>
+
+  	<div class="message">
+		<strong>Error ${request.'javax.servlet.error.status_code'}:</strong> ${request.'javax.servlet.error.message'.encodeAsHTML()}<br/>
+		<strong>Servlet:</strong> ${request.'javax.servlet.error.servlet_name'}<br/>
+		<strong>URI:</strong> ${request.'javax.servlet.error.request_uri'}<br/>
+		<g:if test="${exception}">
+	  		<strong>Exception Message:</strong> ${exception.message?.encodeAsHTML()} <br />
+	  		<strong>Caused by:</strong> ${exception.cause?.message?.encodeAsHTML()} <br />
+	  		<strong>Class:</strong> ${exception.className} <br />
+	  		<strong>At Line:</strong> [${exception.lineNumber}] <br />
+	  		<strong>Code Snippet:</strong><br />
+	  		<div class="snippet">
+	  			<g:each var="cs" in="${exception.codeSnippet}">
+	  				${cs?.encodeAsHTML()}<br />
+	  			</g:each>
+	  		</div>
+		</g:if>
+  	</div>
+	<g:if test="${exception}">
+	    <h2>Stack Trace</h2>
+	    <div class="stack">
+	      <pre><g:each in="${exception.stackTraceLines}">${it.encodeAsHTML()}<br/></g:each></pre>
+	    </div>
+	</g:if>
+  </body>
+</html>

grails-app/views/index.gsp

+<html>
+    <head>
+        <title>Welcome to Grails</title>
+        <meta name="layout" content="main" />
+        <style type="text/css" media="screen">
+
+        #nav {
+            margin-top:20px;
+            margin-left:30px;
+            width:228px;
+            float:left;
+
+        }
+        .homePagePanel * {
+            margin:0px;
+        }
+        .homePagePanel .panelBody ul {
+            list-style-type:none;
+            margin-bottom:10px;
+        }
+        .homePagePanel .panelBody h1 {
+            text-transform:uppercase;
+            font-size:1.1em;
+            margin-bottom:10px;
+        }
+        .homePagePanel .panelBody {
+            background: url(images/leftnav_midstretch.png) repeat-y top;
+            margin:0px;
+            padding:15px;
+        }
+        .homePagePanel .panelBtm {
+            background: url(images/leftnav_btm.png) no-repeat top;
+            height:20px;
+            margin:0px;
+        }
+
+        .homePagePanel .panelTop {
+            background: url(images/leftnav_top.png) no-repeat top;
+            height:11px;
+            margin:0px;
+        }
+        h2 {
+            margin-top:15px;
+            margin-bottom:15px;
+            font-size:1.2em;
+        }
+        #pageBody {
+            margin-left:280px;
+            margin-right:20px;
+        }
+        </style>
+    </head>
+    <body>
+        <div id="nav">
+            <div class="homePagePanel">
+                <div class="panelTop"></div>
+                <div class="panelBody">
+                    <h1>Application Status</h1>
+                    <ul>
+                        <li>App version: <g:meta name="app.version"></g:meta></li>
+                        <li>Grails version: <g:meta name="app.grails.version"></g:meta></li>
+                        <li>Groovy version: ${org.codehaus.groovy.runtime.InvokerHelper.getVersion()}</li>
+                        <li>JVM version: ${System.getProperty('java.version')}</li>
+                        <li>Controllers: ${grailsApplication.controllerClasses.size()}</li>
+                        <li>Domains: ${grailsApplication.domainClasses.size()}</li>
+                        <li>Services: ${grailsApplication.serviceClasses.size()}</li>
+                        <li>Tag Libraries: ${grailsApplication.tagLibClasses.size()}</li>
+                    </ul>
+                    <h1>Installed Plugins</h1>
+                    <ul>
+                        <g:set var="pluginManager"
+                               value="${applicationContext.getBean('pluginManager')}"></g:set>
+
+                        <g:each var="plugin" in="${pluginManager.allPlugins}">
+                            <li>${plugin.name} - ${plugin.version}</li>
+                        </g:each>
+
+                    </ul>
+                </div>
+                <div class="panelBtm"></div>
+            </div>
+        </div>
+        <div id="pageBody">
+            <h1>Welcome to Grails</h1>
+            <p>Congratulations, you have successfully started your first Grails application! At the moment
+            this is the default page, feel free to modify it to either redirect to a controller or display whatever
+            content you may choose. Below is a list of controllers that are currently deployed in this application,
+            click on each to execute its default action:</p>
+
+            <div id="controllerList" class="dialog">
+                <h2>Available Controllers:</h2>
+                <ul>
+                    <g:each var="c" in="${grailsApplication.controllerClasses.sort { it.fullName } }">
+                        <li class="controller"><g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link></li>
+                    </g:each>
+                </ul>
+            </div>
+        </div>
+    </body>
+</html>

grails-app/views/layouts/main.gsp

+<!DOCTYPE html>
+<html>
+    <head>
+        <title><g:layoutTitle default="Grails" /></title>
+        <link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" />
+        <link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
+        <g:layoutHead />
+        <g:javascript library="application" />
+    </head>
+    <body>
+        <div id="spinner" class="spinner" style="display:none;">
+            <img src="${resource(dir:'images',file:'spinner.gif')}" alt="${message(code:'spinner.alt',default:'Loading...')}" />
+        </div>
+        <div id="grailsLogo"><a href="http://grails.org"><img src="${resource(dir:'images',file:'grails_logo.png')}" alt="Grails" border="0" /></a></div>
+        <g:layoutBody />
+    </body>
+</html>
+<plugin name='activiti-shiro' version='0.1' grailsVersion='1.3.5  &gt; *'>
+  <author>Nicolas Bohorquez Gutierrez (@nickmancoi)</author>
+  <authorEmail>nicolas.bg@gmail.com</authorEmail>
+  <title>This plugin integrates Shiro Security to Activiti.</title>
+  <description>
+        Activiti plugin brings the power of bpmn to grails, Shiro security enables an elegant way to perform 
+authentication and authorization in your app, this plugin provides the basic glue necessary between them
+</description>
+  <documentation>https://bitbucket.org/Nickmancol/grails-activiti-shiro-security-plugin</documentation>
+  <resources>
+    <resource>BootStrap</resource>
+    <resource>BuildConfig</resource>
+    <resource>Config</resource>
+    <resource>DataSource</resource>
+    <resource>UrlMappings</resource>
+    <resource>org.grails.activiti.shirosecurity.SetAuthenticatedUsernameFilter</resource>
+    <resource>spring.resources</resource>
+    <resource>org.grails.activiti.shirosecurity.ShiroActivitiSessionService</resource>
+  </resources>
+  <dependencies>
+    <plugin name='shiro' version='1.1.3 &gt; *' />
+    <plugin name='activiti' version='5.5 &gt; *' />
+  </dependencies>
+  <behavior />
+</plugin>

src/groovy/org/grails/activiti/shirosecurity/GroupManager.groovy

+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.grails.activiti.shirosecurity
+
+import org.activiti.engine.identity.Group
+import org.activiti.engine.identity.GroupQuery
+import org.activiti.engine.impl.GroupQueryImpl
+import org.activiti.engine.impl.Page
+import org.activiti.engine.impl.context.Context
+import org.activiti.engine.impl.persistence.entity.GroupEntity
+
+import org.apache.commons.logging.Log
+import org.apache.commons.logging.LogFactory
+import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
+import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH
+import org.codehaus.groovy.grails.web.pages.FastStringWriter
+import grails.util.GrailsNameUtils as GNU
+
+
+/**
+ *
+ * @author nickman
+ */
+class GroupManager extends org.activiti.engine.impl.persistence.entity.GroupManager {
+	static final Log LOG = LogFactory.getLog(GroupManager.class)
+	
+	Group createNewGroup(String groupId) {
+		throw new UnsupportedOperationException("Please use ${getGroupDomainClass()}.save() to create Group.")
+	}
+	
+	void insertGroup(Group group) {
+		throw new UnsupportedOperationException("Please use ${getGroupDomainClass()}.save() to create Group.")
+	}
+	
+	void updateGroup(Group updatedGroup) {
+		throw new UnsupportedOperationException("Please use ${getGroupDomainClass()}.save() to update Group.")
+	}
+	
+	void deleteGroup(String groupId) {
+		throw new UnsupportedOperationException("Please use ${getGroupDomainClass()}.delete() to delete Group.")
+	}
+	
+	GroupQuery createNewGroupQuery() {
+		return new GroupQueryImpl(Context.getProcessEngineConfiguration().getCommandExecutorTxRequired())
+	}
+	
+	List<Group> findGroupsByUser(String userId) {
+		LOG.debug "findGroupsByUser (${userId})"
+		def user = getUserDomainClass()."findBy${getUsernameClassName()}"(userId)
+		def groups = user?.authorities.toList()
+		return groups
+	}
+	
+	List<Group> findGroupByQueryCriteria(Object query, Page page) {
+		LOG.debug "findGroupByQueryCriteria (${query.class.name}, $page)"
+		List<Group> groups
+		String queryString = createGroupQueryString(query)
+		LOG.debug "queryString = $queryString"
+		if (page) { // listPage()
+			groups = getGroupJoinDomainClass().findAll(queryString, [offset:page.firstResult, max:page.maxResults]).collect{it.userGroup}
+		} else { // list()
+			groups = getGroupJoinDomainClass().findAll(queryString, Collections.emptyMap()).collect{it."${GNU.getPropertyName(getGroupDomainClassName())}"}
+		}
+		return groups
+	}
+	
+	long findGroupCountByQueryCriteria(Object query) {
+		LOG.debug "findGroupCountByQueryCriteria (${query.class.name})"
+		String queryString = createGroupQueryString(query)
+		LOG.debug "queryString = $queryString"
+		return getGroupJoinDomainClass().executeQuery("select count(g) ${queryString}")[0]
+	}
+	
+	private String createGroupQueryString(Object query) {
+		FastStringWriter queryString = new FastStringWriter()
+		queryString << "from ${getGroupJoinDomainClassName()} as g where 1=1"
+		String groupPropertyName = GNU.getPropertyName(getGroupDomainClassName())
+		if (query.id)
+			queryString << " and g.${groupPropertyName}.id='${query.id}'"
+		
+		if (query.name) {
+			queryString << " and g.${groupPropertyName}.name = '${query.name}'"
+		}
+		
+		if (query.nameLike) {
+			queryString << " and g.${groupPropertyName}.name like '${query.nameLike}'"
+		}
+		
+		if (query.type) {
+			queryString << " and g.${groupPropertyName}.type = '${query.type}'"
+		}
+		
+		if (query.userId) {
+			queryString << " and g.${GNU.getPropertyName(getUserDomainClassName())}.id = '${query.userId}'"
+		}
+		
+		if (query.orderBy) {
+			String orderBy = query.orderBy.toLowerCase().replace('_', '')
+			orderBy = orderBy.replace('g', "g.${groupPropertyName}")
+			queryString << " order by ${orderBy}"
+		}
+		return queryString.toString()
+	}
+	
+	GroupEntity findGroupById(String groupId) {
+		throw new UnsupportedOperationException("Please use ${getGroupDomainClass()}.get(id) to find Group by Id.")
+	}
+	
+	private getGroupDomainClassName() {
+		return CH.config.securityConfig.userLookup.authority.className
+	}
+	
+	private getGroupDomainClass() {
+		return AH.application.getDomainClass(getGroupDomainClassName()).clazz
+	}
+	
+	private getGroupJoinDomainClassName() {
+		return CH.config.securityConfig.userLookup.authorityJoinClassName
+	}
+	
+	private getGroupJoinDomainClass() {
+		return AH.application.getDomainClass(getGroupJoinDomainClassName()).clazz
+	}
+	
+	private getUserDomainClassName() {
+		return CH.config.securityConfig.userLookup.userDomainClassName
+	}
+
+	private getUserDomainClass() {
+                return AH.application.getDomainClass(getUserDomainClassName()).clazz
+	}
+	
+	private getUsernamePropertyName() {
+		return CH.config.securityConfig.userLookup.usernamePropertyName
+	}
+		
+	private getUsernameClassName() {
+		return GNU.getClassNameRepresentation(getUsernamePropertyName())
+	}
+}
+

src/groovy/org/grails/activiti/shirosecurity/ShiroAutenticationSuccessListener.groovy

+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.grails.activiti.shirosecurity
+
+import org.apache.shiro.session.SessionListenerAdapter
+import org.apache.shiro.session.Session
+import org.apache.shiro.SecurityUtils
+
+/**
+ *
+ * @author nickman
+ */
+class ShiroAutenticationSuccessListener extends SessionListenerAdapter {
+	
+    void onStart(Session session) {
+        if(SecurityUtils.subject) {
+            def sessionUsernameKey = CH.config.activiti.sessionUsernameKey?:ActivitiConstants.DEFAULT_SESSION_USERNAME_KEY
+            session.setAttribute(sessionUsernameKey , SecurityUtils.subject?.principal)
+        }
+    }
+}
+

src/groovy/org/grails/activiti/shirosecurity/UserManager.groovy

+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.grails.activiti.shirosecurity
+
+import org.activiti.engine.identity.Group
+import org.activiti.engine.identity.User
+import org.activiti.engine.identity.UserQuery
+import org.activiti.engine.impl.Page
+import org.activiti.engine.impl.UserQueryImpl
+import org.activiti.engine.impl.context.Context
+import org.activiti.engine.impl.persistence.entity.UserEntity
+import org.activiti.engine.impl.persistence.entity.IdentityInfoEntity
+
+import org.apache.commons.logging.Log
+import org.apache.commons.logging.LogFactory
+import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
+import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH
+import org.codehaus.groovy.grails.web.pages.FastStringWriter
+
+/**
+ *
+ * @author nickman
+ */
+class UserManager extends org.activiti.engine.impl.persistence.entity.UserManager {
+	static final Log LOG = LogFactory.getLog(UserManager.class)
+	
+	User createNewUser(String userId) {
+		throw new UnsupportedOperationException("Please use ${getUserDomainClassName()}.save() to create User.")
+	}
+		
+	void insertUser(User user) {
+		throw new UnsupportedOperationException("Please use ${getUserDomainClassName()}.save() to insert User.")
+	}
+	
+	void updateUser(User updatedUser) {
+		throw new UnsupportedOperationException("Please use ${getUserDomainClassName()}.save() to update User.")
+	}
+	
+	void deleteUser(String userId) {
+		throw new UnsupportedOperationException("Please use ${getUserDomainClassName()}.delete() to delete User.")
+	}
+	
+	UserEntity findUserById(String userId) {
+		LOG.debug "findUserById ($userId)"
+		User user = getUserDomainClass()."findBy${getUsernameClassName()}"(userId)
+		return user
+	}
+	
+	List<User> findUserByQueryCriteria(Object query, Page page) {
+		LOG.debug "findUserByQueryCriteria (${query.class.name}, $page)"
+		List<User> users
+		String queryString = createUserQueryString(query)
+		LOG.debug "queryString = $queryString"
+		if (page) { // listPage()
+			users = getUserDomainClass().findAll(queryString, [offset:page.firstResult, max:page.maxResults])
+		} else { // list()
+			users = getUserDomainClass().findAll(queryString, Collections.emptyMap())
+		}
+		LOG.debug "query.groupId = ${query.groupId}"
+		if (users && query.groupId) {
+			users = users.findAll { it.authorities*.id.contains(query.groupId) }
+		}
+		return users
+	}
+	
+	long findUserCountByQueryCriteria(Object query) {
+		LOG.debug "findUserCountByQueryCriteria (${query.class.name})"
+		String queryString = createUserQueryString(query)
+		LOG.debug "queryString = $queryString"
+		return getUserDomainClass().executeQuery("select count(u) ${queryString}")[0]
+	}
+	
+	List<Group> findGroupsByUser(String userId) {
+		LOG.debug "findGroupsByUser (${userId})"
+		def user = getUserDomainClass()."findBy${getUsernameClassName()}"(userId)
+		def groups = user?.authorities.toList()
+		return groups
+	}
+	
+	UserQuery createNewUserQuery() {
+		return new UserQueryImpl(Context.getProcessEngineConfiguration().getCommandExecutorTxRequired())
+	}
+	
+	IdentityInfoEntity findUserInfoByUserIdAndKey(String userId, String key) {
+		throw new UnsupportedOperationException()
+	}
+	
+	List<String> findUserInfoKeysByUserIdAndType(String userId, String type) {
+		throw new UnsupportedOperationException()
+	}
+	
+	private String createUserQueryString(Object query) {
+		FastStringWriter queryString = new FastStringWriter()
+		queryString << "from ${getUserDomainClassName()} as u where 1=1"
+		if (query.id)
+			queryString << " and u.${getUsernamePropertyName()}='${query.id}'"
+		
+		if (query.firstName) {
+			queryString << " and u.firstName = '${query.firstName}'"
+		}
+		
+		if (query.firstNameLike) {
+			queryString << " and u.firstName like '${query.firstNameLike}'"
+		}
+		
+		if (query.lastName) {
+			queryString << " and u.lastName = '${query.lastName}'"
+		}
+		
+		if (query.lastNameLike) {
+			queryString << " and u.lastName like '${query.lastNameLike}'"
+		}
+		
+		if (query.email) {
+			queryString << " and u.email = '${query.email}'"
+		}
+		
+		if (query.emailLike) {
+			queryString << " and u.email like '${query.emailLike}'"
+		}
+		
+		if (query.orderBy) {
+			String orderBy = query.orderBy.toLowerCase().replace('_', '')
+			orderBy = orderBy.replace("last", "lastName")
+			orderBy = orderBy.replace("first", "firstName")
+			queryString << " order by ${orderBy}"
+		}
+		return queryString.toString()
+	}
+	
+	private getUsernamePropertyName() {
+		return CH.config.securityConfig.userLookup.usernamePropertyName
+	}
+		
+	private getUserDomainClassName() {
+		return CH.config.securityConfig.userLookup.userDomainClassName
+	}
+	
+	private getUserDomainClass() {
+		return AH.application.getDomainClass(getUserDomainClassName()).clazz
+	}
+}
+

src/java/org/grails/activiti/shirosecurity/ShiroSecurityGroupManagerFactory.java

+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.grails.activiti.shirosecurity;
+
+import org.activiti.engine.impl.interceptor.Session;
+import org.activiti.engine.impl.interceptor.SessionFactory;
+
+/**
+ *
+ * @author nickman
+ */
+public class ShiroSecurityGroupManagerFactory implements SessionFactory {
+
+    public Class<?> getSessionType() {
+        return org.activiti.engine.impl.persistence.entity.GroupManager.class;
+    }
+
+    public Session openSession() {
+        return new GroupManager();
+    }
+}

src/java/org/grails/activiti/shirosecurity/ShiroSecurityUserManagerFactory.java

+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.grails.activiti.shirosecurity;
+
+import org.activiti.engine.impl.interceptor.Session;
+import org.activiti.engine.impl.interceptor.SessionFactory;
+
+/**
+ *
+ * @author nickman
+ */
+public class ShiroSecurityUserManagerFactory  implements SessionFactory {
+    public Class<?> getSessionType() {
+                return org.activiti.engine.impl.persistence.entity.UserManager.class;
+    }
+
+    public Session openSession() {
+                return new UserManager();
+    }
+}

test/unit/org/grails/activiti/shirosecurity/ShiroActivitiSessionServiceTests.groovy

+package org.grails.activiti.shirosecurity
+
+import grails.test.*
+
+class ShiroActivitiSessionServiceTests extends GrailsUnitTestCase {
+    protected void setUp() {
+        super.setUp()
+    }
+
+    protected void tearDown() {
+        super.tearDown()
+    }
+
+    void testSomething() {
+
+    }
+}

web-app/WEB-INF/applicationContext.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+	<bean id="grailsApplication" class="org.codehaus.groovy.grails.commons.GrailsApplicationFactoryBean">
+		<description>Grails application factory bean</description>
+        <property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
+        <property name="grailsResourceLoader" ref="grailsResourceLoader" />
+	</bean>
+
+	<bean id="pluginManager" class="org.codehaus.groovy.grails.plugins.GrailsPluginManagerFactoryBean">
+		<description>A bean that manages Grails plugins</description>
+        <property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
+        <property name="application" ref="grailsApplication" />
+	</bean>
+
+    <bean id="grailsConfigurator" class="org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator">
+        <constructor-arg>
+            <ref bean="grailsApplication" />
+        </constructor-arg>
+        <property name="pluginManager" ref="pluginManager" />
+    </bean>
+
+    <bean id="grailsResourceLoader" class="org.codehaus.groovy.grails.commons.GrailsResourceLoaderFactoryBean">
+        <property name="grailsResourceHolder" ref="grailsResourceHolder" />
+    </bean>
+
+    <bean id="grailsResourceHolder" scope="prototype" class="org.codehaus.groovy.grails.commons.spring.GrailsResourceHolder">
+        <property name="resources">
+              <value>classpath*:**/grails-app/**/*.groovy</value>
+        </property>
+    </bean>    
+    
+   <bean id="characterEncodingFilter"
+      class="org.springframework.web.filter.CharacterEncodingFilter">
+        <property name="encoding">
+          <value>utf-8</value>
+        </property>
+   </bean>    	
+</beans>

web-app/WEB-INF/sitemesh.xml

+<sitemesh>
+    <page-parsers>
+        <parser content-type="text/html"
+            class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
+        <parser content-type="text/html;charset=ISO-8859-1"
+            class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
+        <parser content-type="text/html;charset=UTF-8"
+            class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />            
+    </page-parsers>
+
+    <decorator-mappers>
+        <mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper" />
+    </decorator-mappers>
+</sitemesh>

web-app/WEB-INF/tld/c.tld

+<?xml version="1.0" encoding="UTF-8" ?>
+
+<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
+    version="2.0">
+    
+  <description>JSTL 1.1 core library</description>
+  <display-name>JSTL core</display-name>
+  <tlib-version>1.1</tlib-version>
+  <short-name>c</short-name>
+  <uri>http://java.sun.com/jsp/jstl/core</uri>
+
+  <validator>
+    <description>
+        Provides core validation features for JSTL tags.
+    </description>
+    <validator-class>
+        org.apache.taglibs.standard.tlv.JstlCoreTLV
+    </validator-class>
+  </validator>
+
+  <tag>
+    <description>
+        Catches any Throwable that occurs in its body and optionally
+        exposes it.
+    </description>
+    <name>catch</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+exception thrown from a nested action. The type of the
+scoped variable is the type of the exception thrown.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	Simple conditional tag that establishes a context for
+	mutually exclusive conditional operations, marked by
+	&lt;when&gt; and &lt;otherwise&gt;
+    </description>
+    <name>choose</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.ChooseTag</tag-class>
+    <body-content>JSP</body-content>
+  </tag>
+
+  <tag>
+    <description>
+	Simple conditional tag, which evalutes its body if the
+	supplied condition is true and optionally exposes a Boolean
+	scripting variable representing the evaluation of this condition
+    </description>
+    <name>if</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.IfTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The test condition that determines whether or
+not the body content should be processed.
+        </description>
+        <name>test</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+	<type>boolean</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resulting value of the test condition. The type
+of the scoped variable is Boolean.        
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Retrieves an absolute or relative URL and exposes its contents
+        to either the page, a String in 'var', or a Reader in 'varReader'.
+    </description>
+    <name>import</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ImportTag</tag-class>
+    <tei-class>org.apache.taglibs.standard.tei.ImportTEI</tei-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The URL of the resource to import.
+        </description>
+        <name>url</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resource's content. The type of the scoped
+variable is String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resource's content. The type of the scoped
+variable is Reader.
+        </description>
+        <name>varReader</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when accessing a relative
+URL resource that belongs to a foreign
+context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Character encoding of the content at the input
+resource.
+        </description>
+        <name>charEncoding</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	The basic iteration tag, accepting many different
+        collection types and supporting subsetting and other
+        functionality
+    </description>
+    <name>forEach</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForEachTag</tag-class>
+    <tei-class>org.apache.taglibs.standard.tei.ForEachTEI</tei-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Collection of items to iterate over.
+        </description>
+	<name>items</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>java.lang.Object</type>
+    </attribute>
+    <attribute>
+        <description>
+If items specified:
+Iteration begins at the item located at the
+specified index. First item of the collection has
+index 0.
+If items not specified:
+Iteration begins with index set at the value
+specified.
+        </description>
+	<name>begin</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+If items specified:
+Iteration ends at the item located at the
+specified index (inclusive).
+If items not specified:
+Iteration ends when index reaches the value
+specified.
+        </description>
+	<name>end</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration will only process every step items of
+the collection, starting with the first one.
+        </description>
+	<name>step</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+current item of the iteration. This scoped
+variable has nested visibility. Its type depends
+on the object of the underlying collection.
+        </description>
+	<name>var</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+status of the iteration. Object exported is of type
+javax.servlet.jsp.jstl.core.LoopTagStatus. This scoped variable has nested
+visibility.
+        </description>
+	<name>varStatus</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	Iterates over tokens, separated by the supplied delimeters
+    </description>
+    <name>forTokens</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForTokensTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+String of tokens to iterate over.
+        </description>
+	<name>items</name>
+	<required>true</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>java.lang.String</type>
+    </attribute>
+    <attribute>
+        <description>
+The set of delimiters (the characters that
+separate the tokens in the string).
+        </description>
+	<name>delims</name>
+	<required>true</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>java.lang.String</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration begins at the token located at the
+specified index. First token has index 0.
+        </description>
+	<name>begin</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration ends at the token located at the
+specified index (inclusive).
+        </description>
+	<name>end</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration will only process every step tokens
+of the string, starting with the first one.
+        </description>
+	<name>step</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+current item of the iteration. This scoped
+variable has nested visibility.
+        </description>
+	<name>var</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+status of the iteration. Object exported is of
+type
+javax.servlet.jsp.jstl.core.LoopTag
+Status. This scoped variable has nested
+visibility.
+        </description>
+	<name>varStatus</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Like &lt;%= ... &gt;, but for expressions.
+    </description> 
+    <name>out</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Expression to be evaluated.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Default value if the resulting value is null.
+        </description>
+        <name>default</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Determines whether characters &lt;,&gt;,&amp;,'," in the
+resulting string should be converted to their
+corresponding character entity codes. Default value is
+true.
+        </description>
+        <name>escapeXml</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+
+  <tag>
+    <description>
+        Subtag of &lt;choose&gt; that follows &lt;when&gt; tags
+        and runs only if all of the prior conditions evaluated to
+        'false'
+    </description>
+    <name>otherwise</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.OtherwiseTag</tag-class>
+    <body-content>JSP</body-content>
+  </tag>
+
+  <tag>
+    <description>
+        Adds a parameter to a containing 'import' tag's URL.
+    </description>
+    <name>param</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ParamTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the query string parameter.
+        </description>
+        <name>name</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Value of the parameter.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Redirects to a new URL.
+    </description>
+    <name>redirect</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.RedirectTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The URL of the resource to redirect to.
+        </description>
+        <name>url</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when redirecting to a relative URL
+resource that belongs to a foreign context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Removes a scoped variable (from a particular scope, if specified).
+    </description>
+    <name>remove</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.RemoveTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Name of the scoped variable to be removed.
+        </description>
+        <name>var</name>
+        <required>true</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+ <tag>
+    <description>
+        Sets the result of an expression evaluation in a 'scope'
+    </description>
+    <name>set</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.SetTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable to hold the value
+specified in the action. The type of the scoped variable is
+whatever type the value expression evaluates to.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Expression to be evaluated.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Target object whose property will be set. Must evaluate to
+a JavaBeans object with setter property property, or to a
+java.util.Map object.
+        </description>
+        <name>target</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the property to be set in the target object.
+        </description>
+        <name>property</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Creates a URL with optional query parameters.
+    </description>
+    <name>url</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.UrlTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+processed url. The type of the scoped variable is
+String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+URL to be processed.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when specifying a relative URL
+resource that belongs to a foreign context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	Subtag of &lt;choose&gt; that includes its body if its
+	condition evalutes to 'true'
+    </description>
+    <name>when</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.WhenTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The test condition that determines whether or not the
+body content should be processed.
+        </description>
+        <name>test</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+	<type>boolean</type>
+    </attribute>
+  </tag>
+
+</taglib>

web-app/WEB-INF/tld/fmt.tld

+<?xml version="1.0" encoding="UTF-8" ?>
+
+<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
+    version="2.0">
+    
+  <description>JSTL 1.1 i18n-capable formatting library</description>
+  <display-name>JSTL fmt</display-name>
+  <tlib-version>1.1</tlib-version>
+  <short-name>fmt</short-name>
+  <uri>http://java.sun.com/jsp/jstl/fmt</uri>
+
+  <validator>
+    <description>
+        Provides core validation features for JSTL tags.
+    </description>
+    <validator-class>
+        org.apache.taglibs.standard.tlv.JstlFmtTLV
+    </validator-class>
+  </validator>
+
+  <tag>
+    <description>
+        Sets the request character encoding
+    </description>
+    <name>requestEncoding</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.RequestEncodingTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Name of character encoding to be applied when
+decoding request parameters.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Stores the given locale in the locale configuration variable
+    </description>
+    <name>setLocale</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.SetLocaleTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+A String value is interpreted as the
+printable representation of a locale, which
+must contain a two-letter (lower-case)
+language code (as defined by ISO-639),
+and may contain a two-letter (upper-case)
+country code (as defined by ISO-3166).
+Language and country codes must be
+separated by hyphen (-) or underscore
+(_).        
+	</description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Vendor- or browser-specific variant.
+See the java.util.Locale javadocs for
+more information on variants.
+        </description>
+        <name>variant</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of the locale configuration variable.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Specifies the time zone for any time formatting or parsing actions
+        nested in its body
+    </description>
+    <name>timeZone</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.TimeZoneTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The time zone. A String value is interpreted as
+a time zone ID. This may be one of the time zone
+IDs supported by the Java platform (such as
+"America/Los_Angeles") or a custom time zone
+ID (such as "GMT-8"). See
+java.util.TimeZone for more information on
+supported time zone formats.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Stores the given time zone in the time zone configuration variable
+    </description>
+    <name>setTimeZone</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.SetTimeZoneTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+The time zone. A String value is interpreted as
+a time zone ID. This may be one of the time zone
+IDs supported by the Java platform (such as
+"America/Los_Angeles") or a custom time zone
+ID (such as "GMT-8"). See java.util.TimeZone for
+more information on supported time zone
+formats.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable which
+stores the time zone of type
+java.util.TimeZone.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var or the time zone configuration
+variable.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Loads a resource bundle to be used by its tag body
+    </description>
+    <name>bundle</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.BundleTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Resource bundle base name. This is the bundle's
+fully-qualified resource name, which has the same
+form as a fully-qualified class name, that is, it uses
+"." as the package component separator and does not
+have any file type (such as ".class" or ".properties")
+suffix.
+        </description>
+        <name>basename</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Prefix to be prepended to the value of the message
+key of any nested &lt;fmt:message&gt; action.
+        </description>
+        <name>prefix</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Loads a resource bundle and stores it in the named scoped variable or
+        the bundle configuration variable
+    </description>
+    <name>setBundle</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.SetBundleTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Resource bundle base name. This is the bundle's
+fully-qualified resource name, which has the same
+form as a fully-qualified class name, that is, it uses
+"." as the package component separator and does not
+have any file type (such as ".class" or ".properties")
+suffix.
+        </description>
+        <name>basename</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable which stores
+the i18n localization context of type
+javax.servlet.jsp.jstl.fmt.LocalizationC
+ontext.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var or the localization context
+configuration variable.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Maps key to localized message and performs parametric replacement
+    </description>
+    <name>message</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.MessageTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Message key to be looked up.
+        </description>
+        <name>key</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Localization context in whose resource
+bundle the message key is looked up.
+        </description>
+        <name>bundle</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable
+which stores the localized message.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Supplies an argument for parametric replacement to a containing
+        &lt;message&gt; tag
+    </description>
+    <name>param</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.ParamTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Argument used for parametric replacement.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Formats a numeric value as a number, currency, or percentage
+    </description>
+    <name>formatNumber</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.FormatNumberTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Numeric value to be formatted.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether the value is to be
+formatted as number, currency, or
+percentage.
+        </description>
+        <name>type</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Custom formatting pattern.
+        </description>
+        <name>pattern</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+ISO 4217 currency code. Applied only
+when formatting currencies (i.e. if type is
+equal to "currency"); ignored otherwise.
+        </description>
+        <name>currencyCode</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Currency symbol. Applied only when
+formatting currencies (i.e. if type is equal
+to "currency"); ignored otherwise.
+        </description>
+        <name>currencySymbol</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether the formatted output
+will contain any grouping separators.
+        </description>
+        <name>groupingUsed</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Maximum number of digits in the integer
+portion of the formatted output.
+        </description>
+        <name>maxIntegerDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Minimum number of digits in the integer
+portion of the formatted output.
+        </description>
+        <name>minIntegerDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Maximum number of digits in the
+fractional portion of the formatted output.
+        </description>
+        <name>maxFractionDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Minimum number of digits in the
+fractional portion of the formatted output.
+        </description>
+        <name>minFractionDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable
+which stores the formatted result as a