diff --git a/bbb-lti/.classpath b/bbb-lti/.classpath
deleted file mode 100644
index 450fe04df4c4cd76676cc802798188be7b33ff9c..0000000000000000000000000000000000000000
--- a/bbb-lti/.classpath
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="src/java"/>
-	<classpathentry kind="src" path="src/groovy"/>
-	<classpathentry kind="src" path="grails-app/conf"/>
-	<classpathentry kind="src" path="grails-app/controllers"/>
-	<classpathentry kind="src" path="grails-app/domain"/>
-	<classpathentry kind="src" path="grails-app/services"/>
-	<classpathentry kind="src" path="grails-app/taglib"/>
-	<classpathentry kind="src" path="test/integration"/>
-	<classpathentry kind="src" path="test/unit"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="var" path="GRAILS_HOME/ant/lib/ant.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-el-1.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/spring-test-2.5.6.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/oro-2.0.8.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/log4j-1.2.15.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jsr107cache-1.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-fileupload-1.2.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/ant-trax.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-collections-3.2.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-lang-2.4.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/spring-webmvc-2.5.6.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/hsqldb-1.8.0.5.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/ant-1.7.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jcl-over-slf4j-1.5.6.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/backport-util-concurrent-3.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jasper-compiler-5.5.15.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-validator-1.3.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jsp-api-2.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jetty-naming-6.1.14.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jetty-util-6.1.14.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/slf4j-api-1.5.6.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/start.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/servlet-api-2.5-6.1.14.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/ognl-2.6.9.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-dbcp-1.2.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/cglib-nodep-2.1_3.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jline-0.9.91.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jasper-compiler-jdt-5.5.15.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/standard-2.4.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/org.springframework.binding-2.0.3.RELEASE.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/standard-2.3.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/ejb3-persistence-3.3.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-io-1.4.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jetty-6.1.14.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/spring-2.5.6.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jstl-2.3.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jetty-plus-6.1.14.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jstl-2.4.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/xpp3_min-1.1.3.4.O.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-codec-1.3.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-pool-1.2.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/sitemesh-2.4.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/org.springframework.webflow-2.0.3.RELEASE.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/junit-3.8.2.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-beanutils-1.7.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/oscache-2.4.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jta-1.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/ehcache-1.5.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/serializer.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/ant-nodeps-1.7.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/groovy-all-1.6.3.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/gant_groovy1.6-1.6.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/svnkit-1.2.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/slf4j-log4j12-1.5.6.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jsp-api-2.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/jasper-runtime-5.5.15.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/ant-junit-1.7.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/ant-launcher-1.7.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/commons-cli-1.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/ivy-2.0.0.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/antlr-2.7.6.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/lib/org.springframework.js-2.0.3.RELEASE.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/dist/grails-scripts-1.1.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/dist/grails-gorm-1.1.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/dist/grails-webflow-1.1.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/dist/grails-bootstrap-1.1.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/dist/grails-resources-1.1.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/dist/grails-crud-1.1.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/dist/grails-core-1.1.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/dist/grails-spring-1.1.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/dist/grails-web-1.1.1.jar"/>
-	<classpathentry kind="var" path="GRAILS_HOME/dist/grails-test-1.1.1.jar"/>
-</classpath>
diff --git a/bbb-lti/application.properties b/bbb-lti/application.properties
index e5656fae00617fec6e6dc55da4f3e9b0ee3aba4e..6a7eeaaa1d30569b96a7d4e5082d3b7f46296abf 100644
--- a/bbb-lti/application.properties
+++ b/bbb-lti/application.properties
@@ -1,5 +1,5 @@
 #Grails Metadata file
-#Thu Mar 20 10:48:08 PDT 2014
+#Wed Aug 27 13:06:23 PDT 2014
 app.grails.version=2.3.6
 app.name=lti
-app.version=0.1.2
+app.version=0.2
diff --git a/bbb-lti/grails-app/conf/ApplicationResources.groovy b/bbb-lti/grails-app/conf/ApplicationResources.groovy
index 6fe55b24a72d2e4fdfbe1fffdd55d6c641df4954..c0f50d4b63f15f047690dc42b84f3004b97f00bc 100644
--- a/bbb-lti/grails-app/conf/ApplicationResources.groovy
+++ b/bbb-lti/grails-app/conf/ApplicationResources.groovy
@@ -15,7 +15,6 @@
     You should have received a copy of the GNU Lesser General Public License along
     with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 */    
-
 modules = {
     application {
         resource url:'js/application.js'
diff --git a/bbb-lti/grails-app/conf/BuildConfig.groovy b/bbb-lti/grails-app/conf/BuildConfig.groovy
index cff8ee4071b2a0bd81a06f4d4cd75984082c54f6..83d7df24d9fe2475aa270cd372416cc9669d08a0 100644
--- a/bbb-lti/grails-app/conf/BuildConfig.groovy
+++ b/bbb-lti/grails-app/conf/BuildConfig.groovy
@@ -15,8 +15,7 @@
     You should have received a copy of the GNU Lesser General Public License along
     with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 */    
-
-grails.servlet.version = "3.0"
+grails.servlet.version = "3.0" // Change depending on target container compliance (2.5 or 3.0)
 grails.project.class.dir = "target/classes"
 grails.project.test.class.dir = "target/test-classes"
 grails.project.test.reports.dir = "target/test-reports"
@@ -26,48 +25,76 @@ grails.project.source.level = 1.6
 //grails.project.war.file = "target/${appName}-${appVersion}.war"
 
 grails.project.fork = [
+    // configure settings for compilation JVM, note that if you alter the Groovy version forked compilation is required
+    //  compile: [maxMemory: 256, minMemory: 64, debug: false, maxPerm: 256, daemon:true],
+
+    // configure settings for the test-app JVM, uses the daemon by default
     test: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, daemon:true],
+    // configure settings for the run-app JVM
     run: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false],
+    // configure settings for the run-war JVM
     war: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false],
+    // configure settings for the Console UI JVM
     console: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256]
 ]
 
 grails.project.dependency.resolver = "maven" // or ivy
 grails.project.dependency.resolution = {
+    // inherit Grails' default dependencies
     inherits("global") {
+        // specify dependency exclusions here; for example, uncomment this to disable ehcache:
+        // excludes 'ehcache'
     }
-    log "error"
-    checksums true
-    legacyResolve false
+    log "error" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
+    checksums true // Whether to verify checksums on resolve
+    legacyResolve false // whether to do a secondary resolve on plugin installation, not advised and here for backwards compatibility
 
     repositories {
-        inherits true
+        inherits true // Whether to inherit repository definitions from plugins
 
         grailsPlugins()
         grailsHome()
         mavenLocal()
         grailsCentral()
         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/"
-        //mavenRepo "https://raw.github.com/blindsidenetworks/oauth/mvn-repo/"
+        // uncomment these (or add new ones) to enable remote dependency resolution from public Maven repositories
+        //mavenRepo "http://repository.codehaus.org"
+        //mavenRepo "http://download.java.net/maven/2/"
+        //mavenRepo "http://repository.jboss.com/maven2/"
     }
 
     dependencies {
-        //runtime   "commons-net:commons-net:3.0.1"
-        //runtime   "net.oauth:oauth:1.0.1"
+        // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes e.g.
+        // runtime 'mysql:mysql-connector-java:5.1.27'
+        // runtime 'org.postgresql:postgresql:9.3-1100-jdbc41'
     }
 
     plugins {
         // plugins for the build system only
         build ":tomcat:7.0.50.1"
 
+        // plugins for the compile step
+        compile ":scaffolding:2.0.2"
+        compile ':cache:1.1.1'
+
+        // plugins needed at runtime but not for compilation
+        runtime ":hibernate:3.6.10.8" // or ":hibernate4:4.3.1.1"
         runtime ":database-migration:1.3.8"
         runtime ":jquery:1.11.0"
         runtime ":resources:1.2.1"
+        // Uncomment these (or add new ones) to enable additional resources capabilities
+        //runtime ":zipped-resources:1.0.1"
+        //runtime ":cached-resources:1.1"
+        //runtime ":yui-minify-resources:0.1.5"
         runtime ':twitter-bootstrap:3.1.1'
+        
+        // An alternative to the default resources plugin is the asset-pipeline plugin
+        //compile ":asset-pipeline:1.5.0"
+
+        // Uncomment these to enable additional asset-pipeline capabilities
+        //compile ":sass-asset-pipeline:1.5.1"
+        //compile ":less-asset-pipeline:1.5.0"
+        //compile ":coffee-asset-pipeline:1.5.0"
+        //compile ":handlebars-asset-pipeline:1.0.0.3"
     }
 }
diff --git a/bbb-lti/grails-app/conf/Config.groovy b/bbb-lti/grails-app/conf/Config.groovy
index b276a7836329c53dc50cc941bf17dbf1da08388a..8b1c701b29ddf2ce436120defd464887d7632545 100644
--- a/bbb-lti/grails-app/conf/Config.groovy
+++ b/bbb-lti/grails-app/conf/Config.groovy
@@ -1,139 +1,134 @@
-/* 
-    BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
-
-    Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
-
-    This program is free software; you can redistribute it and/or modify it under the
-    terms of the GNU Lesser General Public License as published by the Free Software
-    Foundation; either version 3.0 of the License, or (at your option) any later
-    version.
-
-    BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
-    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-    PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public License along
-    with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-*/    
-
-// locations to search for config files that get merged into the main config;
-// config files can be ConfigSlurper scripts, Java properties files, or classes
-// in the classpath in ConfigSlurper format
-
-grails.config.locations = [ "classpath:lti.properties"]
-// 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"]
-// }
-
-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
-// The ACCEPT header will not be used for content negotiation for user agents containing the following strings (defaults to the 4 major rendering engines)
-grails.mime.disable.accept.header.userAgents = ['Gecko', 'WebKit', 'Presto', 'Trident']
-grails.mime.types = [ // the first one is the default format
-    all:           '*/*', // 'all' maps to '*' or the first available format in withFormat
-    atom:          'application/atom+xml',
-    css:           'text/css',
-    csv:           'text/csv',
-    form:          'application/x-www-form-urlencoded',
-    html:          ['text/html','application/xhtml+xml'],
-    js:            'text/javascript',
-    json:          ['application/json', 'text/json'],
-    multipartForm: 'multipart/form-data',
-    rss:           'application/rss+xml',
-    text:          'text/plain',
-    hal:           ['application/hal+json','application/hal+xml'],
-    xml:           ['text/xml', 'application/xml']
-]
-
-// URL Mapping Cache Max Size, defaults to 5000
-//grails.urlmapping.cache.maxsize = 1000
-
-// What URL patterns should be processed by the resources plugin
-grails.resources.adhoc.patterns = ['/images/*', '/css/*', '/js/*', '/plugins/*']
-grails.resources.adhoc.excludes = ['/WEB-INF/**']
-
-// Legacy setting for codec used to encode data with ${}
-grails.views.default.codec = "html"
-
-// The default scope for controllers. May be prototype, session or singleton.
-// If unspecified, controllers are prototype scoped.
-grails.controllers.defaultScope = 'singleton'
-
-// GSP settings
-grails {
-    views {
-        gsp {
-            encoding = 'UTF-8'
-            htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping
-            codecs {
-                expression = 'html' // escapes values inside ${}
-                scriptlet = 'html' // escapes output from scriptlets in GSPs
-                taglib = 'none' // escapes output from taglibs
-                staticparts = 'none' // escapes output from static template parts
-            }
-        }
-        // escapes all not-encoded output at final stage of outputting
-        // filteringCodecForContentType.'text/html' = 'html'
-    }
-}
-
- 
-grails.converters.encoding = "UTF-8"
-// 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
-// packages to include in Spring bean scanning
-grails.spring.bean.packages = []
-// whether to disable processing of multi part requests
-grails.web.disable.multipart=false
-
-// request parameters to mask when logging exceptions
-grails.exceptionresolver.params.exclude = ['password']
-
-// configure auto-caching of queries by default (if false you can cache individual queries with 'cache: true')
-grails.hibernate.cache.queries = false
-
-environments {
-    development {
-        grails.logging.jul.usebridge = true
-    }
-    production {
-        grails.logging.jul.usebridge = false
-    }
-}
-
-// log4j configuration
-log4j = {
-    appenders {
-        rollingFile name:"logfile", maxFileSize:1000000, file:"/var/log/bigbluebutton/bbb-lti.log", layout:pattern(conversionPattern: '%d{[dd.MM.yy HH:mm:ss.SSS]} %-5p %c %x - %m%n')
-        console name:'console', layout:pattern(conversionPattern: '%d{[dd.MM.yy HH:mm:ss.SSS]} %-5p %c %x - %m%n')
-        'null' name:'stacktrace'
-    }
-    debug logfile:"grails.app"
-
-    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'
-
-}
+/*
+ BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+
+ Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+
+ This program is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3.0 of the License, or (at your option) any later
+ version.
+
+ BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+// locations to search for config files that get merged into the main config;
+// config files can be ConfigSlurper scripts, Java properties files, or classes
+// in the classpath in ConfigSlurper format
+
+grails.config.locations = [ "classpath:lti.properties"]
+// 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"]
+// }
+
+grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination
+
+// The ACCEPT header will not be used for content negotiation for user agents containing the following strings (defaults to the 4 major rendering engines)
+grails.mime.disable.accept.header.userAgents = ['Gecko', 'WebKit', 'Presto', 'Trident']
+grails.mime.types = [ // the first one is the default format
+    all:           '*/*', // 'all' maps to '*' or the first available format in withFormat
+    atom:          'application/atom+xml',
+    css:           'text/css',
+    csv:           'text/csv',
+    form:          'application/x-www-form-urlencoded',
+    html:          ['text/html','application/xhtml+xml'],
+    js:            'text/javascript',
+    json:          ['application/json', 'text/json'],
+    multipartForm: 'multipart/form-data',
+    rss:           'application/rss+xml',
+    text:          'text/plain',
+    hal:           ['application/hal+json','application/hal+xml'],
+    xml:           ['text/xml', 'application/xml']
+]
+
+// URL Mapping Cache Max Size, defaults to 5000
+//grails.urlmapping.cache.maxsize = 1000
+
+// What URL patterns should be processed by the resources plugin
+grails.resources.adhoc.patterns = ['/images/*', '/css/*', '/js/*', '/plugins/*']
+grails.resources.adhoc.excludes = ['/WEB-INF/**']
+
+// Legacy setting for codec used to encode data with ${}
+grails.views.default.codec = "html"
+
+// The default scope for controllers. May be prototype, session or singleton.
+// If unspecified, controllers are prototype scoped.
+grails.controllers.defaultScope = 'singleton'
+
+// GSP settings
+grails {
+    views {
+        gsp {
+            encoding = 'UTF-8'
+            htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping
+            codecs {
+                expression = 'html' // escapes values inside ${}
+                scriptlet = 'html' // escapes output from scriptlets in GSPs
+                taglib = 'none' // escapes output from taglibs
+                staticparts = 'none' // escapes output from static template parts
+            }
+        }
+        // escapes all not-encoded output at final stage of outputting
+        // filteringCodecForContentType.'text/html' = 'html'
+    }
+}
+
+ 
+grails.converters.encoding = "UTF-8"
+// 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
+// packages to include in Spring bean scanning
+grails.spring.bean.packages = []
+// whether to disable processing of multi part requests
+grails.web.disable.multipart=false
+
+// request parameters to mask when logging exceptions
+grails.exceptionresolver.params.exclude = ['password']
+
+// configure auto-caching of queries by default (if false you can cache individual queries with 'cache: true')
+grails.hibernate.cache.queries = false
+
+environments {
+    development {
+        grails.logging.jul.usebridge = true
+    }
+    production {
+        grails.logging.jul.usebridge = false
+        // TODO: grails.serverURL = "http://www.changeme.com"
+    }
+}
+
+// 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'
+}
diff --git a/bbb-lti/grails-app/conf/DataSource.groovy b/bbb-lti/grails-app/conf/DataSource.groovy
index f961ac941f19de1304bdf53319c79d3a39cf6517..0a88abb5c21c1344fd4da4b29080c8512a89515c 100644
--- a/bbb-lti/grails-app/conf/DataSource.groovy
+++ b/bbb-lti/grails-app/conf/DataSource.groovy
@@ -15,7 +15,6 @@
     You should have received a copy of the GNU Lesser General Public License along
     with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 */    
-
 dataSource {
     pooled = true
     jmxExport = true
diff --git a/bbb-lti/grails-app/conf/UrlMappings.groovy b/bbb-lti/grails-app/conf/UrlMappings.groovy
index 1eb88de3773996e3e7ce21d6b4ff00fb6d6b2bc2..beb1333239d1d8dac58ad37aac9abc7b0e78f77f 100644
--- a/bbb-lti/grails-app/conf/UrlMappings.groovy
+++ b/bbb-lti/grails-app/conf/UrlMappings.groovy
@@ -15,11 +15,10 @@
     You should have received a copy of the GNU Lesser General Public License along
     with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 */    
-
 class UrlMappings {
 
 	static mappings = {
-        "/$controller/$action?/$id"{
+        "/$controller/$action?/$id?(.$format)?"{
             constraints {
                 // apply constraints here
             }
diff --git a/bbb-lti/grails-app/conf/lti.properties b/bbb-lti/grails-app/conf/lti.properties
index 1580d2f149cb22317b2ab98533e7a870b5d87dd6..830ecfb8a1719e12b47658ed553ad0d0c2c40c00 100644
--- a/bbb-lti/grails-app/conf/lti.properties
+++ b/bbb-lti/grails-app/conf/lti.properties
@@ -21,8 +21,10 @@
 # BigBlueButton integration information
 #----------------------------------------------------
 # This URL is where the BBB client is accessible. 
+#bigbluebuttonURL=http://test-install.blindsidenetworks.com/bigbluebutton
 bigbluebuttonURL=http://localhost/bigbluebutton
 # Salt which is used by 3rd-party apps to authenticate api calls
+#bigbluebuttonSalt=8cd8ef52e8e101574e400365b55e11a6
 bigbluebuttonSalt=bbb_salt
 
 # LTI basic information
diff --git a/bbb-lti/grails-app/controllers/ToolController.groovy b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy
similarity index 84%
rename from bbb-lti/grails-app/controllers/ToolController.groovy
rename to bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy
index bf03b7a6a6b9137ec52e93574c6fa9138fb9e98a..b4ccbac148099a6ff97d1a38c39b66628a7ecec0 100644
--- a/bbb-lti/grails-app/controllers/ToolController.groovy
+++ b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy
@@ -15,6 +15,7 @@
     You should have received a copy of the GNU Lesser General Public License along
     with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 */
+package org.bigbluebutton
 
 import java.util.ArrayList
 import java.util.HashMap
@@ -29,27 +30,29 @@ import net.oauth.signature.OAuthSignatureMethod
 import net.oauth.signature.HMAC_SHA1
 import org.bigbluebutton.lti.Parameter
 
-import BigbluebuttonService
-import LtiService
-
 class ToolController {
     private static final String CONTROLLER_NAME = 'ToolController'
     private static final String RESP_CODE_SUCCESS = 'SUCCESS'
     private static final String RESP_CODE_FAILED = 'FAILED'
     private static final String REQUEST_METHOD = "request_method";
-    
+
     LtiService ltiService
     BigbluebuttonService bigbluebuttonService
-    
-    def index = {
+
+    def test() {
+        ltiService.logParameters(params)
+        render(text: "<xml></xml>", contentType: "text/xml", encoding: "UTF-8")
+    }
+
+    def index() {
         if( ltiService.consumerMap == null) ltiService.initConsumerMap()
         log.debug CONTROLLER_NAME + "#index"
 
         setLocalization(params)
-        
+
         params.put(REQUEST_METHOD, request.getMethod().toUpperCase())
         ltiService.logParameters(params)
-        
+
         if( request.post ){
             Map<String, String> result = new HashMap<String, String>()
             ArrayList<String> missingParams = new ArrayList<String>()
@@ -62,7 +65,7 @@ class ToolController {
                     log.debug "Found consumer with key " + consumer.get("key") //+ " and sharedSecret " + consumer.get("secret")
                     if (checkValidSignature(params.get(REQUEST_METHOD), ltiService.endPoint, consumer.get("secret"), sanitizedParams, params.get(Parameter.OAUTH_SIGNATURE))) {
                         log.debug  "The message has a valid signature."
-                        
+
                         if( !"extended".equals(ltiService.mode) ) {
                             log.debug  "LTI service running in simple mode."
                             result = doJoinMeeting(params)
@@ -73,18 +76,18 @@ class ToolController {
                                 result = doJoinMeeting(params)
                             }
                         }
-                        
+
                     } else {
                         log.debug  "The message has NOT a valid signature."
                         result.put("resultMessageKey", "InvalidSignature")
                         result.put("resultMessage", "Invalid signature (" + params.get(Parameter.OAUTH_SIGNATURE) + ").")
                     }
-                    
+
                 } else {
                     result.put("resultMessageKey", "ConsumerNotFound")
                     result.put("resultMessage", "Consumer with id = " + params.get(Parameter.CONSUMER_ID) + " was not found.")
                 }
-    
+
             } else {
                 String missingStr = ""
                 for(String str:missingParams) {
@@ -121,7 +124,7 @@ class ToolController {
         }
     }
 
-    def join = {
+    def join() {
         if( ltiService.consumerMap == null) ltiService.initConsumerMap()
         log.debug CONTROLLER_NAME + "#join"
         Map<String, String> result
@@ -136,7 +139,7 @@ class ToolController {
             result = new HashMap<String, String>()
             result.put("resultMessageKey", "InvalidSession")
             result.put("resultMessage", "Invalid session. User can not execute this action.")
-        } 
+        }
 
         if( result.containsKey("resultMessageKey")) {
             log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
@@ -145,7 +148,7 @@ class ToolController {
 
     }
 
-    def publish = {
+    def publish() {
         log.debug CONTROLLER_NAME + "#publish"
         Map<String, String> result
 
@@ -162,10 +165,10 @@ class ToolController {
         } else {
             log.debug "params: " + params
             log.debug "sessionParams: " + sessionParams
-            
+
             //Execute the publish command
             result = bigbluebuttonService.doPublishRecordings(params)
-        } 
+        }
 
         if( result.containsKey("resultMessageKey")) {
             log.debug "Error [resultMessageKey:'" + result.get("resultMessageKey") + "', resultMessage:'" + result.get("resultMessage") + "']"
@@ -182,14 +185,14 @@ class ToolController {
                 /// Add duration
                 recording.put("duration", duration )
             }
-            
+
             render(view: "index", model: ['params': sessionParams, 'recordingList': recordings, 'ismoderator': bigbluebuttonService.isModerator(sessionParams)])
 
         }
 
     }
 
-    def delete = {
+    def delete() {
         log.debug CONTROLLER_NAME + "#delete"
         Map<String, String> result
 
@@ -206,7 +209,7 @@ class ToolController {
         } else {
             log.debug "params: " + params
             log.debug "sessionParams: " + sessionParams
-            
+
             //Execute the delete command
             result = bigbluebuttonService.doDeleteRecordings(params)
         }
@@ -242,23 +245,23 @@ class ToolController {
             session['org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE'] = new Locale(localeCodes[0], localeCodes[1])
         else
             session['org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE'] = new Locale(localeCodes[0])
-                    
+
         log.debug "Locale has been set to " + locale
 
     }
 
     private Object doJoinMeeting(params) {
         Map<String, String> result = new HashMap<String, String>()
-                    
+
         setLocalization(params)
         String welcome = message(code: "bigbluebutton.welcome.header", args: ["\"{0}\"", "\"{1}\""]) + "<br>"
         log.debug "Localized default welcome message: [" + welcome + "]"
 
-		// Check for [custom_]welcome parameter being passed from the LTI
-		if (params.get(Parameter.CUSTOM_WELCOME) != null) {
-			welcome = params.get(Parameter.CUSTOM_WELCOME) + "<br>"
-			log.debug "Overriding default welcome message with: [" + welcome + "]"
-		}
+        // Check for [custom_]welcome parameter being passed from the LTI
+        if (params.get(Parameter.CUSTOM_WELCOME) != null) {
+            welcome = params.get(Parameter.CUSTOM_WELCOME) + "<br>"
+            log.debug "Overriding default welcome message with: [" + welcome + "]"
+        }
 
         if ( Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) ) {
             welcome += "<br><b>" + message(code: "bigbluebutton.welcome.record") + "</b><br>"
@@ -266,7 +269,9 @@ class ToolController {
         }
 
         if ( Integer.parseInt(params.get(Parameter.CUSTOM_DURATION)) > 0 ) {
-            welcome += "<br><b>" + message(code: "bigbluebutton.welcome.duration", args: [params.get(Parameter.CUSTOM_DURATION)]) + "</b><br>"
+            welcome += "<br><b>" + message(code: "bigbluebutton.welcome.duration", args: [
+                params.get(Parameter.CUSTOM_DURATION)
+            ]) + "</b><br>"
             log.debug "Adding duration warning to welcome message, welcome is now: [" + welcome + "]"
         }
 
@@ -275,24 +280,24 @@ class ToolController {
 
         String destinationURL = bigbluebuttonService.getJoinURL(params, welcome, ltiService.mode)
         log.debug "redirecting to " + destinationURL
-                    
+
         if( destinationURL != null ) {
             redirect(url:destinationURL)
         } else {
             result.put("resultMessageKey", "BigBlueButtonServerError")
             result.put("resultMessage", "The join could not be completed")
         }
-        
+
         return result
     }
-        
+
     /**
      * Assemble all parameters passed that is required to sign the request.
      * @param the HTTP request parameters
      * @return the key:val pairs needed for Basic LTI
      */
     private Properties sanitizePrametersForBaseString(Object params) {
-        
+
         Properties reqProp = new Properties();
         for (String key : ((Map<String, String>)params).keySet()) {
             if (key == "action" || key == "controller") {
@@ -348,11 +353,11 @@ class ToolController {
      * @return - TRUE if the signatures matches the calculated signature
      */
     private boolean checkValidSignature(String method, String URL, String conSecret, Object postProp, String signature) {
-		log.debug( "Starting checkValidSignature()" )
+        log.debug( "Starting checkValidSignature()" )
         OAuthMessage oam = new OAuthMessage(method, URL, ((Properties)postProp).entrySet())
-		log.debug( "OAuthMessage oam = " + oam.toString() )
+        log.debug( "OAuthMessage oam = " + oam.toString() )
         HMAC_SHA1 hmac = new HMAC_SHA1()
-		log.debug( "HMAC_SHA1 hmac = " + hmac.toString() )
+        log.debug( "HMAC_SHA1 hmac = " + hmac.toString() )
         hmac.setConsumerSecret(conSecret)
 
         log.debug("Base Message String = [ " + hmac.getBaseString(oam) + " ]\n")
@@ -360,36 +365,33 @@ class ToolController {
         log.debug("Calculated: " + calculatedSignature + " Received: " + signature)
         return calculatedSignature.equals(signature)
     }
-    
+
     private String getCartridgeXML(){
         def cartridge = '' +
-        '<?xml version="1.0" encoding="UTF-8"?>' +
-        '<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"' +
-        '       xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"' +
-        '       xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"' +
-        '       xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"' +
-        '       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"' +
-        '       xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd' +
-        '                             http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd' +
-        '                             http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd' +
-        '                             http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">' +
-        '    <blti:title>BigBlueButton</blti:title>' +
-        '    <blti:description>Single Sign On into BigBlueButton</blti:description>' +
-        '    <blti:launch_url>' + ltiService.retrieveBasicLtiEndpoint() + '</blti:launch_url>' +
-        '    <blti:icon>' + ltiService.retrieveIconEndpoint() + '</blti:icon>' +
-        '    <blti:vendor>' +
-        '        <lticp:code>BBB</lticp:code>' +
-        '        <lticp:name>BigBlueButton</lticp:name>' +
-        '        <lticp:description>Open source web conferencing system for distance learning.</lticp:description>' +
-        '        <lticp:url>http://www.bigbluebutton.org/</lticp:url>' +
-        '    </blti:vendor>' +
-        '    <cartridge_bundle identifierref="BLTI001_Bundle"/>' +
-        '    <cartridge_icon identifierref="BLTI001_Icon"/>' +
-        '</cartridge_basiclti_link>'
-        
+                '<?xml version="1.0" encoding="UTF-8"?>' +
+                '<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"' +
+                '       xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"' +
+                '       xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"' +
+                '       xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"' +
+                '       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"' +
+                '       xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd' +
+                '                             http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd' +
+                '                             http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd' +
+                '                             http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">' +
+                '    <blti:title>BigBlueButton</blti:title>' +
+                '    <blti:description>Single Sign On into BigBlueButton</blti:description>' +
+                '    <blti:launch_url>' + ltiService.retrieveBasicLtiEndpoint() + '</blti:launch_url>' +
+                '    <blti:icon>' + ltiService.retrieveIconEndpoint() + '</blti:icon>' +
+                '    <blti:vendor>' +
+                '        <lticp:code>BBB</lticp:code>' +
+                '        <lticp:name>BigBlueButton</lticp:name>' +
+                '        <lticp:description>Open source web conferencing system for distance learning.</lticp:description>' +
+                '        <lticp:url>http://www.bigbluebutton.org/</lticp:url>' +
+                '    </blti:vendor>' +
+                '    <cartridge_bundle identifierref="BLTI001_Bundle"/>' +
+                '    <cartridge_icon identifierref="BLTI001_Icon"/>' +
+                '</cartridge_basiclti_link>'
+
         return cartridge
-        
     }
-
-
 }
diff --git a/bbb-lti/grails-app/services/BigbluebuttonService.groovy b/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy
similarity index 91%
rename from bbb-lti/grails-app/services/BigbluebuttonService.groovy
rename to bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy
index 8bab7e24006ffb81fdc36060446d6c17e3dcb85c..63a9c9c2c77e6a3f53559be350f22a43d6181166 100644
--- a/bbb-lti/grails-app/services/BigbluebuttonService.groovy
+++ b/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy
@@ -15,40 +15,42 @@
     You should have received a copy of the GNU Lesser General Public License along
     with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 */
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.StringReader;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-import org.apache.commons.codec.digest.DigestUtils;
+package org.bigbluebutton
+
+import java.io.BufferedReader
+import java.io.IOException
+import java.io.InputStreamReader
+import java.io.StringReader
+import java.net.HttpURLConnection
+import java.net.URL
+import java.text.MessageFormat
+import java.util.ArrayList
+import java.util.HashMap
+import java.util.List
+import java.util.Map
+import java.util.Random
+
+import javax.xml.parsers.DocumentBuilder
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.parsers.ParserConfigurationException
+
+import org.w3c.dom.Document
+import org.w3c.dom.Node
+import org.w3c.dom.NodeList
+import org.xml.sax.InputSource
+import org.xml.sax.SAXException
+
+import org.apache.commons.codec.digest.DigestUtils
 
 import org.bigbluebutton.api.Proxy
 import org.bigbluebutton.lti.Role
 import org.bigbluebutton.lti.Parameter
 
+import grails.transaction.Transactional
+
+@Transactional
 class BigbluebuttonService {
 
-    boolean transactional = false
-    
     def url = "http://test-install.blindsidenetworks.com/bigbluebutton"
     def salt = "8cd8ef52e8e101574e400365b55e11a6"
 
@@ -64,19 +66,19 @@ class BigbluebuttonService {
         } catch (ParserConfigurationException e) {
             logger.error("Failed to initialise BaseProxy", e)
         }
-        
+
         //Instantiate bbbProxy and initialize it with default url and salt
         bbbProxy = new Proxy(url, salt)
-    
+
     }
-    
+
     public String getJoinURL(params, welcome, mode){
         //Set the injected values
         if( !url.equals(bbbProxy.url) && !url.equals("") ) bbbProxy.setUrl(url)
         if( !salt.equals(bbbProxy.salt) && !salt.equals("") ) bbbProxy.setSalt(salt)
 
         String joinURL = null
-        
+
         String meetingName = getValidatedMeetingName(params.get(Parameter.RESOURCE_LINK_TITLE))
         String meetingID = getValidatedMeetingId(params.get(Parameter.RESOURCE_LINK_ID), params.get(Parameter.CONSUMER_ID))
         String attendeePW = DigestUtils.shaHex("ap" + params.get(Parameter.RESOURCE_LINK_ID) + params.get(Parameter.CONSUMER_ID))
@@ -86,7 +88,7 @@ class BigbluebuttonService {
         String userFullName = getValidatedUserFullName(params, isModerator)
         String courseTitle = getValidatedCourseTitle(params.get(Parameter.COURSE_TITLE))
         String userID = getValidatedUserId(params.get(Parameter.USER_ID))
-        
+
         Integer voiceBridge = 0
         String record = false
         Integer duration = 0
@@ -95,12 +97,12 @@ class BigbluebuttonService {
             record = getValidatedBBBRecord(params.get(Parameter.CUSTOM_RECORD))
             duration = getValidatedBBBDuration(params.get(Parameter.CUSTOM_DURATION))
         }
-        
+
         String[] values = [meetingName, courseTitle]
         String welcomeMsg = MessageFormat.format(welcome, values)
-        
+
         String meta = getMonitoringMetaData(params)
-        
+
         String createURL = getCreateURL( meetingName, meetingID, attendeePW, moderatorPW, welcomeMsg, voiceBridge, logoutURL, record, duration, meta )
         log.debug "createURL: " + createURL
         Map<String, Object> createResponse = doAPICall(createURL)
@@ -110,22 +112,21 @@ class BigbluebuttonService {
             String returnCode = (String) createResponse.get("returncode")
             String messageKey = (String) createResponse.get("messageKey")
             if ( Proxy.APIRESPONSE_SUCCESS.equals(returnCode) ||
-                (Proxy.APIRESPONSE_FAILED.equals(returnCode) &&  (Proxy.MESSAGEKEY_IDNOTUNIQUE.equals(messageKey) || Proxy.MESSAGEKEY_DUPLICATEWARNING.equals(messageKey)) ) ){
-                joinURL = bbbProxy.getJoinURL( userFullName, meetingID, isModerator? moderatorPW: attendeePW, (String) createResponse.get("createTime"), userID);
+                (Proxy.APIRESPONSE_FAILED.equals(returnCode) && (Proxy.MESSAGEKEY_IDNOTUNIQUE.equals(messageKey) || Proxy.MESSAGEKEY_DUPLICATEWARNING.equals(messageKey)) ) ){
+                    joinURL = bbbProxy.getJoinURL( userFullName, meetingID, isModerator? moderatorPW: attendeePW, (String) createResponse.get("createTime"), userID);
             }
         }
 
         return joinURL
-        
     }
-    
+
     public Object getRecordings(params){
         //Set the injected values
         if( !url.equals(bbbProxy.url) && !url.equals("") ) bbbProxy.setUrl(url)
         if( !salt.equals(bbbProxy.salt) && !salt.equals("") ) bbbProxy.setSalt(salt)
 
         String meetingID = getValidatedMeetingId(params.get(Parameter.RESOURCE_LINK_ID), params.get(Parameter.CONSUMER_ID))
-        
+
         String recordingsURL = bbbProxy.getGetRecordingsURL( meetingID )
         log.debug "recordingsURL: " + recordingsURL
         Map<String, Object> recordings = doAPICall(recordingsURL)
@@ -135,7 +136,7 @@ class BigbluebuttonService {
             String messageKey = (String) recordings.get("messageKey")
             if ( Proxy.APIRESPONSE_SUCCESS.equals(returnCode) && messageKey == null ){
                 return recordings.get("recordings")
-            } 
+            }
         }
 
         return null
@@ -147,9 +148,9 @@ class BigbluebuttonService {
         if( !salt.equals(bbbProxy.salt) && !salt.equals("") ) bbbProxy.setSalt(salt)
 
         Map<String, Object> result
-        
+
         String recordingId = getValidatedBBBRecordingId(params.get(Parameter.BBB_RECORDING_ID))
-        
+
         if( !recordingId.equals("") ){
             String deleteRecordingsURL = bbbProxy.getDeleteRecordingsURL( recordingId )
             log.debug "deleteRecordingsURL: " + deleteRecordingsURL
@@ -162,17 +163,17 @@ class BigbluebuttonService {
 
         return result
     }
-    
+
     public Object doPublishRecordings(params){
         //Set the injected values
         if( !url.equals(bbbProxy.url) && !url.equals("") ) bbbProxy.setUrl(url)
         if( !salt.equals(bbbProxy.salt) && !salt.equals("") ) bbbProxy.setSalt(salt)
-        
+
         Map<String, Object> result
 
         String recordingId = getValidatedBBBRecordingId(params.get(Parameter.BBB_RECORDING_ID))
         String publish = getValidatedBBBRecordingPublished(params.get(Parameter.BBB_RECORDING_PUBLISHED))
-        
+
         if( !recordingId.equals("") ){
             String publishRecordingsURL = bbbProxy.getPublishRecordingsURL( recordingId, "true".equals(publish)?"false":"true" )
             log.debug "publishRecordingsURL: " + publishRecordingsURL
@@ -185,23 +186,23 @@ class BigbluebuttonService {
 
         return result
     }
-    
+
     public boolean isModerator(params) {
         boolean isModerator = params.get(Parameter.ROLES) != null? Role.isModerator(params.get(Parameter.ROLES)): true
         return isModerator
     }
-    
+
     private String getCreateURL(String name, String meetingID, String attendeePW, String moderatorPW, String welcome, Integer voiceBridge, String logoutURL, String record, Integer duration, String meta ) {
         voiceBridge = ( voiceBridge == null || voiceBridge == 0 )? 70000 + new Random(System.currentTimeMillis()).nextInt(10000): voiceBridge;
 
         String url = bbbProxy.getCreateURL(name, meetingID, attendeePW, moderatorPW, welcome, "", voiceBridge.toString(), "", logoutURL, "", record, duration.toString(), meta );
         return url;
     }
-    
+
     private String getValidatedMeetingName(String meetingName){
         return (meetingName == null || meetingName == "")? "Meeting": meetingName
     }
-    
+
     private String getValidatedMeetingId(String resourceId, String consumerId){
         return DigestUtils.shaHex(resourceId + consumerId)
     }
@@ -209,7 +210,7 @@ class BigbluebuttonService {
     private String getValidatedLogoutURL(String logoutURL){
         return (logoutURL == null)? "": logoutURL
     }
-    
+
     private String getValidatedUserFullName(params, boolean isModerator){
         String userFullName = params.get(Parameter.USER_FULL_NAME)
         String userFirstName = params.get(Parameter.USER_FIRSTNAME)
@@ -226,7 +227,7 @@ class BigbluebuttonService {
                 userFullName = isModerator? "Moderator" : "Attendee"
             }
         }
-        
+
         return userFullName
     }
 
@@ -237,15 +238,15 @@ class BigbluebuttonService {
     private String getValidatedUserId(String userId){
         return (userId == null)? "": userId
     }
-    
+
     private Integer getValidatedBBBVoiceBridge(String voiceBridge){
         return (voiceBridge != null )? voiceBridge.toInteger(): 0
     }
-    
+
     private String getValidatedBBBRecord(String record){
         return Boolean.valueOf(record).toString();
     }
-    
+
     private Integer getValidatedBBBDuration(String duration){
         return (duration != null )? duration.toInteger(): 0
     }
@@ -257,7 +258,7 @@ class BigbluebuttonService {
     private String getValidatedBBBRecordingPublished(String published){
         return (published != null && published.equals("true") )? "true": "false"
     }
-    
+
     private String getMonitoringMetaData(params){
         String meta
 
@@ -269,10 +270,10 @@ class BigbluebuttonService {
         meta += "&meta_contextId=" + bbbProxy.getStringEncoded(params.get(Parameter.COURSE_ID) == null? "": params.get(Parameter.COURSE_ID))
         meta += "&meta_contextActivity=" + bbbProxy.getStringEncoded(params.get(Parameter.RESOURCE_LINK_TITLE) == null? "": params.get(Parameter.RESOURCE_LINK_TITLE))
         meta += "&meta_contextActivityDescription=" + bbbProxy.getStringEncoded(params.get(Parameter.RESOURCE_LINK_DESCRIPTION) == null? "": params.get(Parameter.RESOURCE_LINK_DESCRIPTION))
-        
+
         return meta
     }
-    
+
     /** Make an API call */
     private Map<String, Object> doAPICall(String query) {
         StringBuilder urlStr = new StringBuilder(query);
@@ -280,7 +281,7 @@ class BigbluebuttonService {
         try {
             // open connection
             //log.debug("doAPICall.call: " + query );
-            
+
             URL url = new URL(urlStr.toString());
             HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
             httpConnection.setUseCaches(false);
@@ -300,14 +301,16 @@ class BigbluebuttonService {
                     String line = reader.readLine();
                     while (line != null) {
                         if( !line.startsWith("<?xml version=\"1.0\"?>"))
-                            xml.append(line.trim());
+                        xml.append(line.trim());
                         line = reader.readLine();
                     }
                 } finally {
-                    if (reader != null)
-                        reader.close();
-                    if (isr != null)
-                        isr.close();
+                    if ( reader != null ) {
+                        reader.close()
+                    }
+                    if ( isr != null ) {
+                        isr.close()
+                    }
                 }
                 httpConnection.disconnect();
 
@@ -316,13 +319,13 @@ class BigbluebuttonService {
                 //Patch to fix the NaN error
                 String stringXml = xml.toString();
                 stringXml = stringXml.replaceAll(">.\\s+?<", "><");
-                
+
                 Document dom = null;
                 dom = docBuilder.parse(new InputSource( new StringReader(stringXml)));
-                
+
                 Map<String, Object> response = getNodesAsMap(dom, "response");
                 //log.debug("doAPICall.responseMap: " + response);
-                
+
                 String returnCode = (String) response.get("returncode");
                 if (Proxy.APIRESPONSE_FAILED.equals(returnCode)) {
                     log.debug("doAPICall." + (String) response.get("messageKey") + ": Message=" + (String) response.get("message"));
@@ -344,7 +347,7 @@ class BigbluebuttonService {
             log.debug("doAPICall.Exception: Message=" + e.getMessage());
         }
     }
-    
+
     /** Get all nodes under the specified element tag name as a Java map */
     protected Map<String, Object> getNodesAsMap(Document dom, String elementTagName) {
         Node firstNode = dom.getElementsByTagName(elementTagName).item(0);
@@ -361,12 +364,12 @@ class BigbluebuttonService {
                     && ( node.getChildNodes().item(0).getNodeType() == org.w3c.dom.Node.TEXT_NODE || node.getChildNodes().item(0).getNodeType() == org.w3c.dom.Node.CDATA_SECTION_NODE) ) {
                 String nodeValue = node.getTextContent();
                 map.put(nodeName, nodeValue != null ? nodeValue.trim() : null);
-            
+
             } else if (node.getChildNodes().getLength() == 0
                     && node.getNodeType() != org.w3c.dom.Node.TEXT_NODE
                     && node.getNodeType() != org.w3c.dom.Node.CDATA_SECTION_NODE) {
                 map.put(nodeName, "");
-            
+
             } else if ( node.getChildNodes().getLength() >= 1
                     && node.getChildNodes().item(0).getChildNodes().item(0).getNodeType() != org.w3c.dom.Node.TEXT_NODE
                     && node.getChildNodes().item(0).getChildNodes().item(0).getNodeType() != org.w3c.dom.Node.CDATA_SECTION_NODE ) {
@@ -377,12 +380,11 @@ class BigbluebuttonService {
                     list.add(processNode(n));
                 }
                 map.put(nodeName, list);
-            
+
             } else {
                 map.put(nodeName, processNode(node));
             }
         }
         return map;
     }
-
 }
diff --git a/bbb-lti/grails-app/services/LtiService.groovy b/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy
similarity index 84%
rename from bbb-lti/grails-app/services/LtiService.groovy
rename to bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy
index 4072dc9231ed2499217a4bb46f2581d5d88f1523..8254cba65448b7d74545eee2bb1c3ee923005186 100644
--- a/bbb-lti/grails-app/services/LtiService.groovy
+++ b/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy
@@ -15,23 +15,23 @@
     You should have received a copy of the GNU Lesser General Public License along
     with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 */
+package org.bigbluebutton
 
-import java.util.Map;
-
+import grails.transaction.Transactional
+import java.util.Map
 import javax.crypto.spec.SecretKeySpec
 import javax.crypto.Mac
 import org.apache.commons.codec.binary.Base64
 
+@Transactional
 class LtiService {
 
-    boolean transactional = false
-    
     def endPoint = "http://localhost/lti/tool"
     def consumers = "demo:welcome"
     def mode = "simple"
 
     Map<String, String> consumerMap
-    
+
     def retrieveIconEndpoint() {
         return endPoint.replaceFirst("tool", "images/icon.ico")
     }
@@ -39,16 +39,16 @@ class LtiService {
     def retrieveBasicLtiEndpoint() {
         return endPoint
     }
-    
+
     private Map<String, String> getConsumer(consumerId) {
         Map<String, String> consumer = null
-        
+
         if( this.consumerMap.containsKey(consumerId) ){
             consumer = new HashMap<String, String>()
-            consumer.put("key", consumerId);
+            consumer.put("key", consumerId)
             consumer.put("secret",  this.consumerMap.get(consumerId))
         }
-        
+
         return consumer
     }
 
@@ -63,25 +63,22 @@ class LtiService {
                 this.consumerMap.put(consumer[0], consumer[1])
             }
         }
-        
     }
-    
-    public String sign(String sharedSecret, String data) throws Exception
-    {
+
+    public String sign(String sharedSecret, String data) throws Exception {
         Mac mac = setKey(sharedSecret)
-        
+
         // Signed String must be BASE64 encoded.
-        byte[] signBytes = mac.doFinal(data.getBytes("UTF8"));
-        String signature = encodeBase64(signBytes);
+        byte[] signBytes = mac.doFinal(data.getBytes("UTF8"))
+        String signature = encodeBase64(signBytes)
         return signature;
     }
-    
-    private Mac setKey(String sharedSecret) throws Exception
-    {
-        Mac mac = Mac.getInstance("HmacSHA1");
-        byte[] keyBytes = sharedSecret.getBytes("UTF8");
-        SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");
-        mac.init(signingKey);
+
+    private Mac setKey(String sharedSecret) throws Exception {
+        Mac mac = Mac.getInstance("HmacSHA1")
+        byte[] keyBytes = sharedSecret.getBytes("UTF8")
+        SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1")
+        mac.init(signingKey)
         return mac
     }
 
diff --git a/bbb-lti/grails-app/views/index.gsp b/bbb-lti/grails-app/views/index.gsp
index ee53f428082703587142aab87b4ad24ad8ff6263..cf4c0b438bba152d5f21b8f552794c021859be87 100644
--- a/bbb-lti/grails-app/views/index.gsp
+++ b/bbb-lti/grails-app/views/index.gsp
@@ -81,7 +81,6 @@
 		</style>
 	</head>
 	<body>
-<!-- index  -->
 		<a href="#page-body" class="skip"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
 		<div id="status" role="complementary">
 			<h1>Application Status</h1>
diff --git a/bbb-lti/grails-app/views/layouts/main.gsp b/bbb-lti/grails-app/views/layouts/main.gsp
index f79e49285efcfc6be1cfbd0c1bb8015812b3a6c4..c1d070a66d21cd667af6e7d7ab6c3f3b7c933675 100644
--- a/bbb-lti/grails-app/views/layouts/main.gsp
+++ b/bbb-lti/grails-app/views/layouts/main.gsp
@@ -1,32 +1,28 @@
-<!DOCTYPE html>
-<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
-<!--[if IE 7 ]>    <html lang="en" class="no-js ie7"> <![endif]-->
-<!--[if IE 8 ]>    <html lang="en" class="no-js ie8"> <![endif]-->
-<!--[if IE 9 ]>    <html lang="en" class="no-js ie9"> <![endif]-->
-<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"><!--<![endif]-->
-	<head>
-		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
-		<title><g:message code="tool.view.title" /></title>
-		<meta name="viewport" content="width=device-width, initial-scale=1.0">
-		<link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon">
-		<link rel="apple-touch-icon" href="${resource(dir: 'images', file: 'apple-touch-icon.png')}">
-		<link rel="apple-touch-icon" sizes="114x114" href="${resource(dir: 'images', file: 'apple-touch-icon-retina.png')}">
-		<link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}" type="text/css">
-		<link rel="stylesheet" href="${resource(dir: 'css', file: 'mobile.css')}" type="text/css">
-		<g:layoutHead/>
-		<g:javascript library="application"/>		
-		<r:layoutResources />
-	</head>
-	<body>
-        <div id="spinner" class="spinner" style="display:none;">
-            <img src="${resource(dir:'images',file:'spinner.gif')}" alt="Spinner" />
-        </div>
-        <div class="logo" id="logo" role="banner"><img src="${resource(dir: 'images', file: 'bbb_logo.jpg')}" alt="<g:message code="tool.view.app" />" /></div>
-        <g:layoutBody />
-
-		<div class="footer" role="contentinfo"></div>
-		<div id="spinner" class="spinner" style="display:none;"><g:message code="spinner.alt" default="Loading&hellip;"/></div>
-		<r:layoutResources />
-	</body>
-</html>
+<!DOCTYPE html>
+<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
+<!--[if IE 7 ]>    <html lang="en" class="no-js ie7"> <![endif]-->
+<!--[if IE 8 ]>    <html lang="en" class="no-js ie8"> <![endif]-->
+<!--[if IE 9 ]>    <html lang="en" class="no-js ie9"> <![endif]-->
+<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"><!--<![endif]-->
+	<head>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+		<title><g:layoutTitle default="Grails"/></title>
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
+		<link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon">
+		<link rel="apple-touch-icon" href="${resource(dir: 'images', file: 'apple-touch-icon.png')}">
+		<link rel="apple-touch-icon" sizes="114x114" href="${resource(dir: 'images', file: 'apple-touch-icon-retina.png')}">
+		<link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}" type="text/css">
+		<link rel="stylesheet" href="${resource(dir: 'css', file: 'mobile.css')}" type="text/css">
+		<g:layoutHead/>
+		<g:javascript library="application"/>		
+		<r:layoutResources />
+	</head>
+	<body>
+		<div id="grailsLogo" role="banner"><a href="http://grails.org"><img src="${resource(dir: 'images', file: 'grails_logo.png')}" alt="Grails"/></a></div>
+		<g:layoutBody/>
+		<div class="footer" role="contentinfo"></div>
+		<div id="spinner" class="spinner" style="display:none;"><g:message code="spinner.alt" default="Loading&hellip;"/></div>
+		<r:layoutResources />
+	</body>
+</html>
diff --git a/bbb-lti/test/unit/org/bigbluebutton/BigbluebuttonServiceSpec.groovy b/bbb-lti/test/unit/org/bigbluebutton/BigbluebuttonServiceSpec.groovy
new file mode 100644
index 0000000000000000000000000000000000000000..ca3664a6bceae6dc5073f567a8b7dd9d62c6c903
--- /dev/null
+++ b/bbb-lti/test/unit/org/bigbluebutton/BigbluebuttonServiceSpec.groovy
@@ -0,0 +1,20 @@
+package org.bigbluebutton
+
+import grails.test.mixin.TestFor
+import spock.lang.Specification
+
+/**
+ * See the API for {@link grails.test.mixin.services.ServiceUnitTestMixin} for usage instructions
+ */
+@TestFor(BigbluebuttonService)
+class BigbluebuttonServiceSpec extends Specification {
+
+    def setup() {
+    }
+
+    def cleanup() {
+    }
+
+    void "test something"() {
+    }
+}
diff --git a/bbb-lti/test/unit/org/bigbluebutton/LtiServiceSpec.groovy b/bbb-lti/test/unit/org/bigbluebutton/LtiServiceSpec.groovy
new file mode 100644
index 0000000000000000000000000000000000000000..88dcbb942d4b62f306af6cc11e50bc313969a013
--- /dev/null
+++ b/bbb-lti/test/unit/org/bigbluebutton/LtiServiceSpec.groovy
@@ -0,0 +1,20 @@
+package org.bigbluebutton
+
+import grails.test.mixin.TestFor
+import spock.lang.Specification
+
+/**
+ * See the API for {@link grails.test.mixin.services.ServiceUnitTestMixin} for usage instructions
+ */
+@TestFor(LtiService)
+class LtiServiceSpec extends Specification {
+
+    def setup() {
+    }
+
+    def cleanup() {
+    }
+
+    void "test something"() {
+    }
+}
diff --git a/bbb-lti/test/unit/org/bigbluebutton/ToolControllerSpec.groovy b/bbb-lti/test/unit/org/bigbluebutton/ToolControllerSpec.groovy
new file mode 100644
index 0000000000000000000000000000000000000000..9653d4b0717a05d24b1e993044608ca3b407382c
--- /dev/null
+++ b/bbb-lti/test/unit/org/bigbluebutton/ToolControllerSpec.groovy
@@ -0,0 +1,20 @@
+package org.bigbluebutton
+
+import grails.test.mixin.TestFor
+import spock.lang.Specification
+
+/**
+ * See the API for {@link grails.test.mixin.web.ControllerUnitTestMixin} for usage instructions
+ */
+@TestFor(ToolController)
+class ToolControllerSpec extends Specification {
+
+    def setup() {
+    }
+
+    def cleanup() {
+    }
+
+    void "test something"() {
+    }
+}
diff --git a/bbb-lti/web-app/js/application.js b/bbb-lti/web-app/js/application.js
index b2adb962e2013c0b9bb2f9635dc88f12f64a43bf..aa2a2021ec07165112f2fb0ab5bb1cfaa7b7e44c 100644
--- a/bbb-lti/web-app/js/application.js
+++ b/bbb-lti/web-app/js/application.js
@@ -1,3 +1,20 @@
+/*
+    BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+
+    Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+
+    This program is free software; you can redistribute it and/or modify it under the
+    terms of the GNU Lesser General Public License as published by the Free Software
+    Foundation; either version 3.0 of the License, or (at your option) any later
+    version.
+
+    BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+    PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License along
+    with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*/
 if (typeof jQuery !== 'undefined') {
 	(function($) {
 		$('#spinner').ajaxStart(function() {