bbb-lti 0.1: BigBlueButton LTI interface, Initial commit
Signed-off-by: jfederico <jesus@123it.ca>
85
bbb-lti/.classpath
Normal file
@ -0,0 +1,85 @@
|
||||
<?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>
|
7
bbb-lti/application.properties
Normal file
@ -0,0 +1,7 @@
|
||||
#utf-8
|
||||
#Wed Oct 10 08:34:02 PDT 2012
|
||||
app.version=0.1
|
||||
app.servlet.version=2.4
|
||||
app.grails.version=1.1.1
|
||||
plugins.hibernate=1.1.1
|
||||
app.name=lti
|
142
bbb-lti/build.xml
Normal file
@ -0,0 +1,142 @@
|
||||
<project xmlns:ivy="antlib:org.apache.ivy.ant" name="lti" default="test">
|
||||
<property environment="env"/>
|
||||
<property name="ivy.install.version" value="2.0.0" />
|
||||
<condition property="ivy.home" value="${env.IVY_HOME}">
|
||||
<isset property="env.IVY_HOME" />
|
||||
</condition>
|
||||
<property name="ivy.home" value="${user.home}/.ant" />
|
||||
<property name="ivy.jar.dir" value="${ivy.home}/lib" />
|
||||
<property name="ivy.jar.file" value="${ivy.jar.dir}/ivy-${ivy.install.version}.jar" />
|
||||
|
||||
<target name="download-ivy" unless="offline">
|
||||
<available file="${ivy.jar.file}" property="ivy.available"/>
|
||||
<antcall target="-download-ivy" />
|
||||
</target>
|
||||
|
||||
<target name="-download-ivy" unless="ivy.available">
|
||||
<mkdir dir="${ivy.jar.dir}"/>
|
||||
<!-- download Ivy from web site so that it can be used even without any special installation -->
|
||||
<get src="http://www.apache.org/dist/ant/ivy/${ivy.install.version}/apache-ivy-${ivy.install.version}-bin.zip"
|
||||
dest="${ivy.home}/ivy.zip" usetimestamp="true" verbose="true"/>
|
||||
<unzip src="${ivy.home}/ivy.zip" dest="${ivy.jar.dir}">
|
||||
<patternset>
|
||||
<include name="**/*.jar"/>
|
||||
</patternset>
|
||||
<mapper type="flatten"/>
|
||||
</unzip>
|
||||
</target>
|
||||
|
||||
<target name="init-ivy" depends="download-ivy" unless="ivy.lib.path">
|
||||
<!-- try to load ivy here from ivy home, in case the user has not already dropped
|
||||
it into ant's lib dir (note that the latter copy will always take precedence).
|
||||
We will not fail as long as local lib dir exists (it may be empty) and
|
||||
ivy is in at least one of ant's lib dir or the local lib dir. -->
|
||||
<path id="ivy.lib.path">
|
||||
<fileset dir="${ivy.jar.dir}" includes="*.jar"/>
|
||||
</path>
|
||||
<taskdef resource="org/apache/ivy/ant/antlib.xml"
|
||||
uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
|
||||
</target>
|
||||
|
||||
|
||||
<property name="lib.dir" value="${basedir}/lib"/>
|
||||
|
||||
<macrodef name="grails">
|
||||
<attribute name="script"/>
|
||||
<attribute name="args" default="" />
|
||||
<sequential>
|
||||
<grailsTask script="@{script}" args="@{args}" classpathref="grails.classpath">
|
||||
<compileClasspath refid="compile.classpath"/>
|
||||
<testClasspath refid="test.classpath"/>
|
||||
<runtimeClasspath refid="app.classpath"/>
|
||||
</grailsTask>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<!-- =================================
|
||||
target: resolve
|
||||
================================= -->
|
||||
<target name="-resolve" description="--> Retrieve dependencies with ivy" depends="init-ivy">
|
||||
<ivy:retrieve pattern="${lib.dir}/[conf]/[artifact]-[revision].[ext]"/>
|
||||
</target>
|
||||
|
||||
<target name="-init-grails" depends="-resolve">
|
||||
<path id="grails.classpath">
|
||||
<fileset dir="${lib.dir}/build"/>
|
||||
<fileset dir="${lib.dir}"/>
|
||||
</path>
|
||||
|
||||
<path id="compile.classpath">
|
||||
<fileset dir="${lib.dir}/compile"/>
|
||||
</path>
|
||||
|
||||
<path id="test.classpath">
|
||||
<fileset dir="${lib.dir}/test"/>
|
||||
</path>
|
||||
|
||||
<path id="app.classpath">
|
||||
<fileset dir="${lib.dir}/runtime"/>
|
||||
</path>
|
||||
|
||||
<taskdef name="grailsTask"
|
||||
classname="grails.ant.GrailsTask"
|
||||
classpathref="grails.classpath"/>
|
||||
</target>
|
||||
|
||||
<target name="deps-report" depends="-resolve" description="--> Generate report of module dependencies.">
|
||||
<ivy:report conf="*"/>
|
||||
</target>
|
||||
|
||||
<!-- =================================
|
||||
target: clean
|
||||
================================= -->
|
||||
<target name="clean" description="--> Cleans a Grails application">
|
||||
<delete failonerror="true">
|
||||
<fileset dir="${lib.dir}/build" includes="*/"/>
|
||||
<fileset dir="${lib.dir}/compile" includes="*/"/>
|
||||
<fileset dir="${lib.dir}/runtime" includes="*/"/>
|
||||
<fileset dir="${lib.dir}/test" includes="*/"/>
|
||||
</delete>
|
||||
<antcall target="--grails-clean"/>
|
||||
</target>
|
||||
|
||||
<!-- extra target to avoid errors on Windows because libs on classpath can not be deleted -->
|
||||
<target name="--grails-clean" depends="-init-grails">
|
||||
<grails script="Clean"/>
|
||||
</target>
|
||||
|
||||
<!-- =================================
|
||||
target: compile
|
||||
================================= -->
|
||||
<target name="compile" depends="-init-grails" description="--> Compiles a Grails application">
|
||||
<grails script="Compile"/>
|
||||
</target>
|
||||
|
||||
<!-- =================================
|
||||
target: war
|
||||
================================= -->
|
||||
<target name="war" depends="-init-grails" description="--> Creates a WAR of a Grails application">
|
||||
<grails script="War"/>
|
||||
</target>
|
||||
|
||||
<!-- =================================
|
||||
target: test
|
||||
================================= -->
|
||||
<target name="test" depends="-init-grails" description="--> Run a Grails applications unit tests">
|
||||
<grails script="TestApp"/>
|
||||
</target>
|
||||
|
||||
<!-- =================================
|
||||
target: run
|
||||
================================= -->
|
||||
<target name="run" depends="-init-grails" description="--> Runs a Grails application using embedded Jetty">
|
||||
<grails script="RunApp"/>
|
||||
</target>
|
||||
|
||||
<!-- =================================
|
||||
target: deploy
|
||||
================================= -->
|
||||
<target name="deploy" depends="war" description="--> The deploy target (initially empty)">
|
||||
<!-- TODO -->
|
||||
</target>
|
||||
</project>
|
9
bbb-lti/grails-app/conf/BootStrap.groovy
Normal file
@ -0,0 +1,9 @@
|
||||
class BootStrap {
|
||||
|
||||
def init = { servletContext ->
|
||||
log.debug "Bootstrapping bbb-lti"
|
||||
}
|
||||
|
||||
def destroy = {
|
||||
}
|
||||
}
|
75
bbb-lti/grails-app/conf/Config.groovy
Normal file
@ -0,0 +1,75 @@
|
||||
// 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"]
|
||||
|
||||
grails.config.locations = [ "classpath:lti.properties"]
|
||||
|
||||
// if(System.properties["${appName}.config.location"]) {
|
||||
// grails.config.locations << "file:" + System.properties["${appName}.config.location"]
|
||||
// }
|
||||
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'
|
||||
]
|
||||
// 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"
|
||||
|
||||
// enabled native2ascii conversion of i18n properties files
|
||||
grails.enable.native2ascii = true
|
||||
|
||||
// set per-environment serverURL stem for creating absolute links
|
||||
environments {
|
||||
production {
|
||||
grails.serverURL = "http://localhost:8080/${appName}"
|
||||
}
|
||||
development {
|
||||
grails.serverURL = "http://localhost:8080/${appName}"
|
||||
}
|
||||
test {
|
||||
grails.serverURL = "http://localhost:8080/${appName}"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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')
|
||||
}
|
||||
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'
|
||||
|
||||
warn 'org.mortbay.log'
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
33
bbb-lti/grails-app/conf/DataSource.groovy
Normal file
@ -0,0 +1,33 @@
|
||||
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='com.opensymphony.oscache.hibernate.OSCacheProvider'
|
||||
}
|
||||
// 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"
|
||||
url = "jdbc:hsqldb:mem:prodDb"
|
||||
}
|
||||
}
|
||||
}
|
11
bbb-lti/grails-app/conf/UrlMappings.groovy
Normal file
@ -0,0 +1,11 @@
|
||||
class UrlMappings {
|
||||
static mappings = {
|
||||
"/$controller/$action?/$id?"{
|
||||
constraints {
|
||||
// apply constraints here
|
||||
}
|
||||
}
|
||||
"/"(view:"/index")
|
||||
"500"(view:'/error')
|
||||
}
|
||||
}
|
17
bbb-lti/grails-app/conf/lti.properties
Normal file
@ -0,0 +1,17 @@
|
||||
#
|
||||
# These are the default properites for the BigBlueButton LTI interface
|
||||
|
||||
|
||||
#----------------------------------------------------
|
||||
# This URL is where the BBB client is accessible. When a user sucessfully
|
||||
# enters a name and password, she is redirected here to load the client.
|
||||
bigbluebuttonAPIURL=http://192.168.0.153/bigbluebutton/api
|
||||
# Salt which is used by 3rd-party apps to authenticate api calls
|
||||
bigbluebuttonSecuritySalt=e1f2f284119d5754cef6c80ba1e2f393
|
||||
|
||||
#----------------------------------------------------
|
||||
# Inject values into grails service beans
|
||||
beans.bigbluebuttonService.url=${bigbluebuttonAPIURL}
|
||||
beans.bigbluebuttonService.salt=${bigbluebuttonSecuritySalt}
|
||||
|
||||
|
4
bbb-lti/grails-app/conf/spring/resources.groovy
Normal file
@ -0,0 +1,4 @@
|
||||
// Place your Spring DSL code here
|
||||
beans = {
|
||||
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
package org.bigbluebutton.web.controllers
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
import java.util.List
|
||||
import java.util.Map
|
||||
import java.util.Properties
|
||||
|
||||
import net.oauth.OAuthMessage
|
||||
import net.oauth.signature.OAuthSignatureMethod;
|
||||
import net.oauth.signature.HMAC_SHA1;
|
||||
|
||||
import org.bigbluebutton.web.services.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'
|
||||
|
||||
public static final String OAUTH_SIGNATURE = 'oauth_signature'
|
||||
public static final String CUSTOMER_ID = 'oauth_consumer_key'
|
||||
public static final String USER_FULL_NAME = 'lis_person_name_full'
|
||||
public static final String USER_LASTNAME = 'lis_person_name_family'
|
||||
public static final String USER_EMAIL = 'lis_person_contact_email_primary'
|
||||
public static final String USER_ID = 'lis_person_sourcedid'
|
||||
public static final String USER_FIRSTNAME = 'lis_person_name_given'
|
||||
public static final String COURSE_ID = 'context_id'
|
||||
|
||||
public static final String CUSTOM_USER_ID = 'custom_lis_person_sourcedid'
|
||||
|
||||
LtiService ltiService
|
||||
|
||||
def index = {
|
||||
log.debug CONTROLLER_NAME + "#index"
|
||||
|
||||
def resultMessageKey = "init"
|
||||
def resultMessage = "init"
|
||||
def success = false
|
||||
def customer
|
||||
ArrayList<String> missingParams = new ArrayList<String>()
|
||||
log.debug "Checking for required parameters"
|
||||
if (hasAllRequiredParams(params, missingParams)) {
|
||||
def sanitizedParams = sanitizePrametersForBaseString(params)
|
||||
|
||||
customer = getCustomer(params)
|
||||
if (customer != null) {
|
||||
log.debug "Found customer " + customer.get("customerId") + " with secretKey " + customer.get("secretKey")
|
||||
if (checkValidSignature(request.getMethod().toUpperCase(), retrieveBasicLtiEndpoint(), customer.get("secretKey"), sanitizedParams, params.get(OAUTH_SIGNATURE))) {
|
||||
if (hasValidStudentId(params, customer)) {
|
||||
// We have a valid signature. Mark this as successful.
|
||||
success = true
|
||||
|
||||
} else {
|
||||
resultMessageKey = 'InvalidStudentId'
|
||||
resultMessage = "Can not determine user because of missing student id or email."
|
||||
}
|
||||
|
||||
} else {
|
||||
resultMessageKey = 'InvalidSignature'
|
||||
resultMessage = "Invalid signature (" + params.get(OAUTH_SIGNATURE) + ")."
|
||||
log.debug resultMessage
|
||||
}
|
||||
|
||||
} else {
|
||||
resultMessageKey = 'CustomerNotFound'
|
||||
resultMessage = "Customer with id = " + params.get(CUSTOMER_ID) + " was not found."
|
||||
log.debug resultMessage
|
||||
}
|
||||
|
||||
} else {
|
||||
resultMessageKey = 'MissingRequiredParameter'
|
||||
String missingStr = ""
|
||||
for(String str:missingParams)
|
||||
missingStr += str + ", ";
|
||||
|
||||
resultMessage = "Missing parameters [$missingStr]"
|
||||
log.debug resultMessage
|
||||
}
|
||||
|
||||
|
||||
if (success == true) {
|
||||
//def returnUrl
|
||||
//if (customer.ePortfolioUrl.endsWith("/")) {
|
||||
// returnUrl = "${customer.ePortfolioUrl}${epcServerService.signOnUri}"
|
||||
//} else {
|
||||
// returnUrl = "${customer.ePortfolioUrl}/${epcServerService.signOnUri}"
|
||||
//}
|
||||
|
||||
String finalURL = "http://www.google.com"
|
||||
//String finalURL = returnUrl + "?act=single_signon&studentId=" +
|
||||
// URLEncoder.encode(accessLog.studentId, "UTF-8") + "&tocSecId=" +
|
||||
// URLEncoder.encode(accessLog.tocSectionId, "UTF-8") + "&courseId=" +
|
||||
// URLEncoder.encode(params.get(COURSE_ID), "UTF-8") + "&cus=" +
|
||||
// URLEncoder.encode( params.get(CUSTOMER_ID), "UTF-8") + "&studentField=LoginName" +
|
||||
// "&token=" + authToken
|
||||
|
||||
log.debug "redirecting to " + finalURL
|
||||
redirect(url:finalURL)
|
||||
|
||||
} else {
|
||||
log.debug "Error"
|
||||
|
||||
response.addHeader("Cache-Control", "no-cache")
|
||||
withFormat {
|
||||
xml {
|
||||
render(contentType:"text/xml") {
|
||||
response() {
|
||||
returncode(success)
|
||||
messageKey(resultMessageKey)
|
||||
message(resultMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def test = {
|
||||
log.debug CONTROLLER_NAME + "#index"
|
||||
|
||||
response.addHeader("Cache-Control", "no-cache")
|
||||
withFormat {
|
||||
xml {
|
||||
render(contentType:"text/xml") {
|
||||
response() {
|
||||
returncode(false)
|
||||
messageKey('RequestInvalid')
|
||||
message('The request is not supported.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public Properties sanitizePrametersForBaseString(Object params) {
|
||||
|
||||
Properties reqProp = new Properties();
|
||||
for (String key : ((Map<String, String>)params).keySet()) {
|
||||
if (key == "action" || key == "controller") {
|
||||
// Ignore as these are the grails controller and action tied to this request.
|
||||
continue
|
||||
} else if (key == "oauth_signature") {
|
||||
// We don't need this as part of the base string
|
||||
continue
|
||||
}
|
||||
|
||||
reqProp.setProperty(key, ((Map<String, String>)params).get(key));
|
||||
}
|
||||
|
||||
return reqProp
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all required parameters have been passed in the request.
|
||||
* @param params - the HTTP request parameters
|
||||
* @param missingParams - a list of missing parameters
|
||||
* @return - true if all required parameters have been passed in
|
||||
*/
|
||||
public boolean hasAllRequiredParams(Object params, Object missingParams) {
|
||||
boolean hasAllParams = true
|
||||
if (! ((Map<String, String>)params).containsKey(CUSTOMER_ID)) {
|
||||
((ArrayList<String>)missingParams).add(CUSTOMER_ID);
|
||||
hasAllParams = false;
|
||||
}
|
||||
|
||||
if (! ((Map<String, String>)params).containsKey(USER_ID) && ! ((Map<String, String>)params).containsKey(CUSTOM_USER_ID)) {
|
||||
if (! ((Map<String, String>)params).containsKey(USER_EMAIL)) {
|
||||
((ArrayList<String>)missingParams).add(USER_EMAIL);
|
||||
if (! ((Map<String, String>)params).containsKey(USER_ID)) {
|
||||
((ArrayList<String>)missingParams).add(USER_ID);
|
||||
} else {
|
||||
((ArrayList<String>)missingParams).add(CUSTOM_USER_ID);
|
||||
}
|
||||
|
||||
hasAllParams = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (! ((Map<String, String>)params).containsKey(COURSE_ID)) {
|
||||
((ArrayList<String>)missingParams).add(COURSE_ID);
|
||||
hasAllParams = false;
|
||||
}
|
||||
|
||||
if (! ((Map<String, String>)params).containsKey(OAUTH_SIGNATURE)) {
|
||||
((ArrayList<String>)missingParams).add(OAUTH_SIGNATURE);
|
||||
hasAllParams = false;
|
||||
}
|
||||
|
||||
return hasAllParams
|
||||
}
|
||||
|
||||
private boolean hasValidStudentId(params, customer) {
|
||||
if (((Map<String, String>)params).containsKey(USER_ID) || ((Map<String, String>)params).containsKey(CUSTOM_USER_ID)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (((Map<String, String>)params).containsKey(USER_EMAIL)) {
|
||||
((Map<String, String>)params).put(USER_ID, ((Map<String, String>)customer).get(USER_EMAIL))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the passed signature is valid.
|
||||
* @param method - POST or GET method used to make the request
|
||||
* @param URL - The target URL for the Basic LTI tool
|
||||
* @param conSecret - The consumer secret key
|
||||
* @param postProp - the parameters passed in from the tool
|
||||
* @param signature - the passed in signature calculated from the client
|
||||
* @return - TRUE if the signatures matches the calculated signature
|
||||
*/
|
||||
public boolean checkValidSignature(String method, String URL, String conSecret, Object postProp, String signature) {
|
||||
OAuthMessage oam = new OAuthMessage(method, URL, ((Properties)postProp).entrySet());
|
||||
HMAC_SHA1 hmac = new HMAC_SHA1();
|
||||
hmac.setConsumerSecret(conSecret);
|
||||
|
||||
log.debug("Base Message String = [ " + hmac.getBaseString(oam) + " ]\n");
|
||||
String calculatedSignature = hmac.getSignature(hmac.getBaseString(oam))
|
||||
log.debug("Calculated: " + calculatedSignature + " Received: " + signature);
|
||||
return calculatedSignature.equals(signature)
|
||||
}
|
||||
|
||||
private Map<String, String> getCustomer(params) {
|
||||
Map<String, String> customer = new HashMap<String, String>()
|
||||
|
||||
customer.put("customerId", "187");
|
||||
customer.put("secretKey", "Huzzah!!")
|
||||
|
||||
return customer
|
||||
}
|
||||
|
||||
def retrieveBasicLtiEndpoint() {
|
||||
//String basicLtiEndPoint = grailsApplication.config.grails.serverURL + "/bigbluebutton/blti/tool.xml"
|
||||
String basicLtiEndPoint = "http://192.168.0.153/lti/tool.xml"
|
||||
log.debug "basicLtiEndPoint [" + basicLtiEndPoint + "]"
|
||||
return basicLtiEndPoint
|
||||
}
|
||||
|
||||
}
|
34
bbb-lti/grails-app/i18n/messages.properties
Normal file
@ -0,0 +1,34 @@
|
||||
default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}]
|
||||
default.invalid.url.message=Property [{0}] of class [{1}] with value [{2}] is not a valid URL
|
||||
default.invalid.creditCard.message=Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number
|
||||
default.invalid.email.message=Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address
|
||||
default.invalid.range.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}]
|
||||
default.invalid.size.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}]
|
||||
default.invalid.max.message=Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}]
|
||||
default.invalid.min.message=Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}]
|
||||
default.invalid.max.size.message=Property [{0}] of class [{1}] with value [{2}] exceeds the maximum size of [{3}]
|
||||
default.invalid.min.size.message=Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}]
|
||||
default.invalid.validator.message=Property [{0}] of class [{1}] with value [{2}] does not pass custom validation
|
||||
default.not.inlist.message=Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}]
|
||||
default.blank.message=Property [{0}] of class [{1}] cannot be blank
|
||||
default.not.equal.message=Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}]
|
||||
default.null.message=Property [{0}] of class [{1}] cannot be null
|
||||
default.not.unique.message=Property [{0}] of class [{1}] with value [{2}] must be unique
|
||||
|
||||
default.paginate.prev=Previous
|
||||
default.paginate.next=Next
|
||||
default.boolean.true=True
|
||||
default.boolean.false=False
|
||||
default.date.format=yyyy-MM-dd HH:mm:ss z
|
||||
default.number.format=0
|
||||
|
||||
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
|
||||
typeMismatch.java.net.URL=Property {0} must be a valid URL
|
||||
typeMismatch.java.net.URI=Property {0} must be a valid URI
|
||||
typeMismatch.java.util.Date=Property {0} must be a valid Date
|
||||
typeMismatch.java.lang.Double=Property {0} must be a valid number
|
||||
typeMismatch.java.lang.Integer=Property {0} must be a valid number
|
||||
typeMismatch.java.lang.Long=Property {0} must be a valid number
|
||||
typeMismatch.java.lang.Short=Property {0} must be a valid number
|
||||
typeMismatch.java.math.BigDecimal=Property {0} must be a valid number
|
||||
typeMismatch.java.math.BigInteger=Property {0} must be a valid number
|
30
bbb-lti/grails-app/i18n/messages_de.properties
Normal file
@ -0,0 +1,30 @@
|
||||
default.doesnt.match.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] entspricht nicht dem vorgegebenen Muster [{3}]
|
||||
default.invalid.url.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige URL
|
||||
default.invalid.creditCard.message=Das Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige Kreditkartennummer
|
||||
default.invalid.email.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige E-Mail Adresse
|
||||
default.invalid.range.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}]
|
||||
default.invalid.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}]
|
||||
default.invalid.max.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist größer als der Höchstwert von [{3}]
|
||||
default.invalid.min.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist kleiner als der Mindestwert von [{3}]
|
||||
default.invalid.max.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] übersteigt den Höchstwert von [{3}]
|
||||
default.invalid.min.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] unterschreitet den Mindestwert von [{3}]
|
||||
default.invalid.validator.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist ungültig
|
||||
default.not.inlist.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht in der Liste [{3}] enthalten.
|
||||
default.blank.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht leer sein
|
||||
default.not.equal.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nicht gleich [{3}] sein
|
||||
default.null.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht null sein
|
||||
default.not.unique.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nur einmal vorkommen
|
||||
|
||||
default.paginate.prev=Vorherige
|
||||
default.paginate.next=Nächste
|
||||
|
||||
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
|
||||
typeMismatch.java.net.URL=Die Eigenschaft {0} muss eine gültige URL sein
|
||||
typeMismatch.java.net.URI=Die Eigenschaft {0} muss eine gültige URI sein
|
||||
typeMismatch.java.util.Date=Die Eigenschaft {0} muss ein gültiges Datum sein
|
||||
typeMismatch.java.lang.Double=Die Eigenschaft {0} muss eine gültige Zahl sein
|
||||
typeMismatch.java.lang.Integer=Die Eigenschaft {0} muss eine gültige Zahl sein
|
||||
typeMismatch.java.lang.Long=Die Eigenschaft {0} muss eine gültige Zahl sein
|
||||
typeMismatch.java.lang.Short=Die Eigenschaft {0} muss eine gültige Zahl sein
|
||||
typeMismatch.java.math.BigDecimal=Die Eigenschaft {0} muss eine gültige Zahl sein
|
||||
typeMismatch.java.math.BigInteger=Die Eigenschaft {0} muss eine gültige Zahl sein
|
30
bbb-lti/grails-app/i18n/messages_es.properties
Normal file
@ -0,0 +1,30 @@
|
||||
default.doesnt.match.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no corresponde al patrón [{3}]
|
||||
default.invalid.url.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una URL válida
|
||||
default.invalid.creditCard.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es un número de tarjeta de crédito válida
|
||||
default.invalid.email.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una dirección de correo electrónico válida
|
||||
default.invalid.range.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el rango válido de [{3}] a [{4}]
|
||||
default.invalid.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el tamaño válido de [{3}] a [{4}]
|
||||
default.invalid.max.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el valor máximo [{3}]
|
||||
default.invalid.min.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menos que el valor mínimo [{3}]
|
||||
default.invalid.max.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el tamaño máximo de [{3}]
|
||||
default.invalid.min.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menor que el tamaño mínimo de [{3}]
|
||||
default.invalid.validator.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es válido
|
||||
default.not.inlist.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no esta contenido dentro de la lista [{3}]
|
||||
default.blank.message=La propiedad [{0}] de la clase [{1}] no puede ser vacía
|
||||
default.not.equal.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no puede igualar a [{3}]
|
||||
default.null.message=La propiedad [{0}] de la clase [{1}] no puede ser nulo
|
||||
default.not.unique.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] debe ser única
|
||||
|
||||
default.paginate.prev=Anterior
|
||||
default.paginate.next=Siguiente
|
||||
|
||||
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
|
||||
typeMismatch.java.net.URL=La propiedad {0} debe ser una URL válida
|
||||
typeMismatch.java.net.URI=La propiedad {0} debe ser una URI válida
|
||||
typeMismatch.java.util.Date=La propiedad {0} debe ser una fecha válida
|
||||
typeMismatch.java.lang.Double=La propiedad {0} debe ser un número válido
|
||||
typeMismatch.java.lang.Integer=La propiedad {0} debe ser un número válido
|
||||
typeMismatch.java.lang.Long=La propiedad {0} debe ser un número válido
|
||||
typeMismatch.java.lang.Short=La propiedad {0} debe ser un número válido
|
||||
typeMismatch.java.math.BigDecimal=La propiedad {0} debe ser un número válido
|
||||
typeMismatch.java.math.BigInteger=La propiedad {0} debe ser un número válido
|
19
bbb-lti/grails-app/i18n/messages_fr.properties
Normal file
@ -0,0 +1,19 @@
|
||||
default.doesnt.match.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne correspond pas au pattern [{3}]
|
||||
default.invalid.url.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une URL valide
|
||||
default.invalid.creditCard.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas un numéro de carte de crédit valide
|
||||
default.invalid.email.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une adresse e-mail valide
|
||||
default.invalid.range.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] à [{4}]
|
||||
default.invalid.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] à [{4}]
|
||||
default.invalid.max.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est supérieure à la valeur maximum [{3}]
|
||||
default.invalid.min.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est inférieure à la valeur minimum [{3}]
|
||||
default.invalid.max.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est supérieure à la valeur maximum [{3}]
|
||||
default.invalid.min.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est inférieure à la valeur minimum [{3}]
|
||||
default.invalid.validator.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas valide
|
||||
default.not.inlist.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne fait pas partie de la liste [{3}]
|
||||
default.blank.message=La propriété [{0}] de la classe [{1}] ne peut pas être vide
|
||||
default.not.equal.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne peut pas être égale à [{3}]
|
||||
default.null.message=La propriété [{0}] de la classe [{1}] ne peut pas être nulle
|
||||
default.not.unique.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] doit être unique
|
||||
|
||||
default.paginate.prev=Précédent
|
||||
default.paginate.next=Suivant
|
19
bbb-lti/grails-app/i18n/messages_it.properties
Normal file
@ -0,0 +1,19 @@
|
||||
default.doesnt.match.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non corrisponde al pattern [{3}]
|
||||
default.invalid.url.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un URL valido
|
||||
default.invalid.creditCard.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un numero di carta di credito valido
|
||||
default.invalid.email.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un indirizzo email valido
|
||||
default.invalid.range.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo valido da [{3}] a [{4}]
|
||||
default.invalid.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo di dimensioni valide da [{3}] a [{4}]
|
||||
default.invalid.max.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è maggiore di [{3}]
|
||||
default.invalid.min.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è minore di [{3}]
|
||||
default.invalid.max.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è maggiore di [{3}]
|
||||
default.invalid.min.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è minore di [{3}]
|
||||
default.invalid.validator.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è valida
|
||||
default.not.inlist.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è contenuta nella lista [{3}]
|
||||
default.blank.message=La proprietà [{0}] della classe [{1}] non può essere vuota
|
||||
default.not.equal.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non può essere uguale a [{3}]
|
||||
default.null.message=La proprietà [{0}] della classe [{1}] non può essere null
|
||||
default.not.unique.message=La proprietà [{0}] della classe [{1}] con valore [{2}] deve essere unica
|
||||
|
||||
default.paginate.prev=Precedente
|
||||
default.paginate.next=Successivo
|
19
bbb-lti/grails-app/i18n/messages_ja.properties
Normal file
@ -0,0 +1,19 @@
|
||||
default.doesnt.match.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]パターンと一致していません。
|
||||
default.invalid.url.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、URLではありません。
|
||||
default.invalid.creditCard.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、正当なクレジットカード番号ではありません。
|
||||
default.invalid.email.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、メールアドレスではありません。
|
||||
default.invalid.range.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]範囲内を指定してください。
|
||||
default.invalid.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]以内を指定してください。
|
||||
default.invalid.max.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。
|
||||
default.invalid.min.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。
|
||||
default.invalid.max.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。
|
||||
default.invalid.min.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。
|
||||
default.invalid.validator.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、カスタムバリデーションを通過できません。
|
||||
default.not.inlist.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]リスト内に存在しません。
|
||||
default.blank.message=[{1}]クラスのプロパティ[{0}]の空白は許可されません。
|
||||
default.not.equal.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]と同等ではありません。
|
||||
default.null.message=[{1}]クラスのプロパティ[{0}]にnullは許可されません。
|
||||
default.not.unique.message=クラス[{1}]プロパティ[{0}]の値[{2}]は既に使用されています。
|
||||
|
||||
default.paginate.prev=戻る
|
||||
default.paginate.next=次へ
|
34
bbb-lti/grails-app/i18n/messages_nl.properties
Normal file
@ -0,0 +1,34 @@
|
||||
default.doesnt.match.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] komt niet overeen met het vereiste patroon [{3}]
|
||||
default.invalid.url.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldige URL
|
||||
default.invalid.creditCard.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldig credit card nummer
|
||||
default.invalid.email.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldig e-mailadres
|
||||
default.invalid.range.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] valt niet in de geldige waardenreeks van [{3}] tot [{4}]
|
||||
default.invalid.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] valt niet in de geldige grootte van [{3}] tot [{4}]
|
||||
default.invalid.max.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] overschrijdt de maximumwaarde [{3}]
|
||||
default.invalid.min.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is minder dan de minimumwaarde [{3}]
|
||||
default.invalid.max.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] overschrijdt de maximumgrootte van [{3}]
|
||||
default.invalid.min.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is minder dan mainimumgrootte van [{3}]
|
||||
default.invalid.validator.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is niet geldig
|
||||
default.not.inlist.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] komt niet voor in de lijst [{3}]
|
||||
default.blank.message=Attribuut [{0}] van entiteit [{1}] mag niet leeg zijn
|
||||
default.not.equal.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] mag niet gelijk zijn aan [{3}]
|
||||
default.null.message=Attribuut [{0}] van entiteit [{1}] mag niet leeg zijn
|
||||
default.not.unique.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] moet uniek zijn
|
||||
|
||||
default.paginate.prev=Vorige
|
||||
default.paginate.next=Volgende
|
||||
default.boolean.true=Ja
|
||||
default.boolean.false=Nee
|
||||
default.date.format=dd-MM-yyyy HH:mm:ss z
|
||||
default.number.format=0
|
||||
|
||||
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
|
||||
typeMismatch.java.net.URL=Attribuut {0} is geen geldige URL
|
||||
typeMismatch.java.net.URI=Attribuut {0} is geen geldige URI
|
||||
typeMismatch.java.util.Date=Attribuut {0} is geen geldige datum
|
||||
typeMismatch.java.lang.Double=Attribuut {0} is geen geldig nummer
|
||||
typeMismatch.java.lang.Integer=Attribuut {0} is geen geldig nummer
|
||||
typeMismatch.java.lang.Long=Attribuut {0} is geen geldig nummer
|
||||
typeMismatch.java.lang.Short=Attribuut {0} is geen geldig nummer
|
||||
typeMismatch.java.math.BigDecimal=Attribuut {0} is geen geldig nummer
|
||||
typeMismatch.java.math.BigInteger=Attribuut {0} is geen geldig nummer
|
34
bbb-lti/grails-app/i18n/messages_pt_BR.properties
Normal file
@ -0,0 +1,34 @@
|
||||
#
|
||||
# Translated by Lucas Teixeira - lucastex@gmail.com
|
||||
#
|
||||
|
||||
default.doesnt.match.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atende ao padrão definido [{3}]
|
||||
default.invalid.url.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é uma URL válida
|
||||
default.invalid.creditCard.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um número válido de cartão de crédito
|
||||
default.invalid.email.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um endereço de email válido.
|
||||
default.invalid.range.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está entre a faixa de valores válida de [{3}] até [{4}]
|
||||
default.invalid.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está na faixa de tamanho válida de [{3}] até [{4}]
|
||||
default.invalid.max.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapass o valor máximo [{3}]
|
||||
default.invalid.min.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o valor mínimo [{3}]
|
||||
default.invalid.max.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o tamanho máximo de [{3}]
|
||||
default.invalid.min.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o tamanho mínimo de [{3}]
|
||||
default.invalid.validator.message=O campo [{0}] da classe [{1}] com o valor [{2}] não passou na validação
|
||||
default.not.inlist.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um valor dentre os permitidos na lista [{3}]
|
||||
default.blank.message=O campo [{0}] da classe [{1}] não pode ficar em branco
|
||||
default.not.equal.message=O campo [{0}] da classe [{1}] com o valor [{2}] não pode ser igual a [{3}]
|
||||
default.null.message=O campo [{0}] da classe [{1}] não pode ser vazia
|
||||
default.not.unique.message=O campo [{0}] da classe [{1}] com o valor [{2}] deve ser único
|
||||
|
||||
default.paginate.prev=Anterior
|
||||
default.paginate.next=Próximo
|
||||
|
||||
# Mensagens de erro em atribuição de valores. Use "typeMismatch.$className.$propertyName" para customizar (eg typeMismatch.Book.author)
|
||||
typeMismatch.java.net.URL=O campo {0} deve ser uma URL válida.
|
||||
typeMismatch.java.net.URI=O campo {0} deve ser uma URI válida.
|
||||
typeMismatch.java.util.Date=O campo {0} deve ser uma data válida
|
||||
typeMismatch.java.lang.Double=O campo {0} deve ser um número válido.
|
||||
typeMismatch.java.lang.Integer=O campo {0} deve ser um número válido.
|
||||
typeMismatch.java.lang.Long=O campo {0} deve ser um número válido.
|
||||
typeMismatch.java.lang.Short=O campo {0} deve ser um número válido.
|
||||
typeMismatch.java.math.BigDecimal=O campo {0} deve ser um número válido.
|
||||
typeMismatch.java.math.BigInteger=O campo {0} deve ser um número válido.
|
31
bbb-lti/grails-app/i18n/messages_ru.properties
Normal file
@ -0,0 +1,31 @@
|
||||
default.doesnt.match.message=Значение [{2}] поля [{0}] класса [{1}] не соответствует образцу [{3}]
|
||||
default.invalid.url.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым URL-адресом
|
||||
default.invalid.creditCard.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым номером кредитной карты
|
||||
default.invalid.email.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым e-mail адресом
|
||||
default.invalid.range.message=Значение [{2}] поля [{0}] класса [{1}] не попадает в допустимый интервал от [{3}] до [{4}]
|
||||
default.invalid.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) не попадает в допустимый интервал от [{3}] до [{4}]
|
||||
default.invalid.max.message=Значение [{2}] поля [{0}] класса [{1}] больше чем максимально допустимое значение [{3}]
|
||||
default.invalid.min.message=Значение [{2}] поля [{0}] класса [{1}] меньше чем минимально допустимое значение [{3}]
|
||||
default.invalid.max.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) больше чем максимально допустимый размер [{3}]
|
||||
default.invalid.min.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) меньше чем минимально допустимый размер [{3}]
|
||||
default.invalid.validator.message=Значение [{2}] поля [{0}] класса [{1}] не допустимо
|
||||
default.not.inlist.message=Значение [{2}] поля [{0}] класса [{1}] не попадает в список допустимых значений [{3}]
|
||||
default.blank.message=Поле [{0}] класса [{1}] не может быть пустым
|
||||
default.not.equal.message=Значение [{2}] поля [{0}] класса [{1}] не может быть равно [{3}]
|
||||
default.null.message=Поле [{0}] класса [{1}] не может иметь значение null
|
||||
default.not.unique.message=Значение [{2}] поля [{0}] класса [{1}] должно быть уникальным
|
||||
|
||||
default.paginate.prev=Предыдушая страница
|
||||
default.paginate.next=Следующая страница
|
||||
|
||||
# Ошибки при присвоении данных. Для точной настройки для полей классов используйте
|
||||
# формат "typeMismatch.$className.$propertyName" (например, typeMismatch.Book.author)
|
||||
typeMismatch.java.net.URL=Значение поля {0} не является допустимым URL
|
||||
typeMismatch.java.net.URI=Значение поля {0} не является допустимым URI
|
||||
typeMismatch.java.util.Date=Значение поля {0} не является допустимой датой
|
||||
typeMismatch.java.lang.Double=Значение поля {0} не является допустимым числом
|
||||
typeMismatch.java.lang.Integer=Значение поля {0} не является допустимым числом
|
||||
typeMismatch.java.lang.Long=Значение поля {0} не является допустимым числом
|
||||
typeMismatch.java.lang.Short=Значение поля {0} не является допустимым числом
|
||||
typeMismatch.java.math.BigDecimal=Значение поля {0} не является допустимым числом
|
||||
typeMismatch.java.math.BigInteger=Значение поля {0} не является допустимым числом
|
35
bbb-lti/grails-app/i18n/messages_th.properties
Normal file
@ -0,0 +1,35 @@
|
||||
default.doesnt.match.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบที่กำหนดไว้ใน [{3}]
|
||||
default.invalid.url.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบ URL
|
||||
default.invalid.creditCard.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบหมายเลขบัตรเครดิต
|
||||
default.invalid.email.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบอีเมล์
|
||||
default.invalid.range.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้มีค่าที่ถูกต้องในช่วงจาก [{3}] ถึง [{4}]
|
||||
default.invalid.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้มีขนาดที่ถูกต้องในช่วงจาก [{3}] ถึง [{4}]
|
||||
default.invalid.max.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีค่าเกิดกว่าค่ามากสุด [{3}]
|
||||
default.invalid.min.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีค่าน้อยกว่าค่าต่ำสุด [{3}]
|
||||
default.invalid.max.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีขนาดเกินกว่าขนาดมากสุดของ [{3}]
|
||||
default.invalid.min.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีขนาดต่ำกว่าขนาดต่ำสุดของ [{3}]
|
||||
default.invalid.validator.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ผ่านการทวนสอบค่าที่ตั้งขึ้น
|
||||
default.not.inlist.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้อยู่ในรายการต่อไปนี้ [{3}]
|
||||
default.blank.message=คุณสมบัติ [{0}] ของคลาส [{1}] ไม่สามารถเป็นค่าว่างได้
|
||||
default.not.equal.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่สามารถเท่ากับ [{3}] ได้
|
||||
default.null.message=คุณสมบัติ [{0}] ของคลาส [{1}] ไม่สามารถเป็น null ได้
|
||||
default.not.unique.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] จะต้องไม่ซ้ำ (unique)
|
||||
|
||||
default.paginate.prev=ก่อนหน้า
|
||||
default.paginate.next=ถัดไป
|
||||
|
||||
default.boolean.true=จริง
|
||||
default.boolean.false=เท็จ
|
||||
default.date.format=dd-MM-yyyy HH:mm:ss z
|
||||
default.number.format=0
|
||||
|
||||
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
|
||||
typeMismatch.java.net.URL=คุณสมบัติ '{0}' จะต้องเป็นค่า URL ที่ถูกต้อง
|
||||
typeMismatch.java.net.URI=คุณสมบัติ '{0}' จะต้องเป็นค่า URI ที่ถูกต้อง
|
||||
typeMismatch.java.util.Date=คุณสมบัติ '{0}' จะต้องมีค่าเป็นวันที่
|
||||
typeMismatch.java.lang.Double=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Double
|
||||
typeMismatch.java.lang.Integer=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Integer
|
||||
typeMismatch.java.lang.Long=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Long
|
||||
typeMismatch.java.lang.Short=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Short
|
||||
typeMismatch.java.math.BigDecimal=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท BigDecimal
|
||||
typeMismatch.java.math.BigInteger=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท BigInteger
|
18
bbb-lti/grails-app/i18n/messages_zh_CN.properties
Normal file
@ -0,0 +1,18 @@
|
||||
default.blank.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3A\u7A7A
|
||||
default.doesnt.match.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0E\u5B9A\u4E49\u7684\u6A21\u5F0F [{3}]\u4E0D\u5339\u914D
|
||||
default.invalid.creditCard.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684\u4FE1\u7528\u5361\u53F7
|
||||
default.invalid.email.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740
|
||||
default.invalid.max.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u6BD4\u6700\u5927\u503C [{3}]\u8FD8\u5927
|
||||
default.invalid.max.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u6BD4\u6700\u5927\u503C [{3}]\u8FD8\u5927
|
||||
default.invalid.min.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u6BD4\u6700\u5C0F\u503C [{3}]\u8FD8\u5C0F
|
||||
default.invalid.min.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u6BD4\u6700\u5C0F\u503C [{3}]\u8FD8\u5C0F
|
||||
default.invalid.range.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u5728\u5408\u6CD5\u7684\u8303\u56F4\u5185( [{3}] \uFF5E [{4}] )
|
||||
default.invalid.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u4E0D\u5728\u5408\u6CD5\u7684\u8303\u56F4\u5185( [{3}] \uFF5E [{4}] )
|
||||
default.invalid.url.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684URL
|
||||
default.invalid.validator.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u672A\u80FD\u901A\u8FC7\u81EA\u5B9A\u4E49\u7684\u9A8C\u8BC1
|
||||
default.not.equal.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0E[{3}]\u4E0D\u76F8\u7B49
|
||||
default.not.inlist.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u5728\u5217\u8868\u7684\u53D6\u503C\u8303\u56F4\u5185
|
||||
default.not.unique.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u5FC5\u987B\u662F\u552F\u4E00\u7684
|
||||
default.null.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3Anull
|
||||
default.paginate.next=\u4E0B\u9875
|
||||
default.paginate.prev=\u4E0A\u9875
|
@ -0,0 +1,12 @@
|
||||
package org.bigbluebutton.web.services
|
||||
class BigbluebuttonService {
|
||||
|
||||
boolean transactional = true
|
||||
|
||||
def url
|
||||
def salt
|
||||
|
||||
def serviceMethod() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.bigbluebutton.web.services
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import javax.crypto.Mac
|
||||
import org.apache.commons.codec.binary.Base64
|
||||
|
||||
class LtiService {
|
||||
|
||||
boolean transactional = true
|
||||
|
||||
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);
|
||||
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);
|
||||
return mac
|
||||
}
|
||||
|
||||
private String encodeBase64(byte[] signBytes) {
|
||||
return Base64.encodeBase64URLSafeString(signBytes)
|
||||
}
|
||||
}
|
54
bbb-lti/grails-app/views/error.gsp
Normal file
@ -0,0 +1,54 @@
|
||||
<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>
|
20
bbb-lti/grails-app/views/index.gsp
Normal file
@ -0,0 +1,20 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Welcome to Grails</title>
|
||||
<meta name="layout" content="main" />
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="margin-left:20px;">Welcome to Grails</h1>
|
||||
<p style="margin-left:20px;width:80%">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 class="dialog" style="margin-left:20px;width:60%;">
|
||||
<ul>
|
||||
<g:each var="c" in="${grailsApplication.controllerClasses}">
|
||||
<li class="controller"><g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link></li>
|
||||
</g:each>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
16
bbb-lti/grails-app/views/layouts/main.gsp
Normal file
@ -0,0 +1,16 @@
|
||||
<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="Spinner" />
|
||||
</div>
|
||||
<div class="logo"><img src="${resource(dir:'images',file:'grails_logo.jpg')}" alt="Grails" /></div>
|
||||
<g:layoutBody />
|
||||
</body>
|
||||
</html>
|
28
bbb-lti/ivy.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<ivy-module version="2.0">
|
||||
<info organisation="org.example" module="lti"/>
|
||||
<configurations defaultconfmapping="build->default;compile->compile(*),master(*);test,runtime->runtime(*),master(*)">
|
||||
<conf name="build"/>
|
||||
<conf name="compile"/>
|
||||
<conf name="test" extends="compile"/>
|
||||
<conf name="runtime" extends="compile"/>
|
||||
</configurations>
|
||||
<dependencies>
|
||||
<dependency org="org.grails" name="grails-bootstrap" rev="1.1.1" conf="build"/>
|
||||
<dependency org="org.grails" name="grails-scripts" rev="1.1.1" conf="build"/>
|
||||
<dependency org="org.grails" name="grails-gorm" rev="1.1.1" conf="compile"/>
|
||||
<dependency org="org.grails" name="grails-web" rev="1.1.1" conf="compile"/>
|
||||
<dependency org="org.grails" name="grails-test" rev="1.1.1" conf="test"/>
|
||||
<dependency org="org.slf4j" name="slf4j-log4j12" rev="1.5.5" conf="runtime"/>
|
||||
<dependency org="opensymphony" name="oscache" rev="2.4" conf="runtime">
|
||||
<exclude org="javax.jms" module="jms" name="*" type="*" ext="*" conf="" matcher="exact"/>
|
||||
<exclude org="commons-logging" module="commons-logging" name="*" type="*" ext="*" conf="" matcher="exact"/>
|
||||
<exclude org="javax.servlet" module="servlet-api" name="*" type="*" ext="*" conf="" matcher="exact"/>
|
||||
</dependency>
|
||||
<dependency org="hsqldb" name="hsqldb" rev="1.8.0.5" conf="runtime"/>
|
||||
<dependency org="net.sf.ehcache" name="ehcache" rev="1.5.0" conf="runtime"/>
|
||||
<!--
|
||||
<dependency org="mysql" name="mysql-connector-java" rev="5.1.6" conf="runtime"/>
|
||||
<dependency org="postgresql" name="postgresql" rev="8.3-603.jdbc3" conf="runtime"/>
|
||||
-->
|
||||
</dependencies>
|
||||
</ivy-module>
|
15
bbb-lti/ivysettings.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<ivysettings>
|
||||
<settings defaultResolver="codehaus-plus"/>
|
||||
<include url="${ivy.default.settings.dir}/ivysettings-public.xml" />
|
||||
<include url="${ivy.default.settings.dir}/ivysettings-shared.xml"/>
|
||||
<include url="${ivy.default.settings.dir}/ivysettings-local.xml" />
|
||||
<include url="${ivy.default.settings.dir}/ivysettings-main-chain.xml"/>
|
||||
<resolvers>
|
||||
<chain name="codehaus-plus" dual="true">
|
||||
<ibiblio name="codehaus-snapshots" root="http://snapshots.repository.codehaus.org" m2compatible="true" changingPattern=".*SNAPSHOT"/>
|
||||
<ibiblio name="codehaus" root="http://repository.codehaus.org" m2compatible="true"/>
|
||||
<ibiblio name="javanet" root="http://download.java.net/maven/2/" m2compatible="true"/>
|
||||
<resolver ref="public"/>
|
||||
</chain>
|
||||
</resolvers>
|
||||
</ivysettings>
|
11
bbb-lti/lti-test.launch
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
|
||||
<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
|
||||
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value=""/>
|
||||
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
|
||||
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit3"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="grails.test.GrailsAwareGroovyTestSuite"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="lti"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dtest=${resource_loc}"/>
|
||||
</launchConfiguration>
|
19
bbb-lti/lti.launch
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/lti"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER" javaProject="lti" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8"?> <runtimeClasspathEntry containerPath="GRAILS_HOME/dist/grails-bootstrap-1.1.1.jar" path="3" type="3"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8"?> <runtimeClasspathEntry containerPath="GRAILS_HOME/lib/groovy-all-1.6.3.jar" path="3" type="3"/> "/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="grails.util.GrailsMain"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="lti"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dbase.dir="${project_loc:lti}" -Dserver.port=8080 -Dgrails.env=development"/>
|
||||
</launchConfiguration>
|
73
bbb-lti/lti.tmproj
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>documents</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>filename</key>
|
||||
<string>lti.launch</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>filename</key>
|
||||
<string>build.xml</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>grails-app</string>
|
||||
<key>regexFolderFilter</key>
|
||||
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
|
||||
<key>sourceDirectory</key>
|
||||
<string>grails-app</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>test</string>
|
||||
<key>regexFolderFilter</key>
|
||||
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
|
||||
<key>sourceDirectory</key>
|
||||
<string>test</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>lib</string>
|
||||
<key>regexFolderFilter</key>
|
||||
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
|
||||
<key>sourceDirectory</key>
|
||||
<string>lib</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>scripts</string>
|
||||
<key>regexFolderFilter</key>
|
||||
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
|
||||
<key>sourceDirectory</key>
|
||||
<string>scripts</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>src</string>
|
||||
<key>regexFolderFilter</key>
|
||||
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
|
||||
<key>sourceDirectory</key>
|
||||
<string>src</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>web-app</string>
|
||||
<key>regexFolderFilter</key>
|
||||
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
|
||||
<key>sourceDirectory</key>
|
||||
<string>web-app</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>fileHierarchyDrawerWidth</key>
|
||||
<integer>200</integer>
|
||||
<key>metaData</key>
|
||||
<dict/>
|
||||
<key>showFileHierarchyDrawer</key>
|
||||
<true/>
|
||||
<key>windowFrame</key>
|
||||
<string>{{237, 127}, {742, 553}}</string>
|
||||
</dict>
|
||||
</plist>
|
130
bbb-lti/src/java/net/oauth/ConsumerProperties.java
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2007 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* A pool of OAuthConsumers that are constructed from Properties. Each consumer
|
||||
* has a name, which is a property of the OAuthConsumer. Other properties come
|
||||
* from Properties whose names are prefixed with the consumer's name. For
|
||||
* example, a consumer's credentials come from properties named
|
||||
* [name].consumerKey and [name].consumerSecret.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class ConsumerProperties {
|
||||
|
||||
public static URL getResource(String name, ClassLoader loader)
|
||||
throws IOException {
|
||||
URL resource = loader.getResource(name);
|
||||
if (resource == null) {
|
||||
throw new IOException("resource not found: " + name);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
public static Properties getProperties(URL source) throws IOException {
|
||||
InputStream input = source.openStream();
|
||||
try {
|
||||
Properties p = new Properties();
|
||||
p.load(input);
|
||||
return p;
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
public ConsumerProperties(String resourceName, ClassLoader loader)
|
||||
throws IOException {
|
||||
this(getProperties(getResource(resourceName, loader)));
|
||||
}
|
||||
|
||||
public ConsumerProperties(Properties consumerProperties) {
|
||||
this.consumerProperties = consumerProperties;
|
||||
}
|
||||
|
||||
private final Properties consumerProperties;
|
||||
|
||||
private final Map<String, OAuthConsumer> pool = new HashMap<String, OAuthConsumer>();
|
||||
|
||||
/** Get the consumer with the given name. */
|
||||
public OAuthConsumer getConsumer(String name) throws MalformedURLException {
|
||||
OAuthConsumer consumer;
|
||||
synchronized (pool) {
|
||||
consumer = pool.get(name);
|
||||
}
|
||||
if (consumer == null) {
|
||||
consumer = newConsumer(name);
|
||||
}
|
||||
synchronized (pool) {
|
||||
OAuthConsumer first = pool.get(name);
|
||||
if (first == null) {
|
||||
pool.put(name, consumer);
|
||||
} else {
|
||||
/*
|
||||
* Another thread just constructed an identical OAuthConsumer.
|
||||
* Use that one (and discard the one we just constructed).
|
||||
*/
|
||||
consumer = first;
|
||||
}
|
||||
}
|
||||
return consumer;
|
||||
}
|
||||
|
||||
protected OAuthConsumer newConsumer(String name)
|
||||
throws MalformedURLException {
|
||||
String base = consumerProperties.getProperty(name
|
||||
+ ".serviceProvider.baseURL");
|
||||
URL baseURL = (base == null) ? null : new URL(base);
|
||||
OAuthServiceProvider serviceProvider = new OAuthServiceProvider(getURL(
|
||||
baseURL, name + ".serviceProvider.requestTokenURL"), getURL(
|
||||
baseURL, name + ".serviceProvider.userAuthorizationURL"),
|
||||
getURL(baseURL, name + ".serviceProvider.accessTokenURL"));
|
||||
OAuthConsumer consumer = new OAuthConsumer(consumerProperties
|
||||
.getProperty(name + ".callbackURL"), consumerProperties
|
||||
.getProperty(name + ".consumerKey"), consumerProperties
|
||||
.getProperty(name + ".consumerSecret"), serviceProvider);
|
||||
consumer.setProperty("name", name);
|
||||
if (baseURL != null) {
|
||||
consumer.setProperty("serviceProvider.baseURL", baseURL);
|
||||
}
|
||||
for (Map.Entry prop : consumerProperties.entrySet()) {
|
||||
String propName = (String) prop.getKey();
|
||||
if (propName.startsWith(name + ".consumer.")) {
|
||||
String c = propName.substring(name.length() + 10);
|
||||
consumer.setProperty(c, prop.getValue());
|
||||
}
|
||||
}
|
||||
return consumer;
|
||||
}
|
||||
|
||||
private String getURL(URL base, String name) throws MalformedURLException {
|
||||
String url = consumerProperties.getProperty(name);
|
||||
if (base != null) {
|
||||
url = (new URL(base, url)).toExternalForm();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
33
bbb-lti/src/java/net/oauth/MessageWithBody.java
Normal file
@ -0,0 +1,33 @@
|
||||
package net.oauth;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import net.oauth.http.HttpMessage;
|
||||
import net.oauth.http.HttpMessageDecoder;
|
||||
|
||||
|
||||
public class MessageWithBody extends OAuthMessage {
|
||||
private final byte[] body;
|
||||
|
||||
public MessageWithBody(String method, String URL, Collection<OAuth.Parameter> parameters,
|
||||
String contentType, byte[] body) {
|
||||
super(method, URL, parameters);
|
||||
this.body = body;
|
||||
Collection<Map.Entry<String, String>> headers = getHeaders();
|
||||
headers.add(new OAuth.Parameter(HttpMessage.ACCEPT_ENCODING, HttpMessageDecoder.ACCEPTED));
|
||||
|
||||
if (body != null) {
|
||||
headers.add(new OAuth.Parameter(HttpMessage.CONTENT_LENGTH, String.valueOf(body.length)));
|
||||
}
|
||||
if (contentType != null) {
|
||||
headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE, contentType));
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getBodyAsStream() {
|
||||
return (body == null) ? null : new ByteArrayInputStream(body);
|
||||
}
|
||||
}
|
351
bbb-lti/src/java/net/oauth/OAuth.java
Normal file
@ -0,0 +1,351 @@
|
||||
/*
|
||||
* Copyright 2007 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class OAuth {
|
||||
|
||||
public static final String VERSION_1_0 = "1.0";
|
||||
|
||||
/** The encoding used to represent characters as bytes. */
|
||||
public static final String ENCODING = "UTF-8";
|
||||
|
||||
/** The MIME type for a sequence of OAuth parameters. */
|
||||
public static final String FORM_ENCODED = "application/x-www-form-urlencoded";
|
||||
|
||||
public static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key";
|
||||
public static final String OAUTH_TOKEN = "oauth_token";
|
||||
public static final String OAUTH_TOKEN_SECRET = "oauth_token_secret";
|
||||
public static final String OAUTH_SIGNATURE_METHOD = "oauth_signature_method";
|
||||
public static final String OAUTH_SIGNATURE = "oauth_signature";
|
||||
public static final String OAUTH_TIMESTAMP = "oauth_timestamp";
|
||||
public static final String OAUTH_NONCE = "oauth_nonce";
|
||||
public static final String OAUTH_VERSION = "oauth_version";
|
||||
public static final String OAUTH_CALLBACK = "oauth_callback";
|
||||
|
||||
public static final String HMAC_SHA1 = "HMAC-SHA1";
|
||||
public static final String RSA_SHA1 = "RSA-SHA1";
|
||||
|
||||
/**
|
||||
* Strings used for <a href="http://wiki.oauth.net/ProblemReporting">problem
|
||||
* reporting</a>.
|
||||
*/
|
||||
public static class Problems {
|
||||
public static final String VERSION_REJECTED = "version_rejected";
|
||||
public static final String PARAMETER_ABSENT = "parameter_absent";
|
||||
public static final String PARAMETER_REJECTED = "parameter_rejected";
|
||||
public static final String TIMESTAMP_REFUSED = "timestamp_refused";
|
||||
public static final String NONCE_USED = "nonce_used";
|
||||
public static final String SIGNATURE_METHOD_REJECTED = "signature_method_rejected";
|
||||
public static final String SIGNATURE_INVALID = "signature_invalid";
|
||||
public static final String CONSUMER_KEY_UNKNOWN = "consumer_key_unknown";
|
||||
public static final String CONSUMER_KEY_REJECTED = "consumer_key_rejected";
|
||||
public static final String CONSUMER_KEY_REFUSED = "consumer_key_refused";
|
||||
public static final String TOKEN_USED = "token_used";
|
||||
public static final String TOKEN_EXPIRED = "token_expired";
|
||||
public static final String TOKEN_REVOKED = "token_revoked";
|
||||
public static final String TOKEN_REJECTED = "token_rejected";
|
||||
public static final String ADDITIONAL_AUTHORIZATION_REQUIRED = "additional_authorization_required";
|
||||
public static final String PERMISSION_UNKNOWN = "permission_unknown";
|
||||
public static final String PERMISSION_DENIED = "permission_denied";
|
||||
public static final String USER_REFUSED = "user_refused";
|
||||
|
||||
public static final String OAUTH_ACCEPTABLE_VERSIONS = "oauth_acceptable_versions";
|
||||
public static final String OAUTH_ACCEPTABLE_TIMESTAMPS = "oauth_acceptable_timestamps";
|
||||
public static final String OAUTH_PARAMETERS_ABSENT = "oauth_parameters_absent";
|
||||
public static final String OAUTH_PARAMETERS_REJECTED = "oauth_parameters_rejected";
|
||||
public static final String OAUTH_PROBLEM_ADVICE = "oauth_problem_advice";
|
||||
|
||||
/**
|
||||
* A map from an <a
|
||||
* href="http://wiki.oauth.net/ProblemReporting">oauth_problem</a> value to
|
||||
* the appropriate HTTP response code.
|
||||
*/
|
||||
public static final Map<String, Integer> TO_HTTP_CODE = mapToHttpCode();
|
||||
|
||||
private static Map<String, Integer> mapToHttpCode() {
|
||||
Integer badRequest = new Integer(400);
|
||||
Integer unauthorized = new Integer(401);
|
||||
Integer serviceUnavailable = new Integer(503);
|
||||
Map<String, Integer> map = new HashMap<String, Integer>();
|
||||
|
||||
map.put(Problems.VERSION_REJECTED, badRequest);
|
||||
map.put(Problems.PARAMETER_ABSENT, badRequest);
|
||||
map.put(Problems.PARAMETER_REJECTED, badRequest);
|
||||
map.put(Problems.TIMESTAMP_REFUSED, badRequest);
|
||||
map.put(Problems.SIGNATURE_METHOD_REJECTED, badRequest);
|
||||
|
||||
map.put(Problems.NONCE_USED, unauthorized);
|
||||
map.put(Problems.TOKEN_USED, unauthorized);
|
||||
map.put(Problems.TOKEN_EXPIRED, unauthorized);
|
||||
map.put(Problems.TOKEN_REVOKED, unauthorized);
|
||||
map.put(Problems.TOKEN_REJECTED, unauthorized);
|
||||
map.put("token_not_authorized", unauthorized);
|
||||
map.put(Problems.SIGNATURE_INVALID, unauthorized);
|
||||
map.put(Problems.CONSUMER_KEY_UNKNOWN, unauthorized);
|
||||
map.put(Problems.CONSUMER_KEY_REJECTED, unauthorized);
|
||||
map.put(Problems.ADDITIONAL_AUTHORIZATION_REQUIRED, unauthorized);
|
||||
map.put(Problems.PERMISSION_UNKNOWN, unauthorized);
|
||||
map.put(Problems.PERMISSION_DENIED, unauthorized);
|
||||
|
||||
map.put(Problems.USER_REFUSED, serviceUnavailable);
|
||||
map.put(Problems.CONSUMER_KEY_REFUSED, serviceUnavailable);
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Return true if the given Content-Type header means FORM_ENCODED. */
|
||||
public static boolean isFormEncoded(String contentType) {
|
||||
if (contentType == null) {
|
||||
return false;
|
||||
}
|
||||
int semi = contentType.indexOf(";");
|
||||
if (semi >= 0) {
|
||||
contentType = contentType.substring(0, semi);
|
||||
}
|
||||
return FORM_ENCODED.equalsIgnoreCase(contentType.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a form-urlencoded document containing the given sequence of
|
||||
* name/value pairs. Use OAuth percent encoding (not exactly the encoding
|
||||
* mandated by HTTP).
|
||||
*/
|
||||
public static String formEncode(Iterable<? extends Map.Entry> parameters)
|
||||
throws IOException {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
formEncode(parameters, b);
|
||||
return new String(b.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a form-urlencoded document into the given stream, containing the
|
||||
* given sequence of name/value pairs.
|
||||
*/
|
||||
public static void formEncode(Iterable<? extends Map.Entry> parameters,
|
||||
OutputStream into) throws IOException {
|
||||
if (parameters != null) {
|
||||
boolean first = true;
|
||||
for (Map.Entry parameter : parameters) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
into.write('&');
|
||||
}
|
||||
into.write(percentEncode(toString(parameter.getKey()))
|
||||
.getBytes());
|
||||
into.write('=');
|
||||
into.write(percentEncode(toString(parameter.getValue()))
|
||||
.getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse a form-urlencoded document. */
|
||||
public static List<Parameter> decodeForm(String form) {
|
||||
List<Parameter> list = new ArrayList<Parameter>();
|
||||
if (!isEmpty(form)) {
|
||||
for (String nvp : form.split("\\&")) {
|
||||
int equals = nvp.indexOf('=');
|
||||
String name;
|
||||
String value;
|
||||
if (equals < 0) {
|
||||
name = decodePercent(nvp);
|
||||
value = null;
|
||||
} else {
|
||||
name = decodePercent(nvp.substring(0, equals));
|
||||
value = decodePercent(nvp.substring(equals + 1));
|
||||
}
|
||||
list.add(new Parameter(name, value));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/** Construct a &-separated list of the given values, percentEncoded. */
|
||||
public static String percentEncode(Iterable values) {
|
||||
StringBuilder p = new StringBuilder();
|
||||
for (Object v : values) {
|
||||
if (p.length() > 0) {
|
||||
p.append("&");
|
||||
}
|
||||
p.append(OAuth.percentEncode(toString(v)));
|
||||
}
|
||||
return p.toString();
|
||||
}
|
||||
|
||||
public static String percentEncode(String s) {
|
||||
if (s == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
return URLEncoder.encode(s, ENCODING)
|
||||
// OAuth encodes some characters differently:
|
||||
.replace("+", "%20").replace("*", "%2A")
|
||||
.replace("%7E", "~");
|
||||
// This could be done faster with more hand-crafted code.
|
||||
} catch (UnsupportedEncodingException wow) {
|
||||
throw new RuntimeException(wow.getMessage(), wow);
|
||||
}
|
||||
}
|
||||
|
||||
public static String decodePercent(String s) {
|
||||
try {
|
||||
return URLDecoder.decode(s, ENCODING);
|
||||
// This implements http://oauth.pbwiki.com/FlexibleDecoding
|
||||
} catch (java.io.UnsupportedEncodingException wow) {
|
||||
throw new RuntimeException(wow.getMessage(), wow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Map containing a copy of the given parameters. If several
|
||||
* parameters have the same name, the Map will contain the first value,
|
||||
* only.
|
||||
*/
|
||||
public static Map<String, String> newMap(Iterable<? extends Map.Entry> from) {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
if (from != null) {
|
||||
for (Map.Entry f : from) {
|
||||
String key = toString(f.getKey());
|
||||
if (!map.containsKey(key)) {
|
||||
map.put(key, toString(f.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** Construct a list of Parameters from name, value, name, value... */
|
||||
public static List<Parameter> newList(String... parameters) {
|
||||
List<Parameter> list = new ArrayList<Parameter>(parameters.length / 2);
|
||||
for (int p = 0; p + 1 < parameters.length; p += 2) {
|
||||
list.add(new Parameter(parameters[p], parameters[p + 1]));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/** A name/value pair. */
|
||||
public static class Parameter implements Map.Entry<String, String> {
|
||||
|
||||
public Parameter(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private final String key;
|
||||
|
||||
private String value;
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String setValue(String value) {
|
||||
try {
|
||||
return this.value;
|
||||
} finally {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return percentEncode(getKey()) + '=' + percentEncode(getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((key == null) ? 0 : key.hashCode());
|
||||
result = prime * result + ((value == null) ? 0 : value.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final Parameter that = (Parameter) obj;
|
||||
if (key == null) {
|
||||
if (that.key != null)
|
||||
return false;
|
||||
} else if (!key.equals(that.key))
|
||||
return false;
|
||||
if (value == null) {
|
||||
if (that.value != null)
|
||||
return false;
|
||||
} else if (!value.equals(that.value))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String toString(Object from) {
|
||||
return (from == null) ? null : from.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a URL like the given one, but with the given parameters added
|
||||
* to its query string.
|
||||
*/
|
||||
public static String addParameters(String url, String... parameters)
|
||||
throws IOException {
|
||||
return addParameters(url, newList(parameters));
|
||||
}
|
||||
|
||||
public static String addParameters(String url,
|
||||
Iterable<? extends Map.Entry<String, String>> parameters)
|
||||
throws IOException {
|
||||
String form = formEncode(parameters);
|
||||
if (form == null || form.length() <= 0) {
|
||||
return url;
|
||||
} else {
|
||||
return url + ((url.indexOf("?") < 0) ? '?' : '&') + form;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isEmpty(String str) {
|
||||
return (str == null) || (str.length() == 0);
|
||||
}
|
||||
}
|
100
bbb-lti/src/java/net/oauth/OAuthAccessor.java
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2007 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Properties of one User of an OAuthConsumer. Properties may be added freely,
|
||||
* e.g. to support extensions.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class OAuthAccessor implements Cloneable, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 5590788443138352999L;
|
||||
|
||||
public final OAuthConsumer consumer;
|
||||
public String requestToken;
|
||||
public String accessToken;
|
||||
public String tokenSecret;
|
||||
|
||||
public OAuthAccessor(OAuthConsumer consumer) {
|
||||
this.consumer = consumer;
|
||||
this.requestToken = null;
|
||||
this.accessToken = null;
|
||||
this.tokenSecret = null;
|
||||
}
|
||||
|
||||
private final Map<String, Object> properties = new HashMap<String, Object>();
|
||||
|
||||
@Override
|
||||
public OAuthAccessor clone() {
|
||||
try {
|
||||
return (OAuthAccessor) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Object getProperty(String name) {
|
||||
return properties.get(name);
|
||||
}
|
||||
|
||||
public void setProperty(String name, Object value) {
|
||||
properties.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a request message containing the given parameters but no body.
|
||||
* Don't send the message, merely construct it. The caller will ordinarily
|
||||
* send it, for example by calling OAuthClient.invoke or access.
|
||||
*
|
||||
* @param method
|
||||
* the HTTP request method. If this is null, use the default
|
||||
* method; that is getProperty("httpMethod") or (if that's null)
|
||||
* consumer.getProperty("httpMethod") or (if that's null)
|
||||
* OAuthMessage.GET.
|
||||
*/
|
||||
public OAuthMessage newRequestMessage(String method, String url, Collection<? extends Map.Entry> parameters,
|
||||
InputStream body) throws OAuthException, IOException, URISyntaxException {
|
||||
if (method == null) {
|
||||
method = (String) this.getProperty("httpMethod");
|
||||
if (method == null) {
|
||||
method = (String) this.consumer.getProperty("httpMethod");
|
||||
if (method == null) {
|
||||
method = OAuthMessage.GET;
|
||||
}
|
||||
}
|
||||
}
|
||||
OAuthMessage message = new OAuthMessage(method, url, parameters, body);
|
||||
message.addRequiredParameters(this);
|
||||
return message;
|
||||
}
|
||||
|
||||
public OAuthMessage newRequestMessage(String method, String url, Collection<? extends Map.Entry> parameters)
|
||||
throws OAuthException, IOException, URISyntaxException {
|
||||
return newRequestMessage(method, url, parameters, null);
|
||||
}
|
||||
|
||||
}
|
69
bbb-lti/src/java/net/oauth/OAuthConsumer.java
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2007 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.oauth.http.HttpMessage;
|
||||
|
||||
/**
|
||||
* Properties of an OAuth Consumer. Properties may be added freely, e.g. to
|
||||
* support extensions.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class OAuthConsumer implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -2258581186977818580L;
|
||||
|
||||
public final String callbackURL;
|
||||
public final String consumerKey;
|
||||
public final String consumerSecret;
|
||||
public final OAuthServiceProvider serviceProvider;
|
||||
|
||||
public OAuthConsumer(String callbackURL, String consumerKey,
|
||||
String consumerSecret, OAuthServiceProvider serviceProvider) {
|
||||
this.callbackURL = callbackURL;
|
||||
this.consumerKey = consumerKey;
|
||||
this.consumerSecret = consumerSecret;
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
private final Map<String, Object> properties = new HashMap<String, Object>();
|
||||
|
||||
public Object getProperty(String name) {
|
||||
return properties.get(name);
|
||||
}
|
||||
|
||||
public void setProperty(String name, Object value) {
|
||||
properties.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the property whose value is the Accept-Encoding header in
|
||||
* HTTP requests.
|
||||
*/
|
||||
public static final String ACCEPT_ENCODING = "HTTP.header." + HttpMessage.ACCEPT_ENCODING;
|
||||
|
||||
/**
|
||||
* The name of the property whose value is the <a
|
||||
* href="http://oauth.pbwiki.com/AccessorSecret">Accessor Secret</a>.
|
||||
*/
|
||||
public static final String ACCESSOR_SECRET = "oauth_accessor_secret";
|
||||
|
||||
}
|
53
bbb-lti/src/java/net/oauth/OAuthException.java
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2008 Google, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.oauth;
|
||||
|
||||
/**
|
||||
* Superclass for extensions thrown by the OAuth library.
|
||||
*/
|
||||
public class OAuthException extends Exception {
|
||||
|
||||
/**
|
||||
* For subclasses only.
|
||||
*/
|
||||
protected OAuthException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message
|
||||
*/
|
||||
public OAuthException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cause
|
||||
*/
|
||||
public OAuthException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message
|
||||
* @param cause
|
||||
*/
|
||||
public OAuthException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
}
|
456
bbb-lti/src/java/net/oauth/OAuthMessage.java
Normal file
@ -0,0 +1,456 @@
|
||||
/*
|
||||
* Copyright 2007, 2008 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import net.oauth.ParameterStyle;
|
||||
import net.oauth.http.HttpMessage;
|
||||
import net.oauth.signature.OAuthSignatureMethod;
|
||||
|
||||
/**
|
||||
* A request or response message used in the OAuth protocol.
|
||||
* <p>
|
||||
* The parameters in this class are not percent-encoded. Methods like
|
||||
* OAuthClient.invoke and OAuthResponseMessage.completeParameters are
|
||||
* responsible for percent-encoding parameters before transmission and decoding
|
||||
* them after reception.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class OAuthMessage {
|
||||
|
||||
public OAuthMessage(String method, String URL, Collection<? extends Map.Entry> parameters) {
|
||||
this(method, URL, parameters, null);
|
||||
}
|
||||
|
||||
public OAuthMessage(String method, String URL, Collection<? extends Map.Entry> parameters,
|
||||
InputStream bodyAsStream) {
|
||||
this.method = method;
|
||||
this.URL = URL;
|
||||
this.bodyAsStream = bodyAsStream;
|
||||
if (parameters == null) {
|
||||
this.parameters = new ArrayList<Map.Entry<String, String>>();
|
||||
} else {
|
||||
this.parameters = new ArrayList<Map.Entry<String, String>>(parameters.size());
|
||||
for (Map.Entry p : parameters) {
|
||||
this.parameters.add(new OAuth.Parameter(
|
||||
toString(p.getKey()), toString(p.getValue())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String method;
|
||||
public String URL;
|
||||
|
||||
private final List<Map.Entry<String, String>> parameters;
|
||||
private Map<String, String> parameterMap;
|
||||
private boolean parametersAreComplete = false;
|
||||
private final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>();
|
||||
private final InputStream bodyAsStream;
|
||||
|
||||
public String toString() {
|
||||
return "OAuthMessage(" + method + ", " + URL + ", " + parameters + ")";
|
||||
}
|
||||
|
||||
/** A caller is about to get a parameter. */
|
||||
private void beforeGetParameter() throws IOException {
|
||||
if (!parametersAreComplete) {
|
||||
completeParameters();
|
||||
parametersAreComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish adding parameters; for example read an HTTP response body and
|
||||
* parse parameters from it.
|
||||
*/
|
||||
protected void completeParameters() throws IOException {
|
||||
}
|
||||
|
||||
public List<Map.Entry<String, String>> getParameters() throws IOException {
|
||||
beforeGetParameter();
|
||||
return Collections.unmodifiableList(parameters);
|
||||
}
|
||||
|
||||
public void addParameter(String key, String value) {
|
||||
addParameter(new OAuth.Parameter(key, value));
|
||||
}
|
||||
|
||||
public void addParameter(Map.Entry<String, String> parameter) {
|
||||
parameters.add(parameter);
|
||||
parameterMap = null;
|
||||
}
|
||||
|
||||
public void addParameters(
|
||||
Collection<? extends Map.Entry<String, String>> parameters) {
|
||||
this.parameters.addAll(parameters);
|
||||
parameterMap = null;
|
||||
}
|
||||
|
||||
public String getParameter(String name) throws IOException {
|
||||
return getParameterMap().get(name);
|
||||
}
|
||||
|
||||
public String getConsumerKey() throws IOException {
|
||||
return getParameter(OAuth.OAUTH_CONSUMER_KEY);
|
||||
}
|
||||
|
||||
public String getToken() throws IOException {
|
||||
return getParameter(OAuth.OAUTH_TOKEN);
|
||||
}
|
||||
|
||||
public String getSignatureMethod() throws IOException {
|
||||
return getParameter(OAuth.OAUTH_SIGNATURE_METHOD);
|
||||
}
|
||||
|
||||
public String getSignature() throws IOException {
|
||||
return getParameter(OAuth.OAUTH_SIGNATURE);
|
||||
}
|
||||
|
||||
protected Map<String, String> getParameterMap() throws IOException {
|
||||
beforeGetParameter();
|
||||
if (parameterMap == null) {
|
||||
parameterMap = OAuth.newMap(parameters);
|
||||
}
|
||||
return parameterMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* The MIME type of the body of this message.
|
||||
*
|
||||
* @return the MIME type, or null to indicate the type is unknown.
|
||||
*/
|
||||
public String getBodyType() {
|
||||
return getHeader(HttpMessage.CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* The character encoding of the body of this message.
|
||||
*
|
||||
* @return the name of an encoding, or "ISO-8859-1" if no charset has been
|
||||
* specified.
|
||||
*/
|
||||
public String getBodyEncoding() {
|
||||
return HttpMessage.DEFAULT_CHARSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of the last HTTP header with the given name. The name is case
|
||||
* insensitive.
|
||||
*
|
||||
* @return the value of the last header, or null to indicate that there is
|
||||
* no such header in this message.
|
||||
*/
|
||||
public final String getHeader(String name) {
|
||||
String value = null; // no such header
|
||||
for (Map.Entry<String, String> header : getHeaders()) {
|
||||
if (name.equalsIgnoreCase(header.getKey())) {
|
||||
value = header.getValue();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** All HTTP headers. You can add headers to this list. */
|
||||
public final List<Map.Entry<String, String>> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the body of the HTTP request or response and convert it to a String.
|
||||
* This method isn't repeatable, since it consumes and closes getBodyAsStream.
|
||||
*
|
||||
* @return the body, or null to indicate there is no body.
|
||||
*/
|
||||
public final String readBodyAsString() throws IOException
|
||||
{
|
||||
InputStream body = getBodyAsStream();
|
||||
return readAll(body, getBodyEncoding());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a stream from which to read the body of the HTTP request or response.
|
||||
* This is designed to support efficient streaming of a large message.
|
||||
* The caller must close the returned stream, to release the underlying
|
||||
* resources such as the TCP connection for an HTTP response.
|
||||
*
|
||||
* @return a stream from which to read the body, or null to indicate there
|
||||
* is no body.
|
||||
*/
|
||||
public InputStream getBodyAsStream() throws IOException {
|
||||
return bodyAsStream;
|
||||
}
|
||||
|
||||
/** Construct a verbose description of this message and its origins. */
|
||||
public Map<String, Object> getDump() throws IOException {
|
||||
Map<String, Object> into = new HashMap<String, Object>();
|
||||
dump(into);
|
||||
return into;
|
||||
}
|
||||
|
||||
protected void dump(Map<String, Object> into) throws IOException {
|
||||
into.put("URL", URL);
|
||||
if (parametersAreComplete) {
|
||||
try {
|
||||
into.putAll(getParameterMap());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the required parameter names are contained in the actual
|
||||
* collection.
|
||||
*
|
||||
* @throws OAuthProblemException
|
||||
* one or more parameters are absent.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void requireParameters(String... names)
|
||||
throws OAuthProblemException, IOException {
|
||||
Set<String> present = getParameterMap().keySet();
|
||||
List<String> absent = new ArrayList<String>();
|
||||
for (String required : names) {
|
||||
if (!present.contains(required)) {
|
||||
absent.add(required);
|
||||
}
|
||||
}
|
||||
if (!absent.isEmpty()) {
|
||||
OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.PARAMETER_ABSENT);
|
||||
problem.setParameter(OAuth.Problems.OAUTH_PARAMETERS_ABSENT, OAuth.percentEncode(absent));
|
||||
throw problem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some of the parameters needed to request access to a protected
|
||||
* resource, if they aren't already in the message.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
public void addRequiredParameters(OAuthAccessor accessor)
|
||||
throws OAuthException, IOException, URISyntaxException {
|
||||
final Map<String, String> pMap = OAuth.newMap(parameters);
|
||||
if (pMap.get(OAuth.OAUTH_TOKEN) == null && accessor.accessToken != null) {
|
||||
addParameter(OAuth.OAUTH_TOKEN, accessor.accessToken);
|
||||
}
|
||||
final OAuthConsumer consumer = accessor.consumer;
|
||||
if (pMap.get(OAuth.OAUTH_CONSUMER_KEY) == null) {
|
||||
addParameter(OAuth.OAUTH_CONSUMER_KEY, consumer.consumerKey);
|
||||
}
|
||||
String signatureMethod = pMap.get(OAuth.OAUTH_SIGNATURE_METHOD);
|
||||
if (signatureMethod == null) {
|
||||
signatureMethod = (String) consumer.getProperty(OAuth.OAUTH_SIGNATURE_METHOD);
|
||||
if (signatureMethod == null) {
|
||||
signatureMethod = OAuth.HMAC_SHA1;
|
||||
}
|
||||
addParameter(OAuth.OAUTH_SIGNATURE_METHOD, signatureMethod);
|
||||
}
|
||||
if (pMap.get(OAuth.OAUTH_TIMESTAMP) == null) {
|
||||
addParameter(OAuth.OAUTH_TIMESTAMP, (System.currentTimeMillis() / 1000) + "");
|
||||
}
|
||||
if (pMap.get(OAuth.OAUTH_NONCE) == null) {
|
||||
addParameter(OAuth.OAUTH_NONCE, System.nanoTime() + "");
|
||||
}
|
||||
if (pMap.get(OAuth.OAUTH_VERSION) == null) {
|
||||
addParameter(OAuth.OAUTH_VERSION, OAuth.VERSION_1_0);
|
||||
}
|
||||
this.sign(accessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a signature to the message.
|
||||
*
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
public void sign(OAuthAccessor accessor) throws IOException,
|
||||
OAuthException, URISyntaxException {
|
||||
OAuthSignatureMethod.newSigner(this, accessor).sign(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an HTTP request from this OAuth message.
|
||||
*
|
||||
* @param style
|
||||
* where to put the OAuth parameters, within the HTTP request
|
||||
*/
|
||||
public HttpMessage toHttpRequest(ParameterStyle style) throws IOException {
|
||||
final boolean isPost = POST.equalsIgnoreCase(method);
|
||||
InputStream body = getBodyAsStream();
|
||||
if (style == ParameterStyle.BODY && !(isPost && body == null)) {
|
||||
style = ParameterStyle.QUERY_STRING;
|
||||
}
|
||||
String url = this.URL;
|
||||
final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>(getHeaders());
|
||||
switch (style) {
|
||||
case QUERY_STRING:
|
||||
url = OAuth.addParameters(url, getParameters());
|
||||
break;
|
||||
case BODY: {
|
||||
byte[] form = OAuth.formEncode(getParameters()).getBytes(getBodyEncoding());
|
||||
headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE, OAuth.FORM_ENCODED));
|
||||
headers.add(new OAuth.Parameter(HttpMessage.CONTENT_LENGTH, form.length + ""));
|
||||
body = new ByteArrayInputStream(form);
|
||||
break;
|
||||
}
|
||||
case AUTHORIZATION_HEADER:
|
||||
headers.add(new OAuth.Parameter("Authorization", getAuthorizationHeader(null)));
|
||||
// Find the non-OAuth parameters:
|
||||
List<Map.Entry<String, String>> others = getParameters();
|
||||
if (others != null && !others.isEmpty()) {
|
||||
others = new ArrayList<Map.Entry<String, String>>(others);
|
||||
for (Iterator<Map.Entry<String, String>> p = others.iterator(); p.hasNext();) {
|
||||
if (p.next().getKey().startsWith("oauth_")) {
|
||||
p.remove();
|
||||
}
|
||||
}
|
||||
// Place the non-OAuth parameters elsewhere in the request:
|
||||
if (isPost && body == null) {
|
||||
byte[] form = OAuth.formEncode(others).getBytes(getBodyEncoding());
|
||||
headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE, OAuth.FORM_ENCODED));
|
||||
headers.add(new OAuth.Parameter(HttpMessage.CONTENT_LENGTH, form.length + ""));
|
||||
body = new ByteArrayInputStream(form);
|
||||
} else {
|
||||
url = OAuth.addParameters(url, others);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
HttpMessage httpRequest = new HttpMessage(method, new URL(url), body);
|
||||
httpRequest.headers.addAll(headers);
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the message is valid.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws URISyntaxException
|
||||
*
|
||||
* @throws OAuthProblemException
|
||||
* the message is invalid
|
||||
*/
|
||||
public void validateMessage(OAuthAccessor accessor, OAuthValidator validator)
|
||||
throws OAuthException, IOException, URISyntaxException {
|
||||
validator.validateMessage(this, accessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a WWW-Authenticate or Authentication header value, containing
|
||||
* the given realm plus all the parameters whose names begin with "oauth_".
|
||||
*/
|
||||
public String getAuthorizationHeader(String realm) throws IOException {
|
||||
StringBuilder into = new StringBuilder();
|
||||
if (realm != null) {
|
||||
into.append(" realm=\"").append(OAuth.percentEncode(realm)).append('"');
|
||||
}
|
||||
beforeGetParameter();
|
||||
if (parameters != null) {
|
||||
for (Map.Entry parameter : parameters) {
|
||||
String name = toString(parameter.getKey());
|
||||
if (name.startsWith("oauth_")) {
|
||||
if (into.length() > 0) into.append(",");
|
||||
into.append(" ");
|
||||
into.append(OAuth.percentEncode(name)).append("=\"");
|
||||
into.append(OAuth.percentEncode(toString(parameter.getValue()))).append('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
return AUTH_SCHEME + into.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all the data from the given stream, and close it.
|
||||
*
|
||||
* @return null if from is null, or the data from the stream converted to a
|
||||
* String
|
||||
*/
|
||||
public static String readAll(InputStream from, String encoding) throws IOException
|
||||
{
|
||||
if (from == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
StringBuilder into = new StringBuilder();
|
||||
Reader r = new InputStreamReader(from, encoding);
|
||||
char[] s = new char[512];
|
||||
for (int n; 0 < (n = r.read(s));) {
|
||||
into.append(s, 0, n);
|
||||
}
|
||||
return into.toString();
|
||||
} finally {
|
||||
from.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the parameters from an OAuth Authorization or WWW-Authenticate
|
||||
* header. The realm is included as a parameter. If the given header doesn't
|
||||
* start with "OAuth ", return an empty list.
|
||||
*/
|
||||
public static List<OAuth.Parameter> decodeAuthorization(String authorization) {
|
||||
List<OAuth.Parameter> into = new ArrayList<OAuth.Parameter>();
|
||||
if (authorization != null) {
|
||||
Matcher m = AUTHORIZATION.matcher(authorization);
|
||||
if (m.matches()) {
|
||||
if (AUTH_SCHEME.equalsIgnoreCase(m.group(1))) {
|
||||
for (String nvp : m.group(2).split("\\s*,\\s*")) {
|
||||
m = NVP.matcher(nvp);
|
||||
if (m.matches()) {
|
||||
String name = OAuth.decodePercent(m.group(1));
|
||||
String value = OAuth.decodePercent(m.group(2));
|
||||
into.add(new OAuth.Parameter(name, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return into;
|
||||
}
|
||||
|
||||
public static final String AUTH_SCHEME = "OAuth";
|
||||
|
||||
public static final String GET = "GET";
|
||||
public static final String POST = "POST";
|
||||
public static final String PUT = "PUT";
|
||||
public static final String DELETE = "DELETE";
|
||||
|
||||
private static final Pattern AUTHORIZATION = Pattern.compile("\\s*(\\w*)\\s+(.*)");
|
||||
private static final Pattern NVP = Pattern.compile("(\\S*)\\s*\\=\\s*\"([^\"]*)\"");
|
||||
|
||||
private static final String toString(Object from) {
|
||||
return (from == null) ? null : from.toString();
|
||||
}
|
||||
|
||||
}
|
105
bbb-lti/src/java/net/oauth/OAuthProblemException.java
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2007 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.oauth.http.HttpMessage;
|
||||
import net.oauth.http.HttpResponseMessage;
|
||||
|
||||
/**
|
||||
* Describes an OAuth-related problem, using a set of named parameters. One
|
||||
* parameter identifies the basic problem, and the others provide supplementary
|
||||
* diagnostic information. This can be used to capture information from a
|
||||
* response that conforms to the OAuth <a
|
||||
* href="http://wiki.oauth.net/ProblemReporting">Problem Reporting
|
||||
* extension</a>.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class OAuthProblemException extends OAuthException {
|
||||
|
||||
public static final String OAUTH_PROBLEM = "oauth_problem";
|
||||
|
||||
public OAuthProblemException() {
|
||||
}
|
||||
|
||||
public OAuthProblemException(String problem) {
|
||||
super(problem);
|
||||
if (problem != null) {
|
||||
parameters.put(OAUTH_PROBLEM, problem);
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<String, Object> parameters = new HashMap<String, Object>();
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String msg = super.getMessage();
|
||||
if (msg != null)
|
||||
return msg;
|
||||
msg = getProblem();
|
||||
if (msg != null)
|
||||
return msg;
|
||||
Object response = getParameters().get(HttpMessage.RESPONSE);
|
||||
if (response != null) {
|
||||
msg = response.toString();
|
||||
int eol = msg.indexOf("\n");
|
||||
if (eol < 0) {
|
||||
eol = msg.indexOf("\r");
|
||||
}
|
||||
if (eol >= 0) {
|
||||
msg = msg.substring(0, eol);
|
||||
}
|
||||
msg = msg.trim();
|
||||
if (msg.length() > 0) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
response = getHttpStatusCode();
|
||||
if (response != null) {
|
||||
return HttpResponseMessage.STATUS_CODE + " " + response;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setParameter(String name, Object value) {
|
||||
getParameters().put(name, value);
|
||||
}
|
||||
|
||||
public Map<String, Object> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public String getProblem() {
|
||||
return (String) getParameters().get(OAUTH_PROBLEM);
|
||||
}
|
||||
|
||||
public int getHttpStatusCode() {
|
||||
Object code = getParameters().get(HttpResponseMessage.STATUS_CODE);
|
||||
if (code == null) {
|
||||
return 200;
|
||||
} else if (code instanceof Number) { // the usual case
|
||||
return ((Number) code).intValue();
|
||||
} else {
|
||||
return Integer.parseInt(code.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
}
|
41
bbb-lti/src/java/net/oauth/OAuthServiceProvider.java
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2007 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Properties of an OAuth Service Provider.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class OAuthServiceProvider implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3306534392621038574L;
|
||||
|
||||
public final String requestTokenURL;
|
||||
public final String userAuthorizationURL;
|
||||
public final String accessTokenURL;
|
||||
|
||||
public OAuthServiceProvider(String requestTokenURL,
|
||||
String userAuthorizationURL, String accessTokenURL) {
|
||||
this.requestTokenURL = requestTokenURL;
|
||||
this.userAuthorizationURL = userAuthorizationURL;
|
||||
this.accessTokenURL = accessTokenURL;
|
||||
}
|
||||
|
||||
}
|
42
bbb-lti/src/java/net/oauth/OAuthValidator.java
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2008 Google, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.oauth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/**
|
||||
* An algorithm to determine whether a message has a valid signature, a correct
|
||||
* version number, a fresh timestamp, etc.
|
||||
*
|
||||
* @author Dirk Balfanz
|
||||
* @author John Kristian
|
||||
*/
|
||||
public interface OAuthValidator {
|
||||
|
||||
/**
|
||||
* Check that the given message from the given accessor is valid.
|
||||
* @throws OAuthException TODO
|
||||
* @throws IOException TODO
|
||||
* @throws URISyntaxException
|
||||
* @throws OAuthProblemException the message is invalid.
|
||||
* The implementation should throw exceptions that conform to the OAuth
|
||||
* <a href="http://wiki.oauth.net/ProblemReporting">Problem Reporting extension</a>.
|
||||
*/
|
||||
public void validateMessage(OAuthMessage message, OAuthAccessor accessor)
|
||||
throws OAuthException, IOException, URISyntaxException;
|
||||
|
||||
}
|
24
bbb-lti/src/java/net/oauth/ParameterStyle.java
Normal file
@ -0,0 +1,24 @@
|
||||
package net.oauth;
|
||||
|
||||
/**
|
||||
* Where to place OAuth parameters in an HTTP message. The alternatives are
|
||||
* summarized in OAuth Core section 5.2.
|
||||
*/
|
||||
public enum ParameterStyle {
|
||||
/**
|
||||
* Send parameters whose names begin with "oauth_" in an HTTP header, and
|
||||
* other parameters (whose names don't begin with "oauth_") in either the
|
||||
* message body or URL query string. The header formats are specified by
|
||||
* OAuth Core section 5.4.
|
||||
*/
|
||||
AUTHORIZATION_HEADER,
|
||||
|
||||
/**
|
||||
* Send all parameters in the message body, with a Content-Type of
|
||||
* application/x-www-form-urlencoded.
|
||||
*/
|
||||
BODY,
|
||||
|
||||
/** Send all parameters in the query string part of the URL. */
|
||||
QUERY_STRING;
|
||||
}
|
174
bbb-lti/src/java/net/oauth/SimpleOAuthValidator.java
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2008 Google, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.oauth;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.oauth.signature.OAuthSignatureMethod;
|
||||
|
||||
/**
|
||||
* A simple OAuthValidator, which checks the version, whether the timestamp
|
||||
* is close to now and the signature is valid. Each check may be overridden.
|
||||
*
|
||||
* @author Dirk Balfanz
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class SimpleOAuthValidator implements OAuthValidator {
|
||||
|
||||
/** The default window for timestamps is 5 minutes. */
|
||||
public static final long DEFAULT_TIMESTAMP_WINDOW = 5 * 60 * 1000L;
|
||||
|
||||
/**
|
||||
* Names of parameters that may not appear twice in a valid message.
|
||||
* This limitation is specified by OAuth Core <a
|
||||
* href="http://oauth.net/core/1.0#anchor7">section 5</a>.
|
||||
*/
|
||||
public static final Set<String> SINGLE_PARAMETERS = constructSingleParameters();
|
||||
|
||||
private static Set<String> constructSingleParameters() {
|
||||
Set<String> s = new HashSet<String>();
|
||||
for (String p : new String[] { OAuth.OAUTH_CONSUMER_KEY, OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET,
|
||||
OAuth.OAUTH_CALLBACK, OAuth.OAUTH_SIGNATURE_METHOD, OAuth.OAUTH_SIGNATURE, OAuth.OAUTH_TIMESTAMP,
|
||||
OAuth.OAUTH_NONCE, OAuth.OAUTH_VERSION }) {
|
||||
s.add(p);
|
||||
}
|
||||
return Collections.unmodifiableSet(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a validator that rejects messages more than five minutes out
|
||||
* of date, or with a OAuth version other than 1.0, or with an invalid
|
||||
* signature.
|
||||
*/
|
||||
public SimpleOAuthValidator() {
|
||||
this(DEFAULT_TIMESTAMP_WINDOW, Double.parseDouble(OAuth.VERSION_1_0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Public constructor.
|
||||
*
|
||||
* @param timestampWindowSec
|
||||
* specifies, in seconds, the windows (into the past and
|
||||
* into the future) in which we'll accept timestamps.
|
||||
* @param maxVersion
|
||||
* the maximum acceptable oauth_version
|
||||
*/
|
||||
public SimpleOAuthValidator(long timestampWindowMsec, double maxVersion) {
|
||||
this.timestampWindow = timestampWindowMsec;
|
||||
this.maxVersion = maxVersion;
|
||||
}
|
||||
|
||||
protected final double minVersion = 1.0;
|
||||
protected final double maxVersion;
|
||||
protected final long timestampWindow;
|
||||
|
||||
/** {@inherit}
|
||||
* @throws URISyntaxException */
|
||||
public void validateMessage(OAuthMessage message, OAuthAccessor accessor)
|
||||
throws OAuthException, IOException, URISyntaxException {
|
||||
checkSingleParameters(message);
|
||||
validateVersion(message);
|
||||
validateTimestampAndNonce(message);
|
||||
validateSignature(message, accessor);
|
||||
}
|
||||
|
||||
/** Throw an exception if any SINGLE_PARAMETERS occur repeatedly. */
|
||||
protected void checkSingleParameters(OAuthMessage message) throws IOException, OAuthException {
|
||||
// Check for repeated oauth_ parameters:
|
||||
boolean repeated = false;
|
||||
Map<String, Collection<String>> nameToValues = new HashMap<String, Collection<String>>();
|
||||
String repeatedParameter = "";
|
||||
for (Map.Entry<String, String> parameter : message.getParameters()) {
|
||||
String name = parameter.getKey();
|
||||
if (SINGLE_PARAMETERS.contains(name)) {
|
||||
Collection<String> values = nameToValues.get(name);
|
||||
if (values == null) {
|
||||
values = new ArrayList<String>();
|
||||
nameToValues.put(name, values);
|
||||
} else {
|
||||
repeated = true;
|
||||
repeatedParameter = name;
|
||||
}
|
||||
values.add(parameter.getValue());
|
||||
}
|
||||
}
|
||||
if (repeated) {
|
||||
Collection<OAuth.Parameter> rejected = new ArrayList<OAuth.Parameter>();
|
||||
for (Map.Entry<String, Collection<String>> p : nameToValues.entrySet()) {
|
||||
String name = p.getKey();
|
||||
Collection<String> values = p.getValue();
|
||||
if (values.size() > 1) {
|
||||
for (String value : values) {
|
||||
rejected.add(new OAuth.Parameter(name, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.PARAMETER_REJECTED + ":" + repeatedParameter);
|
||||
problem.setParameter(OAuth.Problems.OAUTH_PARAMETERS_REJECTED, OAuth.formEncode(rejected));
|
||||
throw problem;
|
||||
}
|
||||
}
|
||||
|
||||
protected void validateVersion(OAuthMessage message)
|
||||
throws OAuthException, IOException {
|
||||
String versionString = message.getParameter(OAuth.OAUTH_VERSION);
|
||||
if (versionString != null) {
|
||||
double version = Double.parseDouble(versionString);
|
||||
if (version < minVersion || maxVersion < version) {
|
||||
OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.VERSION_REJECTED);
|
||||
problem.setParameter(OAuth.Problems.OAUTH_ACCEPTABLE_VERSIONS, minVersion + "-" + maxVersion);
|
||||
throw problem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** This implementation doesn't check the nonce value. */
|
||||
protected void validateTimestampAndNonce(OAuthMessage message)
|
||||
throws IOException, OAuthProblemException {
|
||||
message.requireParameters(OAuth.OAUTH_TIMESTAMP, OAuth.OAUTH_NONCE);
|
||||
long timestamp = Long.parseLong(message.getParameter(OAuth.OAUTH_TIMESTAMP)) * 1000L;
|
||||
long now = currentTimeMsec();
|
||||
long min = now - timestampWindow;
|
||||
long max = now + timestampWindow;
|
||||
if (timestamp < min || max < timestamp) {
|
||||
OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.TIMESTAMP_REFUSED);
|
||||
problem.setParameter(OAuth.Problems.OAUTH_ACCEPTABLE_TIMESTAMPS, min + "-" + max);
|
||||
throw problem;
|
||||
}
|
||||
}
|
||||
|
||||
protected void validateSignature(OAuthMessage message, OAuthAccessor accessor)
|
||||
throws OAuthException, IOException, URISyntaxException {
|
||||
message.requireParameters(OAuth.OAUTH_CONSUMER_KEY,
|
||||
OAuth.OAUTH_SIGNATURE_METHOD, OAuth.OAUTH_SIGNATURE);
|
||||
OAuthSignatureMethod.newSigner(message, accessor).validate(message);
|
||||
}
|
||||
|
||||
protected long currentTimeMsec() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
}
|
42
bbb-lti/src/java/net/oauth/client/ExcerptInputStream.java
Normal file
@ -0,0 +1,42 @@
|
||||
package net.oauth.client;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/** A decorator that retains a copy of the first few bytes of data. */
|
||||
public class ExcerptInputStream extends BufferedInputStream
|
||||
{
|
||||
/**
|
||||
* A marker that's appended to the excerpt if it's less than the complete
|
||||
* stream.
|
||||
*/
|
||||
public static final byte[] ELLIPSIS = " ...".getBytes();
|
||||
|
||||
public ExcerptInputStream(InputStream in) throws IOException {
|
||||
super(in);
|
||||
mark(LIMIT);
|
||||
int total = 0;
|
||||
int read;
|
||||
while ((read = read(excerpt, total, LIMIT - total)) != -1 && ((total += read) < LIMIT));
|
||||
if (total == LIMIT) {
|
||||
// Only add the ellipsis if there are at least LIMIT bytes
|
||||
System.arraycopy(ELLIPSIS, 0, excerpt, total, ELLIPSIS.length);
|
||||
} else {
|
||||
byte[] tmp = new byte[total];
|
||||
System.arraycopy(excerpt, 0, tmp, 0, total);
|
||||
excerpt = tmp;
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
private static final int LIMIT = 1024;
|
||||
private byte[] excerpt = new byte[LIMIT + ELLIPSIS.length];
|
||||
|
||||
/** The first few bytes of data, plus ELLIPSIS if there are more bytes. */
|
||||
public byte[] getExcerpt()
|
||||
{
|
||||
return excerpt;
|
||||
}
|
||||
|
||||
}
|
300
bbb-lti/src/java/net/oauth/client/OAuthClient.java
Normal file
@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Copyright 2007, 2008 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.oauth.OAuth;
|
||||
import net.oauth.OAuthAccessor;
|
||||
import net.oauth.OAuthConsumer;
|
||||
import net.oauth.OAuthException;
|
||||
import net.oauth.OAuthMessage;
|
||||
import net.oauth.OAuthProblemException;
|
||||
import net.oauth.ParameterStyle;
|
||||
import net.oauth.http.HttpClient;
|
||||
import net.oauth.http.HttpMessage;
|
||||
import net.oauth.http.HttpMessageDecoder;
|
||||
import net.oauth.http.HttpResponseMessage;
|
||||
|
||||
/**
|
||||
* Methods for an OAuth consumer to request tokens from a service provider.
|
||||
* <p>
|
||||
* This class can also be used to request access to protected resources, in some
|
||||
* cases. But not in all cases. For example, this class can't handle arbitrary
|
||||
* HTTP headers.
|
||||
* <p>
|
||||
* Methods of this class return a response as an OAuthMessage, from which you
|
||||
* can get a body or parameters but not both. Calling a getParameter method will
|
||||
* read and close the body (like readBodyAsString), so you can't read it later.
|
||||
* If you read or close the body first, then getParameter can't read it. The
|
||||
* response headers should tell you whether the response contains encoded
|
||||
* parameters, that is whether you should call getParameter or not.
|
||||
* <p>
|
||||
* Methods of this class don't follow redirects. When they receive a redirect
|
||||
* response, they throw an OAuthProblemException, with properties
|
||||
* HttpResponseMessage.STATUS_CODE = the redirect code
|
||||
* HttpResponseMessage.LOCATION = the redirect URL. Such a redirect can't be
|
||||
* handled at the HTTP level, if the second request must carry another OAuth
|
||||
* signature (with different parameters). For example, Google's Service Provider
|
||||
* routinely redirects requests for access to protected resources, and requires
|
||||
* the redirected request to be signed.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class OAuthClient {
|
||||
|
||||
public OAuthClient(HttpClient http)
|
||||
{
|
||||
this.http = http;
|
||||
httpParameters.put(HttpClient.FOLLOW_REDIRECTS, Boolean.FALSE);
|
||||
}
|
||||
|
||||
private HttpClient http;
|
||||
protected final Map<String, Object> httpParameters = new HashMap<String, Object>();
|
||||
|
||||
public void setHttpClient(HttpClient http) {
|
||||
this.http = http;
|
||||
}
|
||||
|
||||
public HttpClient getHttpClient() {
|
||||
return http;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP client parameters, as a map from parameter name to value.
|
||||
*
|
||||
* @see HttpClient for parameter names.
|
||||
*/
|
||||
public Map<String, Object> getHttpParameters() {
|
||||
return httpParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fresh request token from the service provider.
|
||||
*
|
||||
* @param accessor
|
||||
* should contain a consumer that contains a non-null consumerKey
|
||||
* and consumerSecret. Also,
|
||||
* accessor.consumer.serviceProvider.requestTokenURL should be
|
||||
* the URL (determined by the service provider) for getting a
|
||||
* request token.
|
||||
* @throws OAuthProblemException
|
||||
* the HTTP response status code was not 200 (OK)
|
||||
*/
|
||||
public void getRequestToken(OAuthAccessor accessor) throws IOException,
|
||||
OAuthException, URISyntaxException {
|
||||
getRequestToken(accessor, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fresh request token from the service provider.
|
||||
*
|
||||
* @param accessor
|
||||
* should contain a consumer that contains a non-null consumerKey
|
||||
* and consumerSecret. Also,
|
||||
* accessor.consumer.serviceProvider.requestTokenURL should be
|
||||
* the URL (determined by the service provider) for getting a
|
||||
* request token.
|
||||
* @param httpMethod
|
||||
* typically OAuthMessage.POST or OAuthMessage.GET, or null to
|
||||
* use the default method.
|
||||
* @throws OAuthProblemException
|
||||
* the HTTP response status code was not 200 (OK)
|
||||
*/
|
||||
public void getRequestToken(OAuthAccessor accessor, String httpMethod)
|
||||
throws IOException, OAuthException, URISyntaxException {
|
||||
getRequestToken(accessor, httpMethod, null);
|
||||
}
|
||||
|
||||
/** Get a fresh request token from the service provider.
|
||||
*
|
||||
* @param accessor
|
||||
* should contain a consumer that contains a non-null consumerKey
|
||||
* and consumerSecret. Also,
|
||||
* accessor.consumer.serviceProvider.requestTokenURL should be
|
||||
* the URL (determined by the service provider) for getting a
|
||||
* request token.
|
||||
* @param httpMethod
|
||||
* typically OAuthMessage.POST or OAuthMessage.GET, or null to
|
||||
* use the default method.
|
||||
* @param parameters
|
||||
* additional parameters for this request, or null to indicate
|
||||
* that there are no additional parameters.
|
||||
* @throws OAuthProblemException
|
||||
* the HTTP response status code was not 200 (OK)
|
||||
*/
|
||||
public void getRequestToken(OAuthAccessor accessor, String httpMethod,
|
||||
Collection<? extends Map.Entry> parameters) throws IOException,
|
||||
OAuthException, URISyntaxException {
|
||||
accessor.accessToken = null;
|
||||
accessor.tokenSecret = null;
|
||||
{
|
||||
// This code supports the 'Variable Accessor Secret' extension
|
||||
// described in http://oauth.pbwiki.com/AccessorSecret
|
||||
Object accessorSecret = accessor
|
||||
.getProperty(OAuthConsumer.ACCESSOR_SECRET);
|
||||
if (accessorSecret != null) {
|
||||
List<Map.Entry> p = (parameters == null) ? new ArrayList<Map.Entry>(
|
||||
1)
|
||||
: new ArrayList<Map.Entry>(parameters);
|
||||
p.add(new OAuth.Parameter("oauth_accessor_secret",
|
||||
accessorSecret.toString()));
|
||||
parameters = p;
|
||||
// But don't modify the caller's parameters.
|
||||
}
|
||||
}
|
||||
OAuthMessage response = invoke(accessor, httpMethod,
|
||||
accessor.consumer.serviceProvider.requestTokenURL, parameters);
|
||||
accessor.requestToken = response.getParameter(OAuth.OAUTH_TOKEN);
|
||||
accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET);
|
||||
response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an access token from the service provider, in exchange for an
|
||||
* authorized request token.
|
||||
*
|
||||
* @param accessor
|
||||
* should contain a non-null requestToken and tokenSecret, and a
|
||||
* consumer that contains a consumerKey and consumerSecret. Also,
|
||||
* accessor.consumer.serviceProvider.accessTokenURL should be the
|
||||
* URL (determined by the service provider) for getting an access
|
||||
* token.
|
||||
* @param httpMethod
|
||||
* typically OAuthMessage.POST or OAuthMessage.GET, or null to
|
||||
* use the default method.
|
||||
* @param parameters
|
||||
* additional parameters for this request, or null to indicate
|
||||
* that there are no additional parameters.
|
||||
* @throws OAuthProblemException
|
||||
* the HTTP response status code was not 200 (OK)
|
||||
*/
|
||||
public OAuthMessage getAccessToken(OAuthAccessor accessor, String httpMethod,
|
||||
Collection<? extends Map.Entry> parameters) throws IOException, OAuthException, URISyntaxException {
|
||||
if (accessor.requestToken != null) {
|
||||
if (parameters == null) {
|
||||
parameters = OAuth.newList(OAuth.OAUTH_TOKEN, accessor.requestToken);
|
||||
} else if (!OAuth.newMap(parameters).containsKey(OAuth.OAUTH_TOKEN)) {
|
||||
List<Map.Entry> p = new ArrayList<Map.Entry>(parameters);
|
||||
p.add(new OAuth.Parameter(OAuth.OAUTH_TOKEN, accessor.requestToken));
|
||||
parameters = p;
|
||||
}
|
||||
}
|
||||
OAuthMessage response = invoke(accessor, httpMethod,
|
||||
accessor.consumer.serviceProvider.accessTokenURL, parameters);
|
||||
response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET);
|
||||
accessor.accessToken = response.getParameter(OAuth.OAUTH_TOKEN);
|
||||
accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a request message, send it to the service provider and get the
|
||||
* response.
|
||||
*
|
||||
* @param httpMethod
|
||||
* the HTTP request method, or null to use the default method
|
||||
* @return the response
|
||||
* @throws URISyntaxException
|
||||
* the given url isn't valid syntactically
|
||||
* @throws OAuthProblemException
|
||||
* the HTTP response status code was not 200 (OK)
|
||||
*/
|
||||
public OAuthMessage invoke(OAuthAccessor accessor, String httpMethod,
|
||||
String url, Collection<? extends Map.Entry> parameters)
|
||||
throws IOException, OAuthException, URISyntaxException {
|
||||
OAuthMessage request = accessor.newRequestMessage(httpMethod, url, parameters);
|
||||
Object accepted = accessor.consumer.getProperty(OAuthConsumer.ACCEPT_ENCODING);
|
||||
if (accepted != null) {
|
||||
request.getHeaders().add(new OAuth.Parameter(HttpMessage.ACCEPT_ENCODING, accepted.toString()));
|
||||
}
|
||||
Object ps = accessor.consumer.getProperty(PARAMETER_STYLE);
|
||||
ParameterStyle style = (ps == null) ? ParameterStyle.BODY
|
||||
: Enum.valueOf(ParameterStyle.class, ps.toString());
|
||||
return invoke(request, style);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the OAuthConsumer property whose value is the ParameterStyle
|
||||
* to be used by invoke.
|
||||
*/
|
||||
public static final String PARAMETER_STYLE = "parameterStyle";
|
||||
|
||||
/**
|
||||
* The name of the OAuthConsumer property whose value is the Accept-Encoding
|
||||
* header in HTTP requests.
|
||||
* @deprecated use {@link OAuthConsumer#ACCEPT_ENCODING} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String ACCEPT_ENCODING = OAuthConsumer.ACCEPT_ENCODING;
|
||||
|
||||
/**
|
||||
* Construct a request message, send it to the service provider and get the
|
||||
* response.
|
||||
*
|
||||
* @return the response
|
||||
* @throws URISyntaxException
|
||||
* the given url isn't valid syntactically
|
||||
* @throws OAuthProblemException
|
||||
* the HTTP response status code was not 200 (OK)
|
||||
*/
|
||||
public OAuthMessage invoke(OAuthAccessor accessor, String url,
|
||||
Collection<? extends Map.Entry> parameters) throws IOException,
|
||||
OAuthException, URISyntaxException {
|
||||
return invoke(accessor, null, url, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request message to the service provider and get the response.
|
||||
*
|
||||
* @return the response
|
||||
* @throws IOException
|
||||
* failed to communicate with the service provider
|
||||
* @throws OAuthProblemException
|
||||
* the HTTP response status code was not 200 (OK)
|
||||
*/
|
||||
public OAuthMessage invoke(OAuthMessage request, ParameterStyle style)
|
||||
throws IOException, OAuthException {
|
||||
OAuthResponseMessage response = access(request, style);
|
||||
if ((response.getHttpResponse().getStatusCode() / 100) != 2) {
|
||||
throw response.toOAuthProblemException();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request and return the response. Don't try to decide whether the
|
||||
* response indicates success; merely return it.
|
||||
*/
|
||||
public OAuthResponseMessage access(OAuthMessage request, ParameterStyle style) throws IOException {
|
||||
HttpMessage httpRequest = request.toHttpRequest(style);
|
||||
HttpResponseMessage httpResponse = http.execute(httpRequest, httpParameters);
|
||||
httpResponse = HttpMessageDecoder.decode(httpResponse);
|
||||
return new OAuthResponseMessage(httpResponse);
|
||||
}
|
||||
|
||||
protected static final String PUT = OAuthMessage.PUT;
|
||||
protected static final String POST = OAuthMessage.POST;
|
||||
protected static final String DELETE = OAuthMessage.DELETE;
|
||||
protected static final String CONTENT_LENGTH = HttpMessage.CONTENT_LENGTH;
|
||||
|
||||
}
|
116
bbb-lti/src/java/net/oauth/client/OAuthResponseMessage.java
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2008 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import net.oauth.OAuth;
|
||||
import net.oauth.OAuthMessage;
|
||||
import net.oauth.OAuthProblemException;
|
||||
import net.oauth.http.HttpResponseMessage;
|
||||
|
||||
/**
|
||||
* An HTTP response, encapsulated as an OAuthMessage.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class OAuthResponseMessage extends OAuthMessage
|
||||
{
|
||||
OAuthResponseMessage(HttpResponseMessage http) throws IOException
|
||||
{
|
||||
super(http.method, http.url.toExternalForm(), null);
|
||||
this.http = http;
|
||||
getHeaders().addAll(http.headers);
|
||||
for (Map.Entry<String, String> header : http.headers) {
|
||||
if ("WWW-Authenticate".equalsIgnoreCase(header.getKey())) {
|
||||
for (OAuth.Parameter parameter : decodeAuthorization(header.getValue())) {
|
||||
if (!"realm".equalsIgnoreCase(parameter.getKey())) {
|
||||
addParameter(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final HttpResponseMessage http;
|
||||
|
||||
public HttpResponseMessage getHttpResponse() {
|
||||
return http;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getBodyAsStream() throws IOException
|
||||
{
|
||||
return http.getBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBodyEncoding()
|
||||
{
|
||||
return http.getContentCharset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requireParameters(String... names) throws OAuthProblemException, IOException {
|
||||
try {
|
||||
super.requireParameters(names);
|
||||
} catch (OAuthProblemException problem) {
|
||||
problem.getParameters().putAll(getDump());
|
||||
throw problem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulate this message as an exception. Read and close the body of this
|
||||
* message.
|
||||
*/
|
||||
public OAuthProblemException toOAuthProblemException() throws IOException {
|
||||
OAuthProblemException problem = new OAuthProblemException();
|
||||
try {
|
||||
getParameters(); // decode the response body
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
problem.getParameters().putAll(getDump());
|
||||
try {
|
||||
InputStream b = getBodyAsStream();
|
||||
if (b != null) {
|
||||
b.close(); // release resources
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return problem;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void completeParameters() throws IOException
|
||||
{
|
||||
super.completeParameters();
|
||||
String body = readBodyAsString();
|
||||
if (body != null) {
|
||||
addParameters(OAuth.decodeForm(body.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dump(Map<String, Object> into) throws IOException
|
||||
{
|
||||
super.dump(into);
|
||||
http.dump(into);
|
||||
}
|
||||
|
||||
}
|
122
bbb-lti/src/java/net/oauth/client/URLConnectionClient.java
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2008 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.oauth.http.HttpClient;
|
||||
import net.oauth.http.HttpMessage;
|
||||
import net.oauth.http.HttpResponseMessage;
|
||||
|
||||
/**
|
||||
* An HttpClient based on HttpURLConnection.
|
||||
* <p>
|
||||
* HttpClient3 or HttpClient4 perform better than this class, as a rule; since
|
||||
* they do things like connection pooling. They also support reading the body
|
||||
* of an HTTP response whose status code isn't 200 (OK), which can enable your
|
||||
* application to handle problems better.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class URLConnectionClient implements HttpClient {
|
||||
|
||||
/** Send a message to the service provider and get the response. */
|
||||
public HttpResponseMessage execute(HttpMessage request, Map<String, Object> parameters) throws IOException {
|
||||
final String httpMethod = request.method;
|
||||
final Collection<Map.Entry<String, String>> addHeaders = request.headers;
|
||||
final URL url = request.url;
|
||||
final URLConnection connection = url.openConnection();
|
||||
connection.setDoInput(true);
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
HttpURLConnection http = (HttpURLConnection) connection;
|
||||
http.setRequestMethod(httpMethod);
|
||||
for (Map.Entry<String, Object> p : parameters.entrySet()) {
|
||||
String name = p.getKey();
|
||||
String value = p.getValue().toString();
|
||||
if (FOLLOW_REDIRECTS.equals(name)) {
|
||||
http.setInstanceFollowRedirects(Boolean.parseBoolean(value));
|
||||
} else if (CONNECT_TIMEOUT.equals(name)) {
|
||||
http.setConnectTimeout(Integer.parseInt(value));
|
||||
} else if (READ_TIMEOUT.equals(name)) {
|
||||
http.setReadTimeout(Integer.parseInt(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
StringBuilder headers = new StringBuilder(httpMethod);
|
||||
{
|
||||
headers.append(" ").append(url.getPath());
|
||||
String query = url.getQuery();
|
||||
if (query != null && query.length() > 0) {
|
||||
headers.append("?").append(query);
|
||||
}
|
||||
headers.append(EOL);
|
||||
for (Map.Entry<String, List<String>> header : connection
|
||||
.getRequestProperties().entrySet()) {
|
||||
String key = header.getKey();
|
||||
for (String value : header.getValue()) {
|
||||
headers.append(key).append(": ").append(value).append(EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
String contentLength = null;
|
||||
for (Map.Entry<String, String> header : addHeaders) {
|
||||
String key = header.getKey();
|
||||
if (HttpMessage.CONTENT_LENGTH.equalsIgnoreCase(key)
|
||||
&& connection instanceof HttpURLConnection) {
|
||||
contentLength = header.getValue();
|
||||
} else {
|
||||
connection.setRequestProperty(key, header.getValue());
|
||||
}
|
||||
headers.append(key).append(": ").append(header.getValue()).append(EOL);
|
||||
}
|
||||
byte[] excerpt = null;
|
||||
final InputStream body = request.getBody();
|
||||
if (body != null) {
|
||||
try {
|
||||
if (contentLength != null) {
|
||||
((HttpURLConnection) connection)
|
||||
.setFixedLengthStreamingMode(Integer.parseInt(contentLength));
|
||||
}
|
||||
connection.setDoOutput(true);
|
||||
OutputStream output = connection.getOutputStream();
|
||||
try {
|
||||
final ExcerptInputStream ex = new ExcerptInputStream(body);
|
||||
byte[] b = new byte[1024];
|
||||
for (int n; 0 < (n = ex.read(b));) {
|
||||
output.write(b, 0, n);
|
||||
}
|
||||
excerpt = ex.getExcerpt();
|
||||
} finally {
|
||||
output.close();
|
||||
}
|
||||
} finally {
|
||||
body.close();
|
||||
}
|
||||
}
|
||||
return new URLConnectionResponse(request, headers.toString(), excerpt, connection);
|
||||
}
|
||||
|
||||
private static final String EOL = HttpResponseMessage.EOL;
|
||||
|
||||
}
|
135
bbb-lti/src/java/net/oauth/client/URLConnectionResponse.java
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2008 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.oauth.OAuth;
|
||||
import net.oauth.http.HttpMessage;
|
||||
import net.oauth.http.HttpResponseMessage;
|
||||
|
||||
/**
|
||||
* The response part of a URLConnection, encapsulated as an HttpMessage.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class URLConnectionResponse extends HttpResponseMessage {
|
||||
|
||||
/**
|
||||
* Construct an OAuthMessage from the HTTP response, including parameters
|
||||
* from OAuth WWW-Authenticate headers and the body. The header parameters
|
||||
* come first, followed by the ones from the response body.
|
||||
*/
|
||||
public URLConnectionResponse(HttpMessage request, String requestHeaders,
|
||||
byte[] requestExcerpt, URLConnection connection) throws IOException {
|
||||
super(request.method, request.url);
|
||||
this.requestHeaders = requestHeaders;
|
||||
this.requestExcerpt = requestExcerpt;
|
||||
this.requestEncoding = request.getContentCharset();
|
||||
this.connection = connection;
|
||||
this.headers.addAll(getHeaders());
|
||||
}
|
||||
|
||||
private final String requestHeaders;
|
||||
private final byte[] requestExcerpt;
|
||||
private final String requestEncoding;
|
||||
private final URLConnection connection;
|
||||
|
||||
@Override
|
||||
public int getStatusCode() throws IOException {
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
return ((HttpURLConnection) connection).getResponseCode();
|
||||
}
|
||||
return STATUS_OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openBody() {
|
||||
try {
|
||||
return connection.getInputStream();
|
||||
} catch (IOException ohWell) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Map.Entry<String, String>> getHeaders() {
|
||||
List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>();
|
||||
boolean foundContentType = false;
|
||||
String value;
|
||||
for (int i = 0; (value = connection.getHeaderField(i)) != null; ++i) {
|
||||
String name = connection.getHeaderFieldKey(i);
|
||||
if (name != null) {
|
||||
headers.add(new OAuth.Parameter(name, value));
|
||||
if (CONTENT_TYPE.equalsIgnoreCase(name)) {
|
||||
foundContentType = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundContentType) {
|
||||
headers.add(new OAuth.Parameter(CONTENT_TYPE, connection
|
||||
.getContentType()));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
/** Return a complete description of the HTTP exchange. */
|
||||
@Override
|
||||
public void dump(Map<String, Object> into) throws IOException {
|
||||
super.dump(into);
|
||||
{
|
||||
StringBuilder request = new StringBuilder(requestHeaders);
|
||||
request.append(EOL);
|
||||
if (requestExcerpt != null) {
|
||||
request.append(new String(requestExcerpt, requestEncoding));
|
||||
}
|
||||
into.put(REQUEST, request.toString());
|
||||
}
|
||||
{
|
||||
HttpURLConnection http = (connection instanceof HttpURLConnection) ? (HttpURLConnection) connection
|
||||
: null;
|
||||
StringBuilder response = new StringBuilder();
|
||||
String value;
|
||||
for (int i = 0; (value = connection.getHeaderField(i)) != null; ++i) {
|
||||
String name = connection.getHeaderFieldKey(i);
|
||||
if (i == 0 && name != null && http != null) {
|
||||
String firstLine = "HTTP " + getStatusCode();
|
||||
String message = http.getResponseMessage();
|
||||
if (message != null) {
|
||||
firstLine += (" " + message);
|
||||
}
|
||||
response.append(firstLine).append(EOL);
|
||||
}
|
||||
if (name != null) {
|
||||
response.append(name).append(": ");
|
||||
name = name.toLowerCase();
|
||||
}
|
||||
response.append(value).append(EOL);
|
||||
}
|
||||
response.append(EOL);
|
||||
if (body != null) {
|
||||
response.append(new String(((ExcerptInputStream) body)
|
||||
.getExcerpt(), getContentCharset()));
|
||||
}
|
||||
into.put(HttpMessage.RESPONSE, response.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
16
bbb-lti/src/java/net/oauth/consumer.properties.sample
Normal file
@ -0,0 +1,16 @@
|
||||
# NamedConsumerPool can gets consumer configuration parameters from a file like this.
|
||||
|
||||
ma.gnolia.consumerKey: - Your key here -
|
||||
ma.gnolia.consumerSecret: - Your secret here -
|
||||
ma.gnolia.serviceProvider.requestTokenURL: http://ma.gnolia.com/oauth/get_request_token
|
||||
ma.gnolia.serviceProvider.userAuthorizationURL: http://ma.gnolia.com/oauth/authorize
|
||||
ma.gnolia.serviceProvider.accessTokenURL: http://ma.gnolia.com/oauth/get_access_token
|
||||
|
||||
twitter.consumerKey: - Your key here -
|
||||
twitter.consumerSecret: - Your secret here -
|
||||
twitter.callbackURL: - Your URL here -
|
||||
twitter.consumer.oauth_signature_method: PLAINTEXT
|
||||
# There can be more consumer properties.
|
||||
twitter.serviceProvider.requestTokenURL: http://twitter.com/oauth/request_token
|
||||
twitter.serviceProvider.userAuthorizationURL: http://twitter.com/oauth/authorize
|
||||
twitter.serviceProvider.accessTokenURL: http://twitter.com/oauth/access_token
|
54
bbb-lti/src/java/net/oauth/http/HttpClient.java
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2008 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import net.oauth.OAuthMessage;
|
||||
|
||||
public interface HttpClient {
|
||||
|
||||
/**
|
||||
* Send an HTTP request and return the response.
|
||||
*
|
||||
* @param httpParameters
|
||||
* HTTP client parameters, as a map from parameter name to value.
|
||||
* Parameter names are defined as constants below.
|
||||
*/
|
||||
HttpResponseMessage execute(HttpMessage request, Map<String, Object> httpParameters) throws IOException;
|
||||
|
||||
/**
|
||||
* The name of the parameter that is the maximum time to wait to connect to
|
||||
* the server. (Integer msec)
|
||||
*/
|
||||
static final String CONNECT_TIMEOUT = "connectTimeout";
|
||||
|
||||
/**
|
||||
* The name of the parameter that is the maximum time to wait for response
|
||||
* data. (Integer msec)
|
||||
*/
|
||||
static final String READ_TIMEOUT = "readTimeout";
|
||||
|
||||
/** The name of the parameter to automatically follow redirects. (Boolean) */
|
||||
static final String FOLLOW_REDIRECTS = "followRedirects";
|
||||
|
||||
static final String GET = OAuthMessage.GET;
|
||||
static final String POST = OAuthMessage.POST;
|
||||
static final String PUT = OAuthMessage.PUT;
|
||||
static final String DELETE = OAuthMessage.DELETE;
|
||||
|
||||
}
|
160
bbb-lti/src/java/net/oauth/http/HttpMessage.java
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2008 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import net.oauth.client.ExcerptInputStream;
|
||||
|
||||
/**
|
||||
* An HTTP request or response.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class HttpMessage
|
||||
{
|
||||
|
||||
public HttpMessage()
|
||||
{
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public HttpMessage(String method, URL url)
|
||||
{
|
||||
this(method, url, null);
|
||||
}
|
||||
|
||||
public HttpMessage(String method, URL url, InputStream body)
|
||||
{
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public String method;
|
||||
public URL url;
|
||||
public final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>();
|
||||
protected InputStream body = null;
|
||||
|
||||
/**
|
||||
* Get the value of the last header of the given name. The name is
|
||||
* case-insensitive.
|
||||
*/
|
||||
public final String getHeader(String name)
|
||||
{
|
||||
String value = null;
|
||||
for (Map.Entry<String, String> header : headers) {
|
||||
if (equalsIgnoreCase(name, header.getKey())) {
|
||||
value = header.getValue();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all headers of the given name. The name is case insensitive.
|
||||
*
|
||||
* @return the value of the last header with that name, or null to indicate
|
||||
* there was no such header
|
||||
*/
|
||||
public String removeHeaders(String name)
|
||||
{
|
||||
String value = null;
|
||||
for (Iterator<Map.Entry<String, String>> i = headers.iterator(); i.hasNext();) {
|
||||
Map.Entry<String, String> header = i.next();
|
||||
if (equalsIgnoreCase(name, header.getKey())) {
|
||||
value = header.getValue();
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public final String getContentCharset()
|
||||
{
|
||||
return getCharset(getHeader(CONTENT_TYPE));
|
||||
}
|
||||
|
||||
public final InputStream getBody() throws IOException
|
||||
{
|
||||
if (body == null) {
|
||||
InputStream raw = openBody();
|
||||
if (raw != null) {
|
||||
body = new ExcerptInputStream(raw);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
protected InputStream openBody() throws IOException
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Put a description of this message and its origins into the given Map. */
|
||||
public void dump(Map<String, Object> into) throws IOException
|
||||
{
|
||||
}
|
||||
|
||||
private static boolean equalsIgnoreCase(String x, String y)
|
||||
{
|
||||
if (x == null)
|
||||
return y == null;
|
||||
else
|
||||
return x.equalsIgnoreCase(y);
|
||||
}
|
||||
|
||||
private static final String getCharset(String mimeType)
|
||||
{
|
||||
if (mimeType != null) {
|
||||
Matcher m = CHARSET.matcher(mimeType);
|
||||
if (m.find()) {
|
||||
String charset = m.group(1);
|
||||
if (charset.length() >= 2 && charset.charAt(0) == '"'
|
||||
&& charset.charAt(charset.length() - 1) == '"') {
|
||||
charset = charset.substring(1, charset.length() - 1);
|
||||
charset = charset.replace("\\\"", "\"");
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
}
|
||||
return DEFAULT_CHARSET;
|
||||
}
|
||||
|
||||
/** The name of a dump entry whose value is the HTTP request. */
|
||||
public static final String REQUEST = "HTTP request";
|
||||
|
||||
/** The name of a dump entry whose value is the HTTP response. */
|
||||
public static final String RESPONSE = "HTTP response";
|
||||
|
||||
public static final String ACCEPT_ENCODING = "Accept-Encoding";
|
||||
public static final String CONTENT_ENCODING = "Content-Encoding";
|
||||
public static final String CONTENT_LENGTH = "Content-Length";
|
||||
public static final String CONTENT_TYPE = "Content-Type";
|
||||
public static final String DEFAULT_CHARSET = "ISO-8859-1";
|
||||
|
||||
private static final Pattern CHARSET = Pattern
|
||||
.compile("; *charset *= *([^;\"]*|\"([^\"]|\\\\\")*\")(;|$)");
|
||||
|
||||
}
|
94
bbb-lti/src/java/net/oauth/http/HttpMessageDecoder.java
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2008 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
/** A decorator that handles Content-Encoding. */
|
||||
public class HttpMessageDecoder extends HttpResponseMessage {
|
||||
|
||||
/**
|
||||
* Decode the given message if necessary and possible.
|
||||
*
|
||||
* @return a decorator that decodes the body of the given message; or the
|
||||
* given message if this class can't decode it.
|
||||
*/
|
||||
public static HttpResponseMessage decode(HttpResponseMessage message)
|
||||
throws IOException {
|
||||
if (message != null) {
|
||||
String encoding = getEncoding(message);
|
||||
if (encoding != null) {
|
||||
return new HttpMessageDecoder(message, encoding);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public static final String GZIP = "gzip";
|
||||
public static final String DEFLATE = "deflate";
|
||||
public static final String ACCEPTED = GZIP + "," + DEFLATE;
|
||||
|
||||
private static String getEncoding(HttpMessage message) {
|
||||
String encoding = message.getHeader(CONTENT_ENCODING);
|
||||
if (encoding == null) {
|
||||
// That's easy.
|
||||
} else if (GZIP.equalsIgnoreCase(encoding)
|
||||
|| ("x-" + GZIP).equalsIgnoreCase(encoding)) {
|
||||
return GZIP;
|
||||
} else if (DEFLATE.equalsIgnoreCase(encoding)) {
|
||||
return DEFLATE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private HttpMessageDecoder(HttpResponseMessage in, String encoding)
|
||||
throws IOException {
|
||||
super(in.method, in.url);
|
||||
this.headers.addAll(in.headers);
|
||||
removeHeaders(CONTENT_ENCODING); // handled here
|
||||
removeHeaders(CONTENT_LENGTH); // unpredictable
|
||||
InputStream body = in.getBody();
|
||||
if (body != null) {
|
||||
if (encoding == GZIP) {
|
||||
body = new GZIPInputStream(body);
|
||||
} else if (encoding == DEFLATE) {
|
||||
body = new InflaterInputStream(body);
|
||||
} else {
|
||||
assert false;
|
||||
}
|
||||
}
|
||||
this.body = body;
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
private final HttpResponseMessage in;
|
||||
|
||||
@Override
|
||||
public void dump(Map<String, Object> into) throws IOException {
|
||||
in.dump(into);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatusCode() throws IOException {
|
||||
return in.getStatusCode();
|
||||
}
|
||||
|
||||
}
|
58
bbb-lti/src/java/net/oauth/http/HttpResponseMessage.java
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2008 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An HTTP response.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public abstract class HttpResponseMessage extends HttpMessage {
|
||||
|
||||
protected HttpResponseMessage(String method, URL url) {
|
||||
super(method, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Map<String, Object> into) throws IOException {
|
||||
super.dump(into);
|
||||
into.put(STATUS_CODE, Integer.valueOf(getStatusCode()));
|
||||
String location = getHeader(LOCATION);
|
||||
if (location != null) {
|
||||
into.put(LOCATION, location);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract int getStatusCode() throws IOException;
|
||||
|
||||
/** The name of a dump entry whose value is the response Location header. */
|
||||
public static final String LOCATION = "Location";
|
||||
|
||||
/** The name of a dump entry whose value is the HTTP status code. */
|
||||
public static final String STATUS_CODE = "HTTP status";
|
||||
|
||||
/** The statusCode that indicates a normal outcome. */
|
||||
public static final int STATUS_OK = 200;
|
||||
|
||||
/** The standard end-of-line marker in an HTTP message. */
|
||||
public static final String EOL = "\r\n";
|
||||
|
||||
}
|
92
bbb-lti/src/java/net/oauth/server/HttpRequestMessage.java
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2008 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import net.oauth.OAuth;
|
||||
import net.oauth.OAuthMessage;
|
||||
|
||||
/**
|
||||
* An HttpServletRequest, encapsulated as an OAuthMessage.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class HttpRequestMessage extends OAuthMessage {
|
||||
|
||||
public HttpRequestMessage(HttpServletRequest request, String URL) {
|
||||
super(request.getMethod(), URL, getParameters(request));
|
||||
this.request = request;
|
||||
copyHeaders(request, getHeaders());
|
||||
}
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
@Override
|
||||
public InputStream getBodyAsStream() throws IOException {
|
||||
return request.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBodyEncoding() {
|
||||
return request.getCharacterEncoding();
|
||||
}
|
||||
|
||||
private static void copyHeaders(HttpServletRequest request, Collection<Map.Entry<String, String>> into) {
|
||||
Enumeration<String> names = request.getHeaderNames();
|
||||
if (names != null) {
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
Enumeration<String> values = request.getHeaders(name);
|
||||
if (values != null) {
|
||||
while (values.hasMoreElements()) {
|
||||
into.add(new OAuth.Parameter(name, values.nextElement()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<OAuth.Parameter> getParameters(HttpServletRequest request) {
|
||||
List<OAuth.Parameter> list = new ArrayList<OAuth.Parameter>();
|
||||
for (Enumeration<String> headers = request.getHeaders("Authorization"); headers != null
|
||||
&& headers.hasMoreElements();) {
|
||||
String header = headers.nextElement();
|
||||
for (OAuth.Parameter parameter : OAuthMessage
|
||||
.decodeAuthorization(header)) {
|
||||
if (!"realm".equalsIgnoreCase(parameter.getKey())) {
|
||||
list.add(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Object e : request.getParameterMap().entrySet()) {
|
||||
Map.Entry<String, String[]> entry = (Map.Entry<String, String[]>) e;
|
||||
String name = entry.getKey();
|
||||
for (String value : entry.getValue()) {
|
||||
list.add(new OAuth.Parameter(name, value));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
158
bbb-lti/src/java/net/oauth/server/OAuthServlet.java
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2007 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import net.oauth.OAuth;
|
||||
import net.oauth.OAuthMessage;
|
||||
import net.oauth.OAuthProblemException;
|
||||
import net.oauth.http.HttpResponseMessage;
|
||||
|
||||
/**
|
||||
* Utility methods for servlets that implement OAuth.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public class OAuthServlet {
|
||||
|
||||
/**
|
||||
* Extract the parts of the given request that are relevant to OAuth.
|
||||
* Parameters include OAuth Authorization headers and the usual request
|
||||
* parameters in the query string and/or form encoded body. The header
|
||||
* parameters come first, followed by the rest in the order they came from
|
||||
* request.getParameterMap().
|
||||
*
|
||||
* @param URL
|
||||
* the official URL of this service; that is the URL a legitimate
|
||||
* client would use to compute the digital signature. If this
|
||||
* parameter is null, this method will try to reconstruct the URL
|
||||
* from the HTTP request; which may be wrong in some cases.
|
||||
*/
|
||||
public static OAuthMessage getMessage(HttpServletRequest request, String URL) {
|
||||
if (URL == null) {
|
||||
URL = request.getRequestURL().toString();
|
||||
}
|
||||
int q = URL.indexOf('?');
|
||||
if (q >= 0) {
|
||||
URL = URL.substring(0, q);
|
||||
// The query string parameters will be included in
|
||||
// the result from getParameters(request).
|
||||
}
|
||||
return new HttpRequestMessage(request, URL);
|
||||
}
|
||||
|
||||
/** Reconstruct the requested URL, complete with query string (if any). */
|
||||
public static String getRequestURL(HttpServletRequest request) {
|
||||
StringBuffer url = request.getRequestURL();
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null) {
|
||||
url.append("?").append(queryString);
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
public static void handleException(HttpServletResponse response,
|
||||
Exception e, String realm) throws IOException, ServletException {
|
||||
handleException(response, e, realm, true);
|
||||
}
|
||||
|
||||
public static void handleException(HttpServletResponse response,
|
||||
Exception e, String realm, boolean sendBody) throws IOException,
|
||||
ServletException {
|
||||
if (e instanceof OAuthProblemException) {
|
||||
OAuthProblemException problem = (OAuthProblemException) e;
|
||||
Object httpCode = problem.getParameters().get(HttpResponseMessage.STATUS_CODE);
|
||||
if (httpCode == null) {
|
||||
httpCode = PROBLEM_TO_HTTP_CODE.get(problem.getProblem());
|
||||
}
|
||||
if (httpCode == null) {
|
||||
httpCode = SC_FORBIDDEN;
|
||||
}
|
||||
response.reset();
|
||||
response.setStatus(Integer.parseInt(httpCode.toString()));
|
||||
OAuthMessage message = new OAuthMessage(null, null, problem
|
||||
.getParameters().entrySet());
|
||||
response.addHeader("WWW-Authenticate", message
|
||||
.getAuthorizationHeader(realm));
|
||||
if (sendBody) {
|
||||
sendForm(response, message.getParameters());
|
||||
}
|
||||
} else if (e instanceof IOException) {
|
||||
throw (IOException) e;
|
||||
} else if (e instanceof ServletException) {
|
||||
throw (ServletException) e;
|
||||
} else if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
} else {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Integer SC_FORBIDDEN = new Integer(
|
||||
HttpServletResponse.SC_FORBIDDEN);
|
||||
|
||||
private static final Map<String, Integer> PROBLEM_TO_HTTP_CODE = OAuth.Problems.TO_HTTP_CODE;
|
||||
|
||||
/** Send the given parameters as a form-encoded response body. */
|
||||
public static void sendForm(HttpServletResponse response,
|
||||
Iterable<? extends Map.Entry> parameters) throws IOException {
|
||||
response.resetBuffer();
|
||||
response.setContentType(OAuth.FORM_ENCODED + ";charset="
|
||||
+ OAuth.ENCODING);
|
||||
OAuth.formEncode(parameters, response.getOutputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HTML representation of the given plain text. Characters that
|
||||
* would have special significance in HTML are replaced by <a
|
||||
* href="http://www.w3.org/TR/html401/sgml/entities.html">character entity
|
||||
* references</a>. Whitespace is not converted.
|
||||
*/
|
||||
public static String htmlEncode(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder html = new StringBuilder(s.length());
|
||||
for (char c : s.toCharArray()) {
|
||||
switch (c) {
|
||||
case '<':
|
||||
html.append("<");
|
||||
break;
|
||||
case '>':
|
||||
html.append(">");
|
||||
break;
|
||||
case '&':
|
||||
html.append("&");
|
||||
// This also takes care of numeric character references;
|
||||
// for example © becomes &#169.
|
||||
break;
|
||||
case '"':
|
||||
html.append(""");
|
||||
break;
|
||||
default:
|
||||
html.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return html.toString();
|
||||
}
|
||||
|
||||
}
|
714
bbb-lti/src/java/net/oauth/signature/Base64.java
Normal file
@ -0,0 +1,714 @@
|
||||
/*
|
||||
* Copyright 2001-2008 The Apache Software Foundation.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.signature;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Provides Base64 encoding and decoding as defined by RFC 2045.
|
||||
*
|
||||
* <p>
|
||||
* This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
|
||||
* Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
|
||||
* </p>
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
|
||||
* @author Apache Software Foundation
|
||||
* @author John Kristian
|
||||
*/
|
||||
class Base64 {
|
||||
/**
|
||||
* Chunk size per RFC 2045 section 6.8.
|
||||
*
|
||||
* <p>
|
||||
* The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
|
||||
* equal signs.
|
||||
* </p>
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
|
||||
*/
|
||||
static final int CHUNK_SIZE = 76;
|
||||
|
||||
/**
|
||||
* Chunk separator per RFC 2045 section 2.1.
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
|
||||
*/
|
||||
static final byte[] CHUNK_SEPARATOR = {'\r','\n'};
|
||||
|
||||
/**
|
||||
* This array is a lookup table that translates 6-bit positive integer
|
||||
* index values into their "Base64 Alphabet" equivalents as specified
|
||||
* in Table 1 of RFC 2045.
|
||||
*
|
||||
* Thanks to "commons" project in ws.apache.org for this code.
|
||||
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
|
||||
*/
|
||||
private static final byte[] intToBase64 = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
|
||||
};
|
||||
|
||||
/**
|
||||
* Byte used to pad output.
|
||||
*/
|
||||
private static final byte PAD = '=';
|
||||
|
||||
/**
|
||||
* This array is a lookup table that translates unicode characters
|
||||
* drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
|
||||
* into their 6-bit positive integer equivalents. Characters that
|
||||
* are not in the Base64 alphabet but fall within the bounds of the
|
||||
* array are translated to -1.
|
||||
*
|
||||
* Thanks to "commons" project in ws.apache.org for this code.
|
||||
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
|
||||
*/
|
||||
private static final byte[] base64ToInt = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
|
||||
55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
|
||||
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
|
||||
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||
};
|
||||
|
||||
/** Mask used to extract 6 bits, used when encoding */
|
||||
private static final int MASK_6BITS = 0x3f;
|
||||
|
||||
/** Mask used to extract 8 bits, used in decoding base64 bytes */
|
||||
private static final int MASK_8BITS = 0xff;
|
||||
|
||||
// The static final fields above are used for the original static byte[] methods on Base64.
|
||||
// The private member fields below are used with the new streaming approach, which requires
|
||||
// some state be preserved between calls of encode() and decode().
|
||||
|
||||
|
||||
/**
|
||||
* Line length for encoding. Not used when decoding. A value of zero or less implies
|
||||
* no chunking of the base64 encoded data.
|
||||
*/
|
||||
private final int lineLength;
|
||||
|
||||
/**
|
||||
* Line separator for encoding. Not used when decoding. Only used if lineLength > 0.
|
||||
*/
|
||||
private final byte[] lineSeparator;
|
||||
|
||||
/**
|
||||
* Convenience variable to help us determine when our buffer is going to run out of
|
||||
* room and needs resizing. <code>decodeSize = 3 + lineSeparator.length;</code>
|
||||
*/
|
||||
private final int decodeSize;
|
||||
|
||||
/**
|
||||
* Convenience variable to help us determine when our buffer is going to run out of
|
||||
* room and needs resizing. <code>encodeSize = 4 + lineSeparator.length;</code>
|
||||
*/
|
||||
private final int encodeSize;
|
||||
|
||||
/**
|
||||
* Buffer for streaming.
|
||||
*/
|
||||
private byte[] buf;
|
||||
|
||||
/**
|
||||
* Position where next character should be written in the buffer.
|
||||
*/
|
||||
private int pos;
|
||||
|
||||
/**
|
||||
* Position where next character should be read from the buffer.
|
||||
*/
|
||||
private int readPos;
|
||||
|
||||
/**
|
||||
* Variable tracks how many characters have been written to the current line.
|
||||
* Only used when encoding. We use it to make sure each encoded line never
|
||||
* goes beyond lineLength (if lineLength > 0).
|
||||
*/
|
||||
private int currentLinePos;
|
||||
|
||||
/**
|
||||
* Writes to the buffer only occur after every 3 reads when encoding, an
|
||||
* every 4 reads when decoding. This variable helps track that.
|
||||
*/
|
||||
private int modulus;
|
||||
|
||||
/**
|
||||
* Boolean flag to indicate the EOF has been reached. Once EOF has been
|
||||
* reached, this Base64 object becomes useless, and must be thrown away.
|
||||
*/
|
||||
private boolean eof;
|
||||
|
||||
/**
|
||||
* Place holder for the 3 bytes we're dealing with for our base64 logic.
|
||||
* Bitwise operations store and extract the base64 encoding or decoding from
|
||||
* this variable.
|
||||
*/
|
||||
private int x;
|
||||
|
||||
/**
|
||||
* Default constructor: lineLength is 76, and the lineSeparator is CRLF
|
||||
* when encoding, and all forms can be decoded.
|
||||
*/
|
||||
public Base64() {
|
||||
this(CHUNK_SIZE, CHUNK_SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Consumer can use this constructor to choose a different lineLength
|
||||
* when encoding (lineSeparator is still CRLF). All forms of data can
|
||||
* be decoded.
|
||||
* </p><p>
|
||||
* Note: lineLengths that aren't multiples of 4 will still essentially
|
||||
* end up being multiples of 4 in the encoded data.
|
||||
* </p>
|
||||
*
|
||||
* @param lineLength each line of encoded data will be at most this long
|
||||
* (rounded up to nearest multiple of 4).
|
||||
* If lineLength <= 0, then the output will not be divided into lines (chunks).
|
||||
* Ignored when decoding.
|
||||
*/
|
||||
public Base64(int lineLength) {
|
||||
this(lineLength, CHUNK_SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Consumer can use this constructor to choose a different lineLength
|
||||
* and lineSeparator when encoding. All forms of data can
|
||||
* be decoded.
|
||||
* </p><p>
|
||||
* Note: lineLengths that aren't multiples of 4 will still essentially
|
||||
* end up being multiples of 4 in the encoded data.
|
||||
* </p>
|
||||
* @param lineLength Each line of encoded data will be at most this long
|
||||
* (rounded up to nearest multiple of 4). Ignored when decoding.
|
||||
* If <= 0, then output will not be divided into lines (chunks).
|
||||
* @param lineSeparator Each line of encoded data will end with this
|
||||
* sequence of bytes.
|
||||
* If lineLength <= 0, then the lineSeparator is not used.
|
||||
* @throws IllegalArgumentException The provided lineSeparator included
|
||||
* some base64 characters. That's not going to work!
|
||||
*/
|
||||
public Base64(int lineLength, byte[] lineSeparator) {
|
||||
this.lineLength = lineLength;
|
||||
this.lineSeparator = new byte[lineSeparator.length];
|
||||
System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
|
||||
if (lineLength > 0) {
|
||||
this.encodeSize = 4 + lineSeparator.length;
|
||||
} else {
|
||||
this.encodeSize = 4;
|
||||
}
|
||||
this.decodeSize = encodeSize - 1;
|
||||
if (containsBase64Byte(lineSeparator)) {
|
||||
String sep;
|
||||
try {
|
||||
sep = new String(lineSeparator, "UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
sep = new String(lineSeparator);
|
||||
}
|
||||
throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this Base64 object has buffered data for reading.
|
||||
*
|
||||
* @return true if there is Base64 object still available for reading.
|
||||
*/
|
||||
boolean hasData() { return buf != null; }
|
||||
|
||||
/**
|
||||
* Returns the amount of buffered data available for reading.
|
||||
*
|
||||
* @return The amount of buffered data available for reading.
|
||||
*/
|
||||
int avail() { return buf != null ? pos - readPos : 0; }
|
||||
|
||||
/** Doubles our buffer. */
|
||||
private void resizeBuf() {
|
||||
if (buf == null) {
|
||||
buf = new byte[8192];
|
||||
pos = 0;
|
||||
readPos = 0;
|
||||
} else {
|
||||
byte[] b = new byte[buf.length * 2];
|
||||
System.arraycopy(buf, 0, b, 0, buf.length);
|
||||
buf = b;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts buffered data into the provided byte[] array, starting
|
||||
* at position bPos, up to a maximum of bAvail bytes. Returns how
|
||||
* many bytes were actually extracted.
|
||||
*
|
||||
* @param b byte[] array to extract the buffered data into.
|
||||
* @param bPos position in byte[] array to start extraction at.
|
||||
* @param bAvail amount of bytes we're allowed to extract. We may extract
|
||||
* fewer (if fewer are available).
|
||||
* @return The number of bytes successfully extracted into the provided
|
||||
* byte[] array.
|
||||
*/
|
||||
int readResults(byte[] b, int bPos, int bAvail) {
|
||||
if (buf != null) {
|
||||
int len = Math.min(avail(), bAvail);
|
||||
if (buf != b) {
|
||||
System.arraycopy(buf, readPos, b, bPos, len);
|
||||
readPos += len;
|
||||
if (readPos >= pos) {
|
||||
buf = null;
|
||||
}
|
||||
} else {
|
||||
// Re-using the original consumer's output array is only
|
||||
// allowed for one round.
|
||||
buf = null;
|
||||
}
|
||||
return len;
|
||||
} else {
|
||||
return eof ? -1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Small optimization where we try to buffer directly to the consumer's
|
||||
* output array for one round (if consumer calls this method first!) instead
|
||||
* of starting our own buffer.
|
||||
*
|
||||
* @param out byte[] array to buffer directly to.
|
||||
* @param outPos Position to start buffering into.
|
||||
* @param outAvail Amount of bytes available for direct buffering.
|
||||
*/
|
||||
void setInitialBuffer(byte[] out, int outPos, int outAvail) {
|
||||
// We can re-use consumer's original output array under
|
||||
// special circumstances, saving on some System.arraycopy().
|
||||
if (out != null && out.length == outAvail) {
|
||||
buf = out;
|
||||
pos = outPos;
|
||||
readPos = outPos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Encodes all of the provided data, starting at inPos, for inAvail bytes.
|
||||
* Must be called at least twice: once with the data to encode, and once
|
||||
* with inAvail set to "-1" to alert encoder that EOF has been reached,
|
||||
* so flush last remaining bytes (if not multiple of 3).
|
||||
* </p><p>
|
||||
* Thanks to "commons" project in ws.apache.org for the bitwise operations,
|
||||
* and general approach.
|
||||
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
|
||||
* </p>
|
||||
*
|
||||
* @param in byte[] array of binary data to base64 encode.
|
||||
* @param inPos Position to start reading data from.
|
||||
* @param inAvail Amount of bytes available from input for encoding.
|
||||
*/
|
||||
void encode(byte[] in, int inPos, int inAvail) {
|
||||
if (eof) {
|
||||
return;
|
||||
}
|
||||
|
||||
// inAvail < 0 is how we're informed of EOF in the underlying data we're
|
||||
// encoding.
|
||||
if (inAvail < 0) {
|
||||
eof = true;
|
||||
if (buf == null || buf.length - pos < encodeSize) {
|
||||
resizeBuf();
|
||||
}
|
||||
switch (modulus) {
|
||||
case 1:
|
||||
buf[pos++] = intToBase64[(x >> 2) & MASK_6BITS];
|
||||
buf[pos++] = intToBase64[(x << 4) & MASK_6BITS];
|
||||
buf[pos++] = PAD;
|
||||
buf[pos++] = PAD;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
buf[pos++] = intToBase64[(x >> 10) & MASK_6BITS];
|
||||
buf[pos++] = intToBase64[(x >> 4) & MASK_6BITS];
|
||||
buf[pos++] = intToBase64[(x << 2) & MASK_6BITS];
|
||||
buf[pos++] = PAD;
|
||||
break;
|
||||
}
|
||||
if (lineLength > 0) {
|
||||
System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
|
||||
pos += lineSeparator.length;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < inAvail; i++) {
|
||||
if (buf == null || buf.length - pos < encodeSize) {
|
||||
resizeBuf();
|
||||
}
|
||||
modulus = (++modulus) % 3;
|
||||
int b = in[inPos++];
|
||||
if (b < 0) { b += 256; }
|
||||
x = (x << 8) + b;
|
||||
if (0 == modulus) {
|
||||
buf[pos++] = intToBase64[(x >> 18) & MASK_6BITS];
|
||||
buf[pos++] = intToBase64[(x >> 12) & MASK_6BITS];
|
||||
buf[pos++] = intToBase64[(x >> 6) & MASK_6BITS];
|
||||
buf[pos++] = intToBase64[x & MASK_6BITS];
|
||||
currentLinePos += 4;
|
||||
if (lineLength > 0 && lineLength <= currentLinePos) {
|
||||
System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
|
||||
pos += lineSeparator.length;
|
||||
currentLinePos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Decodes all of the provided data, starting at inPos, for inAvail bytes.
|
||||
* Should be called at least twice: once with the data to decode, and once
|
||||
* with inAvail set to "-1" to alert decoder that EOF has been reached.
|
||||
* The "-1" call is not necessary when decoding, but it doesn't hurt, either.
|
||||
* </p><p>
|
||||
* Ignores all non-base64 characters. This is how chunked (e.g. 76 character)
|
||||
* data is handled, since CR and LF are silently ignored, but has implications
|
||||
* for other bytes, too. This method subscribes to the garbage-in, garbage-out
|
||||
* philosophy: it will not check the provided data for validity.
|
||||
* </p><p>
|
||||
* Thanks to "commons" project in ws.apache.org for the bitwise operations,
|
||||
* and general approach.
|
||||
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
|
||||
* </p>
|
||||
|
||||
* @param in byte[] array of ascii data to base64 decode.
|
||||
* @param inPos Position to start reading data from.
|
||||
* @param inAvail Amount of bytes available from input for encoding.
|
||||
*/
|
||||
void decode(byte[] in, int inPos, int inAvail) {
|
||||
if (eof) {
|
||||
return;
|
||||
}
|
||||
if (inAvail < 0) {
|
||||
eof = true;
|
||||
}
|
||||
for (int i = 0; i < inAvail; i++) {
|
||||
if (buf == null || buf.length - pos < decodeSize) {
|
||||
resizeBuf();
|
||||
}
|
||||
byte b = in[inPos++];
|
||||
if (b == PAD) {
|
||||
x = x << 6;
|
||||
switch (modulus) {
|
||||
case 2:
|
||||
x = x << 6;
|
||||
buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
|
||||
break;
|
||||
case 3:
|
||||
buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
|
||||
buf[pos++] = (byte) ((x >> 8) & MASK_8BITS);
|
||||
break;
|
||||
}
|
||||
// WE'RE DONE!!!!
|
||||
eof = true;
|
||||
return;
|
||||
} else {
|
||||
if (b >= 0 && b < base64ToInt.length) {
|
||||
int result = base64ToInt[b];
|
||||
if (result >= 0) {
|
||||
modulus = (++modulus) % 4;
|
||||
x = (x << 6) + result;
|
||||
if (modulus == 0) {
|
||||
buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
|
||||
buf[pos++] = (byte) ((x >> 8) & MASK_8BITS);
|
||||
buf[pos++] = (byte) (x & MASK_8BITS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the <code>octet</code> is in the base 64 alphabet.
|
||||
*
|
||||
* @param octet
|
||||
* The value to test
|
||||
* @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
|
||||
*/
|
||||
public static boolean isBase64(byte octet) {
|
||||
return octet == PAD || (octet >= 0 && octet < base64ToInt.length && base64ToInt[octet] != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
|
||||
* Currently the method treats whitespace as valid.
|
||||
*
|
||||
* @param arrayOctet
|
||||
* byte array to test
|
||||
* @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is
|
||||
* empty; false, otherwise
|
||||
*/
|
||||
public static boolean isArrayByteBase64(byte[] arrayOctet) {
|
||||
for (int i = 0; i < arrayOctet.length; i++) {
|
||||
if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
|
||||
*
|
||||
* @param arrayOctet
|
||||
* byte array to test
|
||||
* @return <code>true</code> if any byte is a valid character in the Base64 alphabet; false herwise
|
||||
*/
|
||||
private static boolean containsBase64Byte(byte[] arrayOctet) {
|
||||
for (int i = 0; i < arrayOctet.length; i++) {
|
||||
if (isBase64(arrayOctet[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes binary data using the base64 algorithm but does not chunk the output.
|
||||
*
|
||||
* @param binaryData
|
||||
* binary data to encode
|
||||
* @return Base64 characters
|
||||
*/
|
||||
public static byte[] encodeBase64(byte[] binaryData) {
|
||||
return encodeBase64(binaryData, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
|
||||
*
|
||||
* @param binaryData
|
||||
* binary data to encode
|
||||
* @return Base64 characters chunked in 76 character blocks
|
||||
*/
|
||||
public static byte[] encodeBase64Chunked(byte[] binaryData) {
|
||||
return encodeBase64(binaryData, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a byte[] containing containing characters in the Base64 alphabet.
|
||||
*
|
||||
* @param pArray
|
||||
* A byte array containing Base64 character data
|
||||
* @return a byte array containing binary data
|
||||
*/
|
||||
public byte[] decode(byte[] pArray) {
|
||||
return decodeBase64(pArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
|
||||
*
|
||||
* @param binaryData
|
||||
* Array containing binary data to encode.
|
||||
* @param isChunked
|
||||
* if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
|
||||
* @return Base64-encoded data.
|
||||
* @throws IllegalArgumentException
|
||||
* Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
|
||||
*/
|
||||
public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) {
|
||||
if (binaryData == null || binaryData.length == 0) {
|
||||
return binaryData;
|
||||
}
|
||||
Base64 b64 = isChunked ? new Base64() : new Base64(0);
|
||||
|
||||
long len = (binaryData.length * 4) / 3;
|
||||
long mod = len % 4;
|
||||
if (mod != 0) {
|
||||
len += 4 - mod;
|
||||
}
|
||||
if (isChunked) {
|
||||
len += (1 + (len / CHUNK_SIZE)) * CHUNK_SEPARATOR.length;
|
||||
}
|
||||
|
||||
if (len > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException(
|
||||
"Input array too big, output array would be bigger than Integer.MAX_VALUE=" + Integer.MAX_VALUE);
|
||||
}
|
||||
byte[] buf = new byte[(int) len];
|
||||
b64.setInitialBuffer(buf, 0, buf.length);
|
||||
b64.encode(binaryData, 0, binaryData.length);
|
||||
b64.encode(binaryData, 0, -1); // Notify encoder of EOF.
|
||||
|
||||
// Encoder might have resized, even though it was unnecessary.
|
||||
if (b64.buf != buf) {
|
||||
b64.readResults(buf, 0, buf.length);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 data into octets
|
||||
*
|
||||
* @param base64Data Byte array containing Base64 data
|
||||
* @return Array containing decoded data.
|
||||
*/
|
||||
public static byte[] decodeBase64(byte[] base64Data) {
|
||||
if (base64Data == null || base64Data.length == 0) {
|
||||
return base64Data;
|
||||
}
|
||||
Base64 b64 = new Base64();
|
||||
|
||||
long len = (base64Data.length * 3) / 4;
|
||||
byte[] buf = new byte[(int) len];
|
||||
b64.setInitialBuffer(buf, 0, buf.length);
|
||||
b64.decode(base64Data, 0, base64Data.length);
|
||||
b64.decode(base64Data, 0, -1); // Notify decoder of EOF.
|
||||
|
||||
// We have no idea what the line-length was, so we
|
||||
// cannot know how much of our array wasn't used.
|
||||
byte[] result = new byte[b64.pos];
|
||||
b64.readResults(result, 0, result.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a byte value is whitespace or not.
|
||||
*
|
||||
* @param byteToCheck the byte to check
|
||||
* @return true if byte is whitespace, false otherwise
|
||||
*/
|
||||
private static boolean isWhiteSpace(byte byteToCheck){
|
||||
switch (byteToCheck) {
|
||||
case ' ' :
|
||||
case '\n' :
|
||||
case '\r' :
|
||||
case '\t' :
|
||||
return true;
|
||||
default :
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards any characters outside of the base64 alphabet, per the requirements on page 25 of RFC 2045 - "Any
|
||||
* characters outside of the base64 alphabet are to be ignored in base64 encoded data."
|
||||
*
|
||||
* @param data
|
||||
* The base-64 encoded data to groom
|
||||
* @return The data, less non-base64 characters (see RFC 2045).
|
||||
*/
|
||||
static byte[] discardNonBase64(byte[] data) {
|
||||
byte groomedData[] = new byte[data.length];
|
||||
int bytesCopied = 0;
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if (isBase64(data[i])) {
|
||||
groomedData[bytesCopied++] = data[i];
|
||||
}
|
||||
}
|
||||
|
||||
byte packedData[] = new byte[bytesCopied];
|
||||
|
||||
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
|
||||
|
||||
return packedData;
|
||||
}
|
||||
|
||||
// Implementation of the Encoder Interface
|
||||
|
||||
/**
|
||||
* Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet.
|
||||
*
|
||||
* @param pArray
|
||||
* a byte array containing binary data
|
||||
* @return A byte array containing only Base64 character data
|
||||
*/
|
||||
public byte[] encode(byte[] pArray) {
|
||||
return encodeBase64(pArray, false);
|
||||
}
|
||||
|
||||
// Implementation of integer encoding used for crypto
|
||||
/**
|
||||
* Decode a byte64-encoded integer according to crypto
|
||||
* standards such as W3C's XML-Signature
|
||||
*
|
||||
* @param pArray a byte array containing base64 character data
|
||||
* @return A BigInteger
|
||||
*/
|
||||
public static BigInteger decodeInteger(byte[] pArray) {
|
||||
return new BigInteger(1, decodeBase64(pArray));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode to a byte64-encoded integer according to crypto
|
||||
* standards such as W3C's XML-Signature
|
||||
*
|
||||
* @param bigInt a BigInteger
|
||||
* @return A byte array containing base64 character data
|
||||
* @throws NullPointerException if null is passed in
|
||||
*/
|
||||
public static byte[] encodeInteger(BigInteger bigInt) {
|
||||
if(bigInt == null) {
|
||||
throw new NullPointerException("encodeInteger called with null parameter");
|
||||
}
|
||||
|
||||
return encodeBase64(toIntegerBytes(bigInt), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte-array representation of a <code>BigInteger</code>
|
||||
* without sign bit.
|
||||
*
|
||||
* @param bigInt <code>BigInteger</code> to be converted
|
||||
* @return a byte array representation of the BigInteger parameter
|
||||
*/
|
||||
static byte[] toIntegerBytes(BigInteger bigInt) {
|
||||
int bitlen = bigInt.bitLength();
|
||||
// round bitlen
|
||||
bitlen = ((bitlen + 7) >> 3) << 3;
|
||||
byte[] bigBytes = bigInt.toByteArray();
|
||||
|
||||
if(((bigInt.bitLength() % 8) != 0) &&
|
||||
(((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
|
||||
return bigBytes;
|
||||
}
|
||||
|
||||
// set up params for copying everything but sign bit
|
||||
int startSrc = 0;
|
||||
int len = bigBytes.length;
|
||||
|
||||
// if bigInt is exactly byte-aligned, just skip signbit in copy
|
||||
if((bigInt.bitLength() % 8) == 0) {
|
||||
startSrc = 1;
|
||||
len--;
|
||||
}
|
||||
|
||||
int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
|
||||
byte[] resizedBytes = new byte[bitlen / 8];
|
||||
|
||||
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
|
||||
|
||||
return resizedBytes;
|
||||
}
|
||||
}
|
102
bbb-lti/src/java/net/oauth/signature/HMAC_SHA1.java
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2007 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.signature;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import net.oauth.OAuth;
|
||||
import net.oauth.OAuthException;
|
||||
|
||||
/**
|
||||
* @author John Kristian
|
||||
*/
|
||||
class HMAC_SHA1 extends OAuthSignatureMethod {
|
||||
|
||||
@Override
|
||||
protected String getSignature(String baseString) throws OAuthException {
|
||||
try {
|
||||
String signature = base64Encode(computeSignature(baseString));
|
||||
return signature;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new OAuthException(e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new OAuthException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValid(String signature, String baseString)
|
||||
throws OAuthException {
|
||||
try {
|
||||
byte[] expected = computeSignature(baseString);
|
||||
byte[] actual = decodeBase64(signature);
|
||||
return Arrays.equals(expected, actual);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new OAuthException(e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new OAuthException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] computeSignature(String baseString)
|
||||
throws GeneralSecurityException, UnsupportedEncodingException {
|
||||
SecretKey key = null;
|
||||
synchronized (this) {
|
||||
if (this.key == null) {
|
||||
String keyString = OAuth.percentEncode(getConsumerSecret())
|
||||
+ '&' + OAuth.percentEncode(getTokenSecret());
|
||||
byte[] keyBytes = keyString.getBytes(ENCODING);
|
||||
this.key = new SecretKeySpec(keyBytes, MAC_NAME);
|
||||
}
|
||||
key = this.key;
|
||||
}
|
||||
Mac mac = Mac.getInstance(MAC_NAME);
|
||||
mac.init(key);
|
||||
byte[] text = baseString.getBytes(ENCODING);
|
||||
return mac.doFinal(text);
|
||||
}
|
||||
|
||||
/** ISO-8859-1 or US-ASCII would work, too. */
|
||||
private static final String ENCODING = OAuth.ENCODING;
|
||||
|
||||
private static final String MAC_NAME = "HmacSHA1";
|
||||
|
||||
private SecretKey key = null;
|
||||
|
||||
@Override
|
||||
public void setConsumerSecret(String consumerSecret) {
|
||||
synchronized (this) {
|
||||
key = null;
|
||||
}
|
||||
super.setConsumerSecret(consumerSecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTokenSecret(String tokenSecret) {
|
||||
synchronized (this) {
|
||||
key = null;
|
||||
}
|
||||
super.setTokenSecret(tokenSecret);
|
||||
}
|
||||
|
||||
}
|
298
bbb-lti/src/java/net/oauth/signature/OAuthSignatureMethod.java
Normal file
@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright 2007 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.signature;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.oauth.OAuth;
|
||||
import net.oauth.OAuthAccessor;
|
||||
import net.oauth.OAuthConsumer;
|
||||
import net.oauth.OAuthException;
|
||||
import net.oauth.OAuthMessage;
|
||||
import net.oauth.OAuthProblemException;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
/**
|
||||
* A pair of algorithms for computing and verifying an OAuth digital signature.
|
||||
*
|
||||
* @author John Kristian
|
||||
*/
|
||||
public abstract class OAuthSignatureMethod {
|
||||
|
||||
/** Add a signature to the message.
|
||||
* @throws URISyntaxException
|
||||
* @throws IOException */
|
||||
public void sign(OAuthMessage message)
|
||||
throws OAuthException, IOException, URISyntaxException {
|
||||
message.addParameter(new OAuth.Parameter("oauth_signature",
|
||||
getSignature(message)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the message has a valid signature.
|
||||
* @throws URISyntaxException
|
||||
*
|
||||
* @throws OAuthProblemException
|
||||
* the signature is invalid
|
||||
*/
|
||||
public void validate(OAuthMessage message)
|
||||
throws IOException, OAuthException, URISyntaxException {
|
||||
message.requireParameters("oauth_signature");
|
||||
String signature = message.getSignature();
|
||||
String baseString = getBaseString(message);
|
||||
if (!isValid(signature, baseString)) {
|
||||
OAuthProblemException problem = new OAuthProblemException(
|
||||
"signature_invalid");
|
||||
problem.setParameter("oauth_signature", signature);
|
||||
problem.setParameter("oauth_signature_base_string", baseString);
|
||||
problem.setParameter("oauth_signature_method", message
|
||||
.getSignatureMethod());
|
||||
throw problem;
|
||||
}
|
||||
}
|
||||
|
||||
protected String getSignature(OAuthMessage message)
|
||||
throws OAuthException, IOException, URISyntaxException {
|
||||
String baseString = getBaseString(message);
|
||||
String signature = getSignature(baseString);
|
||||
// Logger log = Logger.getLogger(getClass().getName());
|
||||
// if (log.isLoggable(Level.FINE)) {
|
||||
// log.fine(signature + "=getSignature(" + baseString + ")");
|
||||
// }
|
||||
return signature;
|
||||
}
|
||||
|
||||
protected void initialize(String name, OAuthAccessor accessor)
|
||||
throws OAuthException {
|
||||
String secret = accessor.consumer.consumerSecret;
|
||||
if (name.endsWith(_ACCESSOR)) {
|
||||
// This code supports the 'Accessor Secret' extensions
|
||||
// described in http://oauth.pbwiki.com/AccessorSecret
|
||||
final String key = OAuthConsumer.ACCESSOR_SECRET;
|
||||
Object accessorSecret = accessor.getProperty(key);
|
||||
if (accessorSecret == null) {
|
||||
accessorSecret = accessor.consumer.getProperty(key);
|
||||
}
|
||||
if (accessorSecret != null) {
|
||||
secret = accessorSecret.toString();
|
||||
}
|
||||
}
|
||||
if (secret == null) {
|
||||
secret = "";
|
||||
}
|
||||
setConsumerSecret(secret);
|
||||
}
|
||||
|
||||
public static final String _ACCESSOR = "-Accessor";
|
||||
|
||||
/** Compute the signature for the given base string. */
|
||||
protected abstract String getSignature(String baseString) throws OAuthException;
|
||||
|
||||
/** Decide whether the signature is valid. */
|
||||
protected abstract boolean isValid(String signature, String baseString)
|
||||
throws OAuthException;
|
||||
|
||||
private String consumerSecret;
|
||||
|
||||
private String tokenSecret;
|
||||
|
||||
protected String getConsumerSecret() {
|
||||
return consumerSecret;
|
||||
}
|
||||
|
||||
protected void setConsumerSecret(String consumerSecret) {
|
||||
this.consumerSecret = consumerSecret;
|
||||
}
|
||||
|
||||
public String getTokenSecret() {
|
||||
return tokenSecret;
|
||||
}
|
||||
|
||||
public void setTokenSecret(String tokenSecret) {
|
||||
this.tokenSecret = tokenSecret;
|
||||
}
|
||||
|
||||
public static String getBaseString(OAuthMessage message)
|
||||
throws IOException, URISyntaxException {
|
||||
List<Map.Entry<String, String>> parameters;
|
||||
String url = message.URL;
|
||||
int q = url.indexOf('?');
|
||||
if (q < 0) {
|
||||
parameters = message.getParameters();
|
||||
} else {
|
||||
// Combine the URL query string with the other parameters:
|
||||
parameters = new ArrayList<Map.Entry<String, String>>();
|
||||
parameters.addAll(OAuth.decodeForm(message.URL.substring(q + 1)));
|
||||
parameters.addAll(message.getParameters());
|
||||
url = url.substring(0, q);
|
||||
}
|
||||
return OAuth.percentEncode(message.method.toUpperCase()) + '&'
|
||||
+ OAuth.percentEncode(normalizeUrl(url)) + '&'
|
||||
+ OAuth.percentEncode(normalizeParameters(parameters));
|
||||
}
|
||||
|
||||
protected static String normalizeUrl(String url) throws URISyntaxException {
|
||||
URI uri = new URI(url);
|
||||
String scheme = uri.getScheme().toLowerCase();
|
||||
String authority = uri.getAuthority().toLowerCase();
|
||||
boolean dropPort = (scheme.equals("http") && uri.getPort() == 80)
|
||||
|| (scheme.equals("https") && uri.getPort() == 443);
|
||||
if (dropPort) {
|
||||
// find the last : in the authority
|
||||
int index = authority.lastIndexOf(":");
|
||||
if (index >= 0) {
|
||||
authority = authority.substring(0, index);
|
||||
}
|
||||
}
|
||||
String path = uri.getRawPath();
|
||||
if (path == null || path.length() <= 0) {
|
||||
path = "/"; // conforms to RFC 2616 section 3.2.2
|
||||
}
|
||||
// we know that there is no query and no fragment here.
|
||||
return scheme + "://" + authority + path;
|
||||
}
|
||||
|
||||
protected static String normalizeParameters(
|
||||
Collection<? extends Map.Entry> parameters) throws IOException {
|
||||
if (parameters == null) {
|
||||
return "";
|
||||
}
|
||||
List<ComparableParameter> p = new ArrayList<ComparableParameter>(
|
||||
parameters.size());
|
||||
for (Map.Entry parameter : parameters) {
|
||||
if (!"oauth_signature".equals(parameter.getKey())) {
|
||||
p.add(new ComparableParameter(parameter));
|
||||
}
|
||||
}
|
||||
Collections.sort(p);
|
||||
return OAuth.formEncode(getParameters(p));
|
||||
}
|
||||
|
||||
public static byte[] decodeBase64(String s) {
|
||||
return BASE64.decode(s.getBytes());
|
||||
}
|
||||
|
||||
public static String base64Encode(byte[] b) {
|
||||
return new String(BASE64.encode(b));
|
||||
}
|
||||
|
||||
private static final Base64 BASE64 = new Base64();
|
||||
|
||||
public static OAuthSignatureMethod newSigner(OAuthMessage message,
|
||||
OAuthAccessor accessor) throws IOException, OAuthException {
|
||||
message.requireParameters(OAuth.OAUTH_SIGNATURE_METHOD);
|
||||
OAuthSignatureMethod signer = newMethod(message.getSignatureMethod(),
|
||||
accessor);
|
||||
signer.setTokenSecret(accessor.tokenSecret);
|
||||
return signer;
|
||||
}
|
||||
|
||||
/** The factory for signature methods. */
|
||||
public static OAuthSignatureMethod newMethod(String name,
|
||||
OAuthAccessor accessor) throws OAuthException {
|
||||
try {
|
||||
Class methodClass = NAME_TO_CLASS.get(name);
|
||||
if (methodClass != null) {
|
||||
OAuthSignatureMethod method = (OAuthSignatureMethod) methodClass
|
||||
.newInstance();
|
||||
method.initialize(name, accessor);
|
||||
return method;
|
||||
}
|
||||
OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.SIGNATURE_METHOD_REJECTED);
|
||||
String acceptable = OAuth.percentEncode(NAME_TO_CLASS.keySet());
|
||||
if (acceptable.length() > 0) {
|
||||
problem.setParameter("oauth_acceptable_signature_methods",
|
||||
acceptable.toString());
|
||||
}
|
||||
throw problem;
|
||||
} catch (InstantiationException e) {
|
||||
throw new OAuthException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new OAuthException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subsequently, newMethod(name) will attempt to instantiate the given
|
||||
* class, with no constructor parameters.
|
||||
*/
|
||||
public static void registerMethodClass(String name, Class clazz) {
|
||||
NAME_TO_CLASS.put(name, clazz);
|
||||
}
|
||||
|
||||
private static final Map<String, Class> NAME_TO_CLASS = new ConcurrentHashMap<String, Class>();
|
||||
static {
|
||||
registerMethodClass("HMAC-SHA1", HMAC_SHA1.class);
|
||||
registerMethodClass("PLAINTEXT", PLAINTEXT.class);
|
||||
registerMethodClass("RSA-SHA1", RSA_SHA1.class);
|
||||
registerMethodClass("HMAC-SHA1" + _ACCESSOR, HMAC_SHA1.class);
|
||||
registerMethodClass("PLAINTEXT" + _ACCESSOR, PLAINTEXT.class);
|
||||
}
|
||||
|
||||
/** An efficiently sortable wrapper around a parameter. */
|
||||
private static class ComparableParameter implements
|
||||
Comparable<ComparableParameter> {
|
||||
|
||||
ComparableParameter(Map.Entry value) {
|
||||
this.value = value;
|
||||
String n = toString(value.getKey());
|
||||
String v = toString(value.getValue());
|
||||
this.key = OAuth.percentEncode(n) + ' ' + OAuth.percentEncode(v);
|
||||
// ' ' is used because it comes before any character
|
||||
// that can appear in a percentEncoded string.
|
||||
}
|
||||
|
||||
final Map.Entry value;
|
||||
|
||||
private final String key;
|
||||
|
||||
private static String toString(Object from) {
|
||||
return (from == null) ? null : from.toString();
|
||||
}
|
||||
|
||||
public int compareTo(ComparableParameter that) {
|
||||
return this.key.compareTo(that.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return key;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Retrieve the original parameters from a sorted collection. */
|
||||
private static List<Map.Entry> getParameters(
|
||||
Collection<ComparableParameter> parameters) {
|
||||
if (parameters == null) {
|
||||
return null;
|
||||
}
|
||||
List<Map.Entry> list = new ArrayList<Map.Entry>(parameters.size());
|
||||
for (ComparableParameter parameter : parameters) {
|
||||
list.add(parameter.value);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
64
bbb-lti/src/java/net/oauth/signature/PLAINTEXT.java
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2007 Netflix, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.signature;
|
||||
|
||||
import net.oauth.OAuth;
|
||||
import net.oauth.OAuthException;
|
||||
|
||||
/**
|
||||
* @author John Kristian
|
||||
*/
|
||||
class PLAINTEXT extends OAuthSignatureMethod {
|
||||
|
||||
@Override
|
||||
public String getSignature(String baseString) {
|
||||
return getSignature();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValid(String signature, String baseString)
|
||||
throws OAuthException {
|
||||
return signature.equals(getSignature());
|
||||
}
|
||||
|
||||
private synchronized String getSignature() {
|
||||
if (signature == null) {
|
||||
signature = OAuth.percentEncode(getConsumerSecret()) + '&'
|
||||
+ OAuth.percentEncode(getTokenSecret());
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
private String signature = null;
|
||||
|
||||
@Override
|
||||
public void setConsumerSecret(String consumerSecret) {
|
||||
synchronized (this) {
|
||||
signature = null;
|
||||
}
|
||||
super.setConsumerSecret(consumerSecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTokenSecret(String tokenSecret) {
|
||||
synchronized (this) {
|
||||
signature = null;
|
||||
}
|
||||
super.setTokenSecret(tokenSecret);
|
||||
}
|
||||
|
||||
}
|
238
bbb-lti/src/java/net/oauth/signature/RSA_SHA1.java
Normal file
@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2007 Google, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.oauth.signature;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.EncodedKeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import net.oauth.OAuth;
|
||||
import net.oauth.OAuthAccessor;
|
||||
import net.oauth.OAuthException;
|
||||
|
||||
/**
|
||||
* Class to handle RSA-SHA1 signatures on OAuth requests. A consumer
|
||||
* that wishes to use public-key signatures on messages does not need
|
||||
* a shared secret with the service provider, but it needs a private
|
||||
* RSA signing key. You create it like this:
|
||||
*
|
||||
* OAuthConsumer c = new OAuthConsumer(callback_url, consumer_key,
|
||||
* null, provider);
|
||||
* c.setProperty(RSA_SHA1.PRIVATE_KEY, consumer_privateRSAKey);
|
||||
*
|
||||
* consumer_privateRSAKey must be an RSA signing key and
|
||||
* of type java.security.PrivateKey, String, or byte[]. In the latter two
|
||||
* cases, the key must be PKCS#8-encoded (byte[]) or PKCS#8-encoded and
|
||||
* then Base64-encoded (String).
|
||||
*
|
||||
* A service provider that wishes to verify signatures made by such a
|
||||
* consumer does not need a shared secret with the consumer, but it needs
|
||||
* to know the consumer's public key. You create the necessary
|
||||
* OAuthConsumer object (on the service provider's side) like this:
|
||||
*
|
||||
* OAuthConsumer c = new OAuthConsumer(callback_url, consumer_key,
|
||||
* null, provider);
|
||||
* c.setProperty(RSA_SHA1.PUBLIC_KEY, consumer_publicRSAKey);
|
||||
*
|
||||
* consumer_publicRSAKey must be the consumer's public RSAkey and
|
||||
* of type java.security.PublicKey, String, or byte[]. In the latter two
|
||||
* cases, the key must be X509-encoded (byte[]) or X509-encoded and
|
||||
* then Base64-encoded (String).
|
||||
*
|
||||
* Alternatively, a service provider that wishes to verify signatures made
|
||||
* by such a consumer can use a X509 certificate containing the consumer's
|
||||
* public key. You create the necessary OAuthConsumer object (on the service
|
||||
* provider's side) like this:
|
||||
*
|
||||
* OAuthConsumer c = new OAuthConsumer(callback_url, consumer_key,
|
||||
* null, provider);
|
||||
* c.setProperty(RSA_SHA1.X509_CERTIFICATE, consumer_cert);
|
||||
*
|
||||
* consumer_cert must be a X509 Certificate containing the consumer's public
|
||||
* key and be of type java.security.cert.X509Certificate, String,
|
||||
* or byte[]. In the latter two cases, the certificate must be DER-encoded
|
||||
* (byte[]) or PEM-encoded (String).
|
||||
*
|
||||
* @author Dirk Balfanz
|
||||
*
|
||||
*/
|
||||
public class RSA_SHA1 extends OAuthSignatureMethod {
|
||||
|
||||
final static public String PRIVATE_KEY = "RSA-SHA1.PrivateKey";
|
||||
final static public String PUBLIC_KEY = "RSA-SHA1.PublicKey";
|
||||
final static public String X509_CERTIFICATE = "RSA-SHA1.X509Certificate";
|
||||
|
||||
private PrivateKey privateKey = null;
|
||||
private PublicKey publicKey = null;
|
||||
|
||||
@Override
|
||||
protected void initialize(String name, OAuthAccessor accessor)
|
||||
throws OAuthException {
|
||||
super.initialize(name, accessor);
|
||||
|
||||
Object privateKeyObject = accessor.consumer.getProperty(PRIVATE_KEY);
|
||||
try {
|
||||
if (privateKeyObject != null) {
|
||||
if (privateKeyObject instanceof PrivateKey) {
|
||||
privateKey = (PrivateKey)privateKeyObject;
|
||||
} else if (privateKeyObject instanceof String) {
|
||||
privateKey = getPrivateKeyFromPem((String)privateKeyObject);
|
||||
} else if (privateKeyObject instanceof byte[]) {
|
||||
privateKey = getPrivateKeyFromDer((byte[])privateKeyObject);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Private key set through RSA_SHA1.PRIVATE_KEY must be of " +
|
||||
"type PrivateKey, String, or byte[], and not " +
|
||||
privateKeyObject.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
Object publicKeyObject = accessor.consumer.getProperty(PUBLIC_KEY);
|
||||
if (publicKeyObject != null) {
|
||||
if (publicKeyObject instanceof PublicKey) {
|
||||
publicKey = (PublicKey)publicKeyObject;
|
||||
} else if (publicKeyObject instanceof String) {
|
||||
publicKey = getPublicKeyFromPem((String)publicKeyObject);
|
||||
} else if (publicKeyObject instanceof byte[]) {
|
||||
publicKey = getPublicKeyFromDer((byte[])publicKeyObject);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Public key set through RSA_SHA1.PRIVATE_KEY must be of " +
|
||||
"type PublicKey, String, or byte[], and not " +
|
||||
publicKeyObject.getClass().getName());
|
||||
}
|
||||
} else { // public key was null. perhaps they gave us a X509 cert.
|
||||
Object certObject = accessor.consumer.getProperty(X509_CERTIFICATE);
|
||||
if (certObject != null) {
|
||||
if (certObject instanceof X509Certificate) {
|
||||
publicKey = ((X509Certificate) certObject).getPublicKey();
|
||||
} else if (certObject instanceof String) {
|
||||
publicKey = getPublicKeyFromPemCert((String)certObject);
|
||||
} else if (certObject instanceof byte[]) {
|
||||
publicKey = getPublicKeyFromDerCert((byte[])certObject);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"X509Certificate set through RSA_SHA1.X509_CERTIFICATE" +
|
||||
" must be of type X509Certificate, String, or byte[]," +
|
||||
" and not " + certObject.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new OAuthException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PublicKey getPublicKeyFromPemCert(String certObject)
|
||||
throws GeneralSecurityException {
|
||||
CertificateFactory fac = CertificateFactory.getInstance("X509");
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(certObject.getBytes());
|
||||
X509Certificate cert = (X509Certificate)fac.generateCertificate(in);
|
||||
return cert.getPublicKey();
|
||||
}
|
||||
|
||||
private PublicKey getPublicKeyFromDerCert(byte[] certObject)
|
||||
throws GeneralSecurityException {
|
||||
CertificateFactory fac = CertificateFactory.getInstance("X509");
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(certObject);
|
||||
X509Certificate cert = (X509Certificate)fac.generateCertificate(in);
|
||||
return cert.getPublicKey();
|
||||
}
|
||||
|
||||
private PublicKey getPublicKeyFromDer(byte[] publicKeyObject)
|
||||
throws GeneralSecurityException {
|
||||
KeyFactory fac = KeyFactory.getInstance("RSA");
|
||||
EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicKeyObject);
|
||||
return fac.generatePublic(pubKeySpec);
|
||||
}
|
||||
|
||||
private PublicKey getPublicKeyFromPem(String publicKeyObject)
|
||||
throws GeneralSecurityException {
|
||||
return getPublicKeyFromDer(decodeBase64(publicKeyObject));
|
||||
}
|
||||
|
||||
private PrivateKey getPrivateKeyFromDer(byte[] privateKeyObject)
|
||||
throws GeneralSecurityException {
|
||||
KeyFactory fac = KeyFactory.getInstance("RSA");
|
||||
EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privateKeyObject);
|
||||
return fac.generatePrivate(privKeySpec);
|
||||
}
|
||||
|
||||
private PrivateKey getPrivateKeyFromPem(String privateKeyObject)
|
||||
throws GeneralSecurityException {
|
||||
return getPrivateKeyFromDer(decodeBase64(privateKeyObject));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSignature(String baseString) throws OAuthException {
|
||||
try {
|
||||
byte[] signature = sign(baseString.getBytes(OAuth.ENCODING));
|
||||
return base64Encode(signature);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new OAuthException(e);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new OAuthException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValid(String signature, String baseString)
|
||||
throws OAuthException {
|
||||
try {
|
||||
return verify(decodeBase64(signature),
|
||||
baseString.getBytes(OAuth.ENCODING));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new OAuthException(e);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new OAuthException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] sign(byte[] message) throws GeneralSecurityException {
|
||||
if (privateKey == null) {
|
||||
throw new IllegalStateException("need to set private key with " +
|
||||
"OAuthConsumer.setProperty when " +
|
||||
"generating RSA-SHA1 signatures.");
|
||||
}
|
||||
Signature signer = Signature.getInstance("SHA1withRSA");
|
||||
signer.initSign(privateKey);
|
||||
signer.update(message);
|
||||
return signer.sign();
|
||||
}
|
||||
|
||||
private boolean verify(byte[] signature, byte[] message)
|
||||
throws GeneralSecurityException {
|
||||
if (publicKey == null) {
|
||||
throw new IllegalStateException("need to set public key with " +
|
||||
" OAuthConsumer.setProperty when " +
|
||||
"verifying RSA-SHA1 signatures.");
|
||||
}
|
||||
Signature verifier = Signature.getInstance("SHA1withRSA");
|
||||
verifier.initVerify(publicKey);
|
||||
verifier.update(message);
|
||||
return verifier.verify(signature);
|
||||
}
|
||||
}
|
150
bbb-lti/src/java/net/oauth/signature/pem/Asn1Object.java
Normal file
@ -0,0 +1,150 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 1998-2009 AOL LLC.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
package net.oauth.signature.pem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* An ASN.1 TLV. The object is not parsed. It can
|
||||
* only handle integers and strings.
|
||||
*
|
||||
* @author zhang
|
||||
*
|
||||
*/
|
||||
class Asn1Object {
|
||||
|
||||
protected final int type;
|
||||
protected final int length;
|
||||
protected final byte[] value;
|
||||
protected final int tag;
|
||||
|
||||
/**
|
||||
* Construct a ASN.1 TLV. The TLV could be either a
|
||||
* constructed or primitive entity.
|
||||
*
|
||||
* <p/>The first byte in DER encoding is made of following fields,
|
||||
* <pre>
|
||||
*-------------------------------------------------
|
||||
*|Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1|
|
||||
*-------------------------------------------------
|
||||
*| Class | CF | + Type |
|
||||
*-------------------------------------------------
|
||||
* </pre>
|
||||
* <ul>
|
||||
* <li>Class: Universal, Application, Context or Private
|
||||
* <li>CF: Constructed flag. If 1, the field is constructed.
|
||||
* <li>Type: This is actually called tag in ASN.1. It
|
||||
* indicates data type (Integer, String) or a construct
|
||||
* (sequence, choice, set).
|
||||
* </ul>
|
||||
*
|
||||
* @param tag Tag or Identifier
|
||||
* @param length Length of the field
|
||||
* @param value Encoded octet string for the field.
|
||||
*/
|
||||
public Asn1Object(int tag, int length, byte[] value) {
|
||||
this.tag = tag;
|
||||
this.type = tag & 0x1F;
|
||||
this.length = length;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public byte[] getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean isConstructed() {
|
||||
return (tag & DerParser.CONSTRUCTED) == DerParser.CONSTRUCTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* For constructed field, return a parser for its content.
|
||||
*
|
||||
* @return A parser for the construct.
|
||||
* @throws IOException
|
||||
*/
|
||||
public DerParser getParser() throws IOException {
|
||||
if (!isConstructed())
|
||||
throw new IOException("Invalid DER: can't parse primitive entity"); //$NON-NLS-1$
|
||||
|
||||
return new DerParser(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value as integer
|
||||
*
|
||||
* @return BigInteger
|
||||
* @throws IOException
|
||||
*/
|
||||
public BigInteger getInteger() throws IOException {
|
||||
if (type != DerParser.INTEGER)
|
||||
throw new IOException("Invalid DER: object is not integer"); //$NON-NLS-1$
|
||||
|
||||
return new BigInteger(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value as string. Most strings are treated
|
||||
* as Latin-1.
|
||||
*
|
||||
* @return Java string
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getString() throws IOException {
|
||||
|
||||
String encoding;
|
||||
|
||||
switch (type) {
|
||||
|
||||
// Not all are Latin-1 but it's the closest thing
|
||||
case DerParser.NUMERIC_STRING:
|
||||
case DerParser.PRINTABLE_STRING:
|
||||
case DerParser.VIDEOTEX_STRING:
|
||||
case DerParser.IA5_STRING:
|
||||
case DerParser.GRAPHIC_STRING:
|
||||
case DerParser.ISO646_STRING:
|
||||
case DerParser.GENERAL_STRING:
|
||||
encoding = "ISO-8859-1"; //$NON-NLS-1$
|
||||
break;
|
||||
|
||||
case DerParser.BMP_STRING:
|
||||
encoding = "UTF-16BE"; //$NON-NLS-1$
|
||||
break;
|
||||
|
||||
case DerParser.UTF8_STRING:
|
||||
encoding = "UTF-8"; //$NON-NLS-1$
|
||||
break;
|
||||
|
||||
case DerParser.UNIVERSAL_STRING:
|
||||
throw new IOException("Invalid DER: can't handle UCS-4 string"); //$NON-NLS-1$
|
||||
|
||||
default:
|
||||
throw new IOException("Invalid DER: object is not a string"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
return new String(value, encoding);
|
||||
}
|
||||
}
|
170
bbb-lti/src/java/net/oauth/signature/pem/DerParser.java
Normal file
@ -0,0 +1,170 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 1998-2009 AOL LLC.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
package net.oauth.signature.pem;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* A bare-minimum ASN.1 DER decoder, just having enough functions to
|
||||
* decode PKCS#1 private keys. Especially, it doesn't handle explicitly
|
||||
* tagged types with an outer tag.
|
||||
*
|
||||
* <p/>This parser can only handle one layer. To parse nested constructs,
|
||||
* get a new parser for each layer using <code>Asn1Object.getParser()</code>.
|
||||
*
|
||||
* <p/>There are many DER decoders in JRE but using them will tie this
|
||||
* program to a specific JCE/JVM.
|
||||
*
|
||||
* @author zhang
|
||||
*
|
||||
*/
|
||||
class DerParser {
|
||||
|
||||
// Classes
|
||||
public final static int UNIVERSAL = 0x00;
|
||||
public final static int APPLICATION = 0x40;
|
||||
public final static int CONTEXT = 0x80;
|
||||
public final static int PRIVATE = 0xC0;
|
||||
|
||||
// Constructed Flag
|
||||
public final static int CONSTRUCTED = 0x20;
|
||||
|
||||
// Tag and data types
|
||||
public final static int ANY = 0x00;
|
||||
public final static int BOOLEAN = 0x01;
|
||||
public final static int INTEGER = 0x02;
|
||||
public final static int BIT_STRING = 0x03;
|
||||
public final static int OCTET_STRING = 0x04;
|
||||
public final static int NULL = 0x05;
|
||||
public final static int OBJECT_IDENTIFIER = 0x06;
|
||||
public final static int REAL = 0x09;
|
||||
public final static int ENUMERATED = 0x0a;
|
||||
public final static int RELATIVE_OID = 0x0d;
|
||||
|
||||
public final static int SEQUENCE = 0x10;
|
||||
public final static int SET = 0x11;
|
||||
|
||||
public final static int NUMERIC_STRING = 0x12;
|
||||
public final static int PRINTABLE_STRING = 0x13;
|
||||
public final static int T61_STRING = 0x14;
|
||||
public final static int VIDEOTEX_STRING = 0x15;
|
||||
public final static int IA5_STRING = 0x16;
|
||||
public final static int GRAPHIC_STRING = 0x19;
|
||||
public final static int ISO646_STRING = 0x1A;
|
||||
public final static int GENERAL_STRING = 0x1B;
|
||||
|
||||
public final static int UTF8_STRING = 0x0C;
|
||||
public final static int UNIVERSAL_STRING = 0x1C;
|
||||
public final static int BMP_STRING = 0x1E;
|
||||
|
||||
public final static int UTC_TIME = 0x17;
|
||||
public final static int GENERALIZED_TIME = 0x18;
|
||||
|
||||
protected InputStream in;
|
||||
|
||||
/**
|
||||
* Create a new DER decoder from an input stream.
|
||||
*
|
||||
* @param in
|
||||
* The DER encoded stream
|
||||
*/
|
||||
public DerParser(InputStream in) throws IOException {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DER decoder from a byte array.
|
||||
*
|
||||
* @param The
|
||||
* encoded bytes
|
||||
* @throws IOException
|
||||
*/
|
||||
public DerParser(byte[] bytes) throws IOException {
|
||||
this(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read next object. If it's constructed, the value holds
|
||||
* encoded content and it should be parsed by a new
|
||||
* parser from <code>Asn1Object.getParser</code>.
|
||||
*
|
||||
* @return A object
|
||||
* @throws IOException
|
||||
*/
|
||||
public Asn1Object read() throws IOException {
|
||||
int tag = in.read();
|
||||
|
||||
if (tag == -1)
|
||||
throw new IOException("Invalid DER: stream too short, missing tag"); //$NON-NLS-1$
|
||||
|
||||
int length = getLength();
|
||||
|
||||
byte[] value = new byte[length];
|
||||
int n = in.read(value);
|
||||
if (n < length)
|
||||
throw new IOException("Invalid DER: stream too short, missing value"); //$NON-NLS-1$
|
||||
|
||||
Asn1Object o = new Asn1Object(tag, length, value);
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the length of the field. Can only support length
|
||||
* encoding up to 4 octets.
|
||||
*
|
||||
* <p/>In BER/DER encoding, length can be encoded in 2 forms,
|
||||
* <ul>
|
||||
* <li>Short form. One octet. Bit 8 has value "0" and bits 7-1
|
||||
* give the length.
|
||||
* <li>Long form. Two to 127 octets (only 4 is supported here).
|
||||
* Bit 8 of first octet has value "1" and bits 7-1 give the
|
||||
* number of additional length octets. Second and following
|
||||
* octets give the length, base 256, most significant digit first.
|
||||
* </ul>
|
||||
* @return The length as integer
|
||||
* @throws IOException
|
||||
*/
|
||||
private int getLength() throws IOException {
|
||||
|
||||
int i = in.read();
|
||||
if (i == -1)
|
||||
throw new IOException("Invalid DER: length missing"); //$NON-NLS-1$
|
||||
|
||||
// A single byte short length
|
||||
if ((i & ~0x7F) == 0)
|
||||
return i;
|
||||
|
||||
int num = i & 0x7F;
|
||||
|
||||
// We can't handle length longer than 4 bytes
|
||||
if ( i >= 0xFF || num > 4)
|
||||
throw new IOException("Invalid DER: length field too big (" //$NON-NLS-1$
|
||||
+ i + ")"); //$NON-NLS-1$
|
||||
|
||||
byte[] bytes = new byte[num];
|
||||
int n = in.read(bytes);
|
||||
if (n < num)
|
||||
throw new IOException("Invalid DER: length too short"); //$NON-NLS-1$
|
||||
|
||||
return new BigInteger(1, bytes).intValue();
|
||||
}
|
||||
|
||||
}
|
134
bbb-lti/src/java/net/oauth/signature/pem/PEMReader.java
Normal file
@ -0,0 +1,134 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 1998-2009 AOL LLC.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
****************************************************************************
|
||||
*
|
||||
* @author: zhang
|
||||
* @version: $Revision: 2 $
|
||||
* @created: Apr 24, 2009
|
||||
*
|
||||
* Description: A class to decode PEM files
|
||||
*
|
||||
****************************************************************************/
|
||||
package net.oauth.signature.pem;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import net.oauth.signature.OAuthSignatureMethod;
|
||||
|
||||
/**
|
||||
* This class convert PEM into byte array. The begin marker
|
||||
* is saved and it can be used to determine the type of the
|
||||
* PEM file.
|
||||
*
|
||||
* @author zhang
|
||||
*/
|
||||
public class PEMReader {
|
||||
|
||||
// Begin markers for all supported PEM files
|
||||
public static final String PRIVATE_PKCS1_MARKER =
|
||||
"-----BEGIN RSA PRIVATE KEY-----";
|
||||
public static final String PRIVATE_PKCS8_MARKER =
|
||||
"-----BEGIN PRIVATE KEY-----";
|
||||
public static final String CERTIFICATE_X509_MARKER =
|
||||
"-----BEGIN CERTIFICATE-----";
|
||||
public static final String PUBLIC_X509_MARKER =
|
||||
"-----BEGIN PUBLIC KEY-----";
|
||||
|
||||
private static final String BEGIN_MARKER = "-----BEGIN ";
|
||||
|
||||
private InputStream stream;
|
||||
private byte[] derBytes;
|
||||
private String beginMarker;
|
||||
|
||||
public PEMReader(InputStream inStream) throws IOException {
|
||||
stream = inStream;
|
||||
readFile();
|
||||
}
|
||||
|
||||
public PEMReader(byte[] buffer) throws IOException {
|
||||
this(new ByteArrayInputStream(buffer));
|
||||
}
|
||||
|
||||
public PEMReader(String fileName) throws IOException {
|
||||
this(new FileInputStream(fileName));
|
||||
}
|
||||
|
||||
public byte[] getDerBytes() {
|
||||
return derBytes;
|
||||
}
|
||||
|
||||
public String getBeginMarker() {
|
||||
return beginMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the PEM file and save the DER encoded octet
|
||||
* stream and begin marker.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void readFile() throws IOException {
|
||||
|
||||
String line;
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(stream));
|
||||
try {
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
if (line.indexOf(BEGIN_MARKER) != -1)
|
||||
{
|
||||
beginMarker = line.trim();
|
||||
String endMarker = beginMarker.replace("BEGIN", "END");
|
||||
derBytes = readBytes(reader, endMarker);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IOException("Invalid PEM file: no begin marker");
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read the lines between BEGIN and END marker and convert
|
||||
* the Base64 encoded content into binary byte array.
|
||||
*
|
||||
* @return DER encoded octet stream
|
||||
* @throws IOException
|
||||
*/
|
||||
private byte[] readBytes(BufferedReader reader, String endMarker) throws IOException
|
||||
{
|
||||
String line = null;
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
if (line.indexOf(endMarker) != -1) {
|
||||
|
||||
return OAuthSignatureMethod.decodeBase64(buf.toString());
|
||||
}
|
||||
|
||||
buf.append(line.trim());
|
||||
}
|
||||
|
||||
throw new IOException("Invalid PEM file: No end marker");
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 1998-2009 AOL LLC.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
****************************************************************************
|
||||
*
|
||||
* @author: zhang
|
||||
* @version: $Revision: 2 $
|
||||
* @created: Apr 24, 2009
|
||||
*
|
||||
* Description: A KeySpec for PKCS#1 encoded RSA private key
|
||||
*
|
||||
****************************************************************************/
|
||||
package net.oauth.signature.pem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.RSAPrivateCrtKeySpec;
|
||||
|
||||
/**
|
||||
* PKCS#1 encoded private key is commonly used with OpenSSL. It provides CRT parameters
|
||||
* so the private key operation can be much faster than using exponent/modulus alone,
|
||||
* which is the case for PKCS#8 encoded key.
|
||||
*
|
||||
* <p/>Unfortunately, JCE doesn't have an API to decode the DER. This class takes DER
|
||||
* buffer and decoded into CRT key.
|
||||
*
|
||||
* @author zhang
|
||||
*/
|
||||
public class PKCS1EncodedKeySpec {
|
||||
|
||||
private RSAPrivateCrtKeySpec keySpec;
|
||||
|
||||
/**
|
||||
* Create a PKCS#1 keyspec from DER encoded buffer
|
||||
*
|
||||
* @param keyBytes DER encoded octet stream
|
||||
* @throws IOException
|
||||
*/
|
||||
public PKCS1EncodedKeySpec(byte[] keyBytes) throws IOException {
|
||||
decode(keyBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key spec that JCE understands.
|
||||
*
|
||||
* @return CRT keyspec defined by JCE
|
||||
*/
|
||||
public RSAPrivateCrtKeySpec getKeySpec() {
|
||||
return keySpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode PKCS#1 encoded private key into RSAPrivateCrtKeySpec.
|
||||
*
|
||||
* <p/>The ASN.1 syntax for the private key with CRT is
|
||||
*
|
||||
* <pre>
|
||||
* --
|
||||
* -- Representation of RSA private key with information for the CRT algorithm.
|
||||
* --
|
||||
* RSAPrivateKey ::= SEQUENCE {
|
||||
* version Version,
|
||||
* modulus INTEGER, -- n
|
||||
* publicExponent INTEGER, -- e
|
||||
* privateExponent INTEGER, -- d
|
||||
* prime1 INTEGER, -- p
|
||||
* prime2 INTEGER, -- q
|
||||
* exponent1 INTEGER, -- d mod (p-1)
|
||||
* exponent2 INTEGER, -- d mod (q-1)
|
||||
* coefficient INTEGER, -- (inverse of q) mod p
|
||||
* otherPrimeInfos OtherPrimeInfos OPTIONAL
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param keyBytes PKCS#1 encoded key
|
||||
* @throws IOException
|
||||
*/
|
||||
|
||||
private void decode(byte[] keyBytes) throws IOException {
|
||||
|
||||
DerParser parser = new DerParser(keyBytes);
|
||||
|
||||
Asn1Object sequence = parser.read();
|
||||
if (sequence.getType() != DerParser.SEQUENCE)
|
||||
throw new IOException("Invalid DER: not a sequence"); //$NON-NLS-1$
|
||||
|
||||
// Parse inside the sequence
|
||||
parser = sequence.getParser();
|
||||
|
||||
parser.read(); // Skip version
|
||||
BigInteger modulus = parser.read().getInteger();
|
||||
BigInteger publicExp = parser.read().getInteger();
|
||||
BigInteger privateExp = parser.read().getInteger();
|
||||
BigInteger prime1 = parser.read().getInteger();
|
||||
BigInteger prime2 = parser.read().getInteger();
|
||||
BigInteger exp1 = parser.read().getInteger();
|
||||
BigInteger exp2 = parser.read().getInteger();
|
||||
BigInteger crtCoef = parser.read().getInteger();
|
||||
|
||||
keySpec = new RSAPrivateCrtKeySpec(
|
||||
modulus, publicExp, privateExp, prime1, prime2,
|
||||
exp1, exp2, crtCoef);
|
||||
}
|
||||
}
|
15
bbb-lti/test/unit/BigbluebuttonServiceTests.groovy
Normal file
@ -0,0 +1,15 @@
|
||||
import grails.test.*
|
||||
|
||||
class BigbluebuttonServiceTests extends GrailsUnitTestCase {
|
||||
protected void setUp() {
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
protected void tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
void testSomething() {
|
||||
|
||||
}
|
||||
}
|
15
bbb-lti/test/unit/LtiServiceTests.groovy
Normal file
@ -0,0 +1,15 @@
|
||||
import grails.test.*
|
||||
|
||||
class LtiServiceTests extends GrailsUnitTestCase {
|
||||
protected void setUp() {
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
protected void tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
void testSomething() {
|
||||
|
||||
}
|
||||
}
|
15
bbb-lti/test/unit/ToolControllerTests.groovy
Normal file
@ -0,0 +1,15 @@
|
||||
import grails.test.*
|
||||
|
||||
class ToolControllerTests extends ControllerUnitTestCase {
|
||||
protected void setUp() {
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
protected void tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
void testSomething() {
|
||||
|
||||
}
|
||||
}
|
47
bbb-lti/web-app/WEB-INF/applicationContext.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?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="pluginMetaManager" class="org.codehaus.groovy.grails.plugins.DefaultPluginMetaManager">
|
||||
<property name="grailsApplication" ref="grailsApplication" />
|
||||
<property name="resourcePattern" value="/WEB-INF/plugins/*/plugin.xml" />
|
||||
</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>
|
14
bbb-lti/web-app/WEB-INF/sitemesh.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<sitemesh>
|
||||
<page-parsers>
|
||||
<parser content-type="text/html"
|
||||
class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" />
|
||||
<parser content-type="text/html;charset=ISO-8859-1"
|
||||
class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" />
|
||||
<parser content-type="text/html;charset=UTF-8"
|
||||
class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" />
|
||||
</page-parsers>
|
||||
|
||||
<decorator-mappers>
|
||||
<mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper" />
|
||||
</decorator-mappers>
|
||||
</sitemesh>
|
563
bbb-lti/web-app/WEB-INF/tld/c.tld
Normal file
@ -0,0 +1,563 @@
|
||||
<?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
|
||||
<when> and <otherwise>
|
||||
</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 <%= ... >, 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 <,>,&,'," 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 <choose> that follows <when> 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 <choose> 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>
|
671
bbb-lti/web-app/WEB-INF/tld/fmt.tld
Normal file
@ -0,0 +1,671 @@
|
||||
<?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 <fmt:message> 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
|
||||
<message> 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
|
||||
String.
|
||||
</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>
|
||||
Parses the string representation of a number, currency, or percentage
|
||||
</description>
|
||||
<name>parseNumber</name>
|
||||
<tag-class>org.apache.taglibs.standard.tag.rt.fmt.ParseNumberTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<description>
|
||||
String to be parsed.
|
||||
</description>
|
||||
<name>value</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Specifies whether the string in the value
|
||||
attribute should be parsed as a number,
|
||||
currency, or percentage.
|
||||
</description>
|
||||
<name>type</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Custom formatting pattern that determines
|
||||
how the string in the value attribute is to be
|
||||
parsed.
|
||||
</description>
|
||||
<name>pattern</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Locale whose default formatting pattern (for
|
||||
numbers, currencies, or percentages,
|
||||
respectively) is to be used during the parse
|
||||
operation, or to which the pattern specified
|
||||
via the pattern attribute (if present) is
|
||||
applied.
|
||||
</description>
|
||||
<name>parseLocale</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Specifies whether just the integer portion of
|
||||
the given value should be parsed.
|
||||
</description>
|
||||
<name>integerOnly</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Name of the exported scoped variable which
|
||||
stores the parsed result (of type
|
||||
java.lang.Number).
|
||||
</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>
|
||||
Formats a date and/or time using the supplied styles and pattern
|
||||
</description>
|
||||
<name>formatDate</name>
|
||||
<tag-class>org.apache.taglibs.standard.tag.rt.fmt.FormatDateTag</tag-class>
|
||||
<body-content>empty</body-content>
|
||||
<attribute>
|
||||
<description>
|
||||
Date and/or time to be formatted.
|
||||
</description>
|
||||
<name>value</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Specifies whether the time, the date, or both
|
||||
the time and date components of the given
|
||||
date are to be formatted.
|
||||
</description>
|
||||
<name>type</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Predefined formatting style for dates. Follows
|
||||
the semantics defined in class
|
||||
java.text.DateFormat. Applied only
|
||||
when formatting a date or both a date and
|
||||
time (i.e. if type is missing or is equal to
|
||||
"date" or "both"); ignored otherwise.
|
||||
</description>
|
||||
<name>dateStyle</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Predefined formatting style for times. Follows
|
||||
the semantics defined in class
|
||||
java.text.DateFormat. Applied only
|
||||
when formatting a time or both a date and
|
||||
time (i.e. if type is equal to "time" or "both");
|
||||
ignored otherwise.
|
||||
</description>
|
||||
<name>timeStyle</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Custom formatting style for dates and times.
|
||||
</description>
|
||||
<name>pattern</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Time zone in which to represent the formatted
|
||||
time.
|
||||
</description>
|
||||
<name>timeZone</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Name of the exported scoped variable which
|
||||
stores the formatted result as a String.
|
||||
</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>
|
||||
Parses the string representation of a date and/or time
|
||||
</description>
|
||||
<name>parseDate</name>
|
||||
<tag-class>org.apache.taglibs.standard.tag.rt.fmt.ParseDateTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<description>
|
||||
Date string to be parsed.
|
||||
</description>
|
||||
<name>value</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Specifies whether the date string in the
|
||||
value attribute is supposed to contain a
|
||||
time, a date, or both.
|
||||
</description>
|
||||
<name>type</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Predefined formatting style for days
|
||||
which determines how the date
|
||||
component of the date string is to be
|
||||
parsed. Applied only when formatting a
|
||||
date or both a date and time (i.e. if type
|
||||
is missing or is equal to "date" or "both");
|
||||
ignored otherwise.
|
||||
</description>
|
||||
<name>dateStyle</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Predefined formatting styles for times
|
||||
which determines how the time
|
||||
component in the date string is to be
|
||||
parsed. Applied only when formatting a
|
||||
time or both a date and time (i.e. if type
|
||||
is equal to "time" or "both"); ignored
|
||||
otherwise.
|
||||
</description>
|
||||
<name>timeStyle</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Custom formatting pattern which
|
||||
determines how the date string is to be
|
||||
parsed.
|
||||
</description>
|
||||
<name>pattern</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Time zone in which to interpret any time
|
||||
information in the date string.
|
||||
</description>
|
||||
<name>timeZone</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Locale whose predefined formatting styles
|
||||
for dates and times are to be used during
|
||||
the parse operation, or to which the
|
||||
pattern specified via the pattern
|
||||
attribute (if present) is applied.
|
||||
</description>
|
||||
<name>parseLocale</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<description>
|
||||
Name of the exported scoped variable in
|
||||
which the parsing result (of type
|
||||
java.util.Date) is stored.
|
||||
</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>
|
||||
|
||||
</taglib>
|
551
bbb-lti/web-app/WEB-INF/tld/grails.tld
Normal file
@ -0,0 +1,551 @@
|
||||
<?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>The Grails (Groovy on Rails) custom tag library</description>
|
||||
<tlib-version>0.2</tlib-version>
|
||||
<short-name>grails</short-name>
|
||||
<uri>http://grails.codehaus.org/tags</uri>
|
||||
|
||||
|
||||
<tag>
|
||||
<name>link</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspLinkTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<name>action</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>controller</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>id</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>url</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>params</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>form</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspFormTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<name>action</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>controller</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>id</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>url</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>method</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>select</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspSelectTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<name>name</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>value</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>optionKey</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>optionValue</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>datePicker</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspDatePickerTag</tag-class>
|
||||
<body-content>empty</body-content>
|
||||
<attribute>
|
||||
<name>name</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>value</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>precision</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>false</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>currencySelect</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspCurrencySelectTag</tag-class>
|
||||
<body-content>empty</body-content>
|
||||
<attribute>
|
||||
<name>name</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>value</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>localeSelect</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspLocaleSelectTag</tag-class>
|
||||
<body-content>empty</body-content>
|
||||
<attribute>
|
||||
<name>name</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>value</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>timeZoneSelect</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspTimeZoneSelectTag</tag-class>
|
||||
<body-content>empty</body-content>
|
||||
<attribute>
|
||||
<name>name</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>value</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>checkBox</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspCheckboxTag</tag-class>
|
||||
<body-content>empty</body-content>
|
||||
<attribute>
|
||||
<name>name</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>value</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>hasErrors</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspHasErrorsTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<name>model</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>bean</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>field</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>false</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>eachError</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspEachErrorTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<name>model</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>bean</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>field</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>false</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>renderErrors</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspEachErrorTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<name>model</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>bean</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>field</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>as</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>false</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>message</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspMessageTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<name>code</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>error</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>default</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>false</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>remoteFunction</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspRemoteFunctionTag</tag-class>
|
||||
<body-content>empty</body-content>
|
||||
<attribute>
|
||||
<name>before</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>after</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>action</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>controller</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>id</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>url</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>params</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>asynchronous</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>method</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>update</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onSuccess</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onFailure</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onComplete</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onLoading</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onLoaded</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onInteractive</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>remoteLink</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspRemoteLinkTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<name>before</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>after</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>action</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>controller</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>id</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>url</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>params</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>asynchronous</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>method</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>update</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onSuccess</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onFailure</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onComplete</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onLoading</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onLoaded</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onInteractive</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>formRemote</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspFormRemoteTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<attribute>
|
||||
<name>before</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>after</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>action</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>controller</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>id</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>url</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>params</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>asynchronous</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>method</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>update</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onSuccess</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onFailure</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onComplete</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onLoading</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onLoaded</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<attribute>
|
||||
<name>onInteractive</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
<tag>
|
||||
<name>invokeTag</name>
|
||||
<tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspInvokeGrailsTagLibTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
<variable>
|
||||
<name-given>it</name-given>
|
||||
<variable-class>java.lang.Object</variable-class>
|
||||
<declare>true</declare>
|
||||
<scope>NESTED</scope>
|
||||
</variable>
|
||||
<attribute>
|
||||
<name>name</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
<dynamic-attributes>true</dynamic-attributes>
|
||||
</tag>
|
||||
</taglib>
|
||||
|
311
bbb-lti/web-app/WEB-INF/tld/spring.tld
Normal file
@ -0,0 +1,311 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
|
||||
|
||||
<taglib>
|
||||
|
||||
<tlib-version>1.1.1</tlib-version>
|
||||
|
||||
<jsp-version>1.2</jsp-version>
|
||||
|
||||
<short-name>Spring</short-name>
|
||||
|
||||
<uri>http://www.springframework.org/tags</uri>
|
||||
|
||||
<description>Spring Framework JSP Tag Library. Authors: Rod Johnson, Juergen Hoeller</description>
|
||||
|
||||
|
||||
<tag>
|
||||
|
||||
<name>htmlEscape</name>
|
||||
<tag-class>org.springframework.web.servlet.tags.HtmlEscapeTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
|
||||
<description>
|
||||
Sets default HTML escape value for the current page.
|
||||
Overrides a "defaultHtmlEscape" context-param in web.xml, if any.
|
||||
</description>
|
||||
|
||||
<attribute>
|
||||
<name>defaultHtmlEscape</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
</tag>
|
||||
|
||||
|
||||
<tag>
|
||||
|
||||
<name>escapeBody</name>
|
||||
<tag-class>org.springframework.web.servlet.tags.EscapeBodyTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
|
||||
<description>
|
||||
Escapes its enclosed body content, applying HTML escaping and/or JavaScript escaping.
|
||||
The HTML escaping flag participates in a page-wide or application-wide setting
|
||||
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
|
||||
</description>
|
||||
|
||||
<attribute>
|
||||
<name>htmlEscape</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>javaScriptEscape</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
</tag>
|
||||
|
||||
|
||||
<tag>
|
||||
|
||||
<name>message</name>
|
||||
<tag-class>org.springframework.web.servlet.tags.MessageTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
|
||||
<description>
|
||||
Retrieves the message with the given code, or text if code isn't resolvable.
|
||||
The HTML escaping flag participates in a page-wide or application-wide setting
|
||||
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
|
||||
</description>
|
||||
|
||||
<attribute>
|
||||
<name>code</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>arguments</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>text</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>var</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>scope</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>htmlEscape</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>javaScriptEscape</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
</tag>
|
||||
|
||||
|
||||
<tag>
|
||||
|
||||
<name>theme</name>
|
||||
<tag-class>org.springframework.web.servlet.tags.ThemeTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
|
||||
<description>
|
||||
Retrieves the theme message with the given code, or text if code isn't resolvable.
|
||||
The HTML escaping flag participates in a page-wide or application-wide setting
|
||||
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
|
||||
</description>
|
||||
|
||||
<attribute>
|
||||
<name>code</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>arguments</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>text</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>var</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>scope</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>htmlEscape</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>javaScriptEscape</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
</tag>
|
||||
|
||||
|
||||
<tag>
|
||||
|
||||
<name>hasBindErrors</name>
|
||||
<tag-class>org.springframework.web.servlet.tags.BindErrorsTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
|
||||
<description>
|
||||
Provides Errors instance in case of bind errors.
|
||||
The HTML escaping flag participates in a page-wide or application-wide setting
|
||||
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
|
||||
</description>
|
||||
|
||||
<variable>
|
||||
<name-given>errors</name-given>
|
||||
<variable-class>org.springframework.validation.Errors</variable-class>
|
||||
</variable>
|
||||
|
||||
<attribute>
|
||||
<name>name</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>htmlEscape</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
</tag>
|
||||
|
||||
|
||||
<tag>
|
||||
|
||||
<name>nestedPath</name>
|
||||
<tag-class>org.springframework.web.servlet.tags.NestedPathTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
|
||||
<description>
|
||||
Sets a nested path to be used by the bind tag's path.
|
||||
</description>
|
||||
|
||||
<variable>
|
||||
<name-given>nestedPath</name-given>
|
||||
<variable-class>java.lang.String</variable-class>
|
||||
</variable>
|
||||
|
||||
<attribute>
|
||||
<name>path</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
</tag>
|
||||
|
||||
|
||||
<tag>
|
||||
|
||||
<name>bind</name>
|
||||
<tag-class>org.springframework.web.servlet.tags.BindTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
|
||||
<description>
|
||||
Provides BindStatus object for the given bind path.
|
||||
The HTML escaping flag participates in a page-wide or application-wide setting
|
||||
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
|
||||
</description>
|
||||
|
||||
<variable>
|
||||
<name-given>status</name-given>
|
||||
<variable-class>org.springframework.web.servlet.support.BindStatus</variable-class>
|
||||
</variable>
|
||||
|
||||
<attribute>
|
||||
<name>path</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>ignoreNestedPath</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>htmlEscape</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
</tag>
|
||||
|
||||
|
||||
<tag>
|
||||
|
||||
<name>transform</name>
|
||||
<tag-class>org.springframework.web.servlet.tags.TransformTag</tag-class>
|
||||
<body-content>JSP</body-content>
|
||||
|
||||
<description>
|
||||
Provides transformation of variables to Strings, using an appropriate
|
||||
custom PropertyEditor from BindTag (can only be used inside BindTag).
|
||||
The HTML escaping flag participates in a page-wide or application-wide setting
|
||||
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
|
||||
</description>
|
||||
|
||||
<attribute>
|
||||
<name>value</name>
|
||||
<required>true</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>var</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>scope</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>htmlEscape</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
</tag>
|
||||
|
||||
</taglib>
|
267
bbb-lti/web-app/css/main.css
Normal file
@ -0,0 +1,267 @@
|
||||
html * {
|
||||
margin: 0;
|
||||
/*padding: 0; SELECT NOT DISPLAYED CORRECTLY IN FIREFOX */
|
||||
}
|
||||
|
||||
/* GENERAL */
|
||||
|
||||
.spinner {
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #fff;
|
||||
color: #333;
|
||||
font: 11px verdana, arial, helvetica, sans-serif;
|
||||
}
|
||||
|
||||
a:link, a:visited, a:hover {
|
||||
color: #666;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #006dba;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
margin: .8em 0 .3em 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
background-color: #fcfcfc;
|
||||
border: 1px solid #ccc;
|
||||
font: 11px verdana, arial, helvetica, sans-serif;
|
||||
margin: 2px 0;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
select {
|
||||
padding: 2px 2px 2px 0;
|
||||
}
|
||||
textarea {
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
input:focus, select:focus, textarea:focus {
|
||||
border: 1px solid #b2d1ff;
|
||||
}
|
||||
|
||||
.body {
|
||||
float: left;
|
||||
margin: 0 15px 10px 15px;
|
||||
}
|
||||
|
||||
/* NAVIGATION MENU */
|
||||
|
||||
.nav {
|
||||
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
|
||||
border: 1px solid #ccc;
|
||||
border-style: solid none solid none;
|
||||
margin-top: 5px;
|
||||
padding: 7px 12px;
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
font-size: 10px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
.menuButton a {
|
||||
color: #333;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
.menuButton a.home {
|
||||
background: url(../images/skin/house.png) center left no-repeat;
|
||||
color: #333;
|
||||
padding-left: 25px;
|
||||
}
|
||||
.menuButton a.list {
|
||||
background: url(../images/skin/database_table.png) center left no-repeat;
|
||||
color: #333;
|
||||
padding-left: 25px;
|
||||
}
|
||||
.menuButton a.create {
|
||||
background: url(../images/skin/database_add.png) center left no-repeat;
|
||||
color: #333;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
/* MESSAGES AND ERRORS */
|
||||
|
||||
.message {
|
||||
background: #f3f8fc url(../images/skin/information.png) 8px 50% no-repeat;
|
||||
border: 1px solid #b2d1ff;
|
||||
color: #006dba;
|
||||
margin: 10px 0 5px 0;
|
||||
padding: 5px 5px 5px 30px
|
||||
}
|
||||
|
||||
div.errors {
|
||||
background: #fff3f3;
|
||||
border: 1px solid red;
|
||||
color: #cc0000;
|
||||
margin: 10px 0 5px 0;
|
||||
padding: 5px 0 5px 0;
|
||||
}
|
||||
div.errors ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
div.errors li {
|
||||
background: url(../images/skin/exclamation.png) 8px 0% no-repeat;
|
||||
line-height: 16px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
td.errors select {
|
||||
border: 1px solid red;
|
||||
}
|
||||
td.errors input {
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
/* TABLES */
|
||||
|
||||
table {
|
||||
border: 1px solid #ccc;
|
||||
width: 100%
|
||||
}
|
||||
tr {
|
||||
border: 0;
|
||||
}
|
||||
td, th {
|
||||
font: 11px verdana, arial, helvetica, sans-serif;
|
||||
line-height: 12px;
|
||||
padding: 5px 6px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
th {
|
||||
background: #fff url(../images/skin/shadow.jpg);
|
||||
color: #666;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
line-height: 17px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
th a:link, th a:visited, th a:hover {
|
||||
color: #333;
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
}
|
||||
th.asc a, th.desc a {
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
th.asc a {
|
||||
background-image: url(../images/skin/sorted_asc.gif);
|
||||
}
|
||||
th.desc a {
|
||||
background-image: url(../images/skin/sorted_desc.gif);
|
||||
}
|
||||
|
||||
.odd {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
.even {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* LIST */
|
||||
|
||||
.list table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.list th, .list td {
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
.list th:hover, .list tr:hover {
|
||||
background: #b2d1ff;
|
||||
}
|
||||
|
||||
/* PAGINATION */
|
||||
|
||||
.paginateButtons {
|
||||
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
|
||||
border: 1px solid #ccc;
|
||||
border-top: 0;
|
||||
color: #666;
|
||||
font-size: 10px;
|
||||
overflow: hidden;
|
||||
padding: 10px 3px;
|
||||
}
|
||||
.paginateButtons a {
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-color: #ccc #aaa #aaa #ccc;
|
||||
color: #666;
|
||||
margin: 0 3px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
.paginateButtons span {
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
/* DIALOG */
|
||||
|
||||
.dialog table {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.prop {
|
||||
padding: 5px;
|
||||
}
|
||||
.prop .name {
|
||||
text-align: left;
|
||||
width: 15%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.prop .value {
|
||||
text-align: left;
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
/* ACTION BUTTONS */
|
||||
|
||||
.buttons {
|
||||
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
|
||||
border: 1px solid #ccc;
|
||||
color: #666;
|
||||
font-size: 10px;
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.buttons input {
|
||||
background: #fff;
|
||||
border: 0;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
margin-left: 3px;
|
||||
overflow: visible;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
.buttons input.delete {
|
||||
background: transparent url(../images/skin/database_delete.png) 5px 50% no-repeat;
|
||||
padding-left: 28px;
|
||||
}
|
||||
.buttons input.edit {
|
||||
background: transparent url(../images/skin/database_edit.png) 5px 50% no-repeat;
|
||||
padding-left: 28px;
|
||||
}
|
||||
.buttons input.save {
|
||||
background: transparent url(../images/skin/database_save.png) 5px 50% no-repeat;
|
||||
padding-left: 28px;
|
||||
}
|
BIN
bbb-lti/web-app/images/favicon.ico
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
bbb-lti/web-app/images/grails_logo.jpg
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
bbb-lti/web-app/images/skin/database_add.png
Normal file
After Width: | Height: | Size: 658 B |
BIN
bbb-lti/web-app/images/skin/database_delete.png
Normal file
After Width: | Height: | Size: 659 B |
BIN
bbb-lti/web-app/images/skin/database_edit.png
Normal file
After Width: | Height: | Size: 767 B |
BIN
bbb-lti/web-app/images/skin/database_save.png
Normal file
After Width: | Height: | Size: 755 B |
BIN
bbb-lti/web-app/images/skin/database_table.png
Normal file
After Width: | Height: | Size: 726 B |
BIN
bbb-lti/web-app/images/skin/exclamation.png
Normal file
After Width: | Height: | Size: 701 B |
BIN
bbb-lti/web-app/images/skin/house.png
Normal file
After Width: | Height: | Size: 806 B |
BIN
bbb-lti/web-app/images/skin/information.png
Normal file
After Width: | Height: | Size: 778 B |
BIN
bbb-lti/web-app/images/skin/shadow.jpg
Normal file
After Width: | Height: | Size: 300 B |
BIN
bbb-lti/web-app/images/skin/sorted_asc.gif
Normal file
After Width: | Height: | Size: 835 B |
BIN
bbb-lti/web-app/images/skin/sorted_desc.gif
Normal file
After Width: | Height: | Size: 834 B |
BIN
bbb-lti/web-app/images/spinner.gif
Normal file
After Width: | Height: | Size: 2.0 KiB |
13
bbb-lti/web-app/js/application.js
Normal file
@ -0,0 +1,13 @@
|
||||
var Ajax;
|
||||
if (Ajax && (Ajax != null)) {
|
||||
Ajax.Responders.register({
|
||||
onCreate: function() {
|
||||
if($('spinner') && Ajax.activeRequestCount>0)
|
||||
Effect.Appear('spinner',{duration:0.5,queue:'end'});
|
||||
},
|
||||
onComplete: function() {
|
||||
if($('spinner') && Ajax.activeRequestCount==0)
|
||||
Effect.Fade('spinner',{duration:0.5,queue:'end'});
|
||||
}
|
||||
});
|
||||
}
|
7
bbb-lti/web-app/js/prototype/animation.js
vendored
Normal file
136
bbb-lti/web-app/js/prototype/builder.js
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
// script.aculo.us builder.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
|
||||
|
||||
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
var Builder = {
|
||||
NODEMAP: {
|
||||
AREA: 'map',
|
||||
CAPTION: 'table',
|
||||
COL: 'table',
|
||||
COLGROUP: 'table',
|
||||
LEGEND: 'fieldset',
|
||||
OPTGROUP: 'select',
|
||||
OPTION: 'select',
|
||||
PARAM: 'object',
|
||||
TBODY: 'table',
|
||||
TD: 'table',
|
||||
TFOOT: 'table',
|
||||
TH: 'table',
|
||||
THEAD: 'table',
|
||||
TR: 'table'
|
||||
},
|
||||
// note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
|
||||
// due to a Firefox bug
|
||||
node: function(elementName) {
|
||||
elementName = elementName.toUpperCase();
|
||||
|
||||
// try innerHTML approach
|
||||
var parentTag = this.NODEMAP[elementName] || 'div';
|
||||
var parentElement = document.createElement(parentTag);
|
||||
try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
|
||||
parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
|
||||
} catch(e) {}
|
||||
var element = parentElement.firstChild || null;
|
||||
|
||||
// see if browser added wrapping tags
|
||||
if(element && (element.tagName.toUpperCase() != elementName))
|
||||
element = element.getElementsByTagName(elementName)[0];
|
||||
|
||||
// fallback to createElement approach
|
||||
if(!element) element = document.createElement(elementName);
|
||||
|
||||
// abort if nothing could be created
|
||||
if(!element) return;
|
||||
|
||||
// attributes (or text)
|
||||
if(arguments[1])
|
||||
if(this._isStringOrNumber(arguments[1]) ||
|
||||
(arguments[1] instanceof Array) ||
|
||||
arguments[1].tagName) {
|
||||
this._children(element, arguments[1]);
|
||||
} else {
|
||||
var attrs = this._attributes(arguments[1]);
|
||||
if(attrs.length) {
|
||||
try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
|
||||
parentElement.innerHTML = "<" +elementName + " " +
|
||||
attrs + "></" + elementName + ">";
|
||||
} catch(e) {}
|
||||
element = parentElement.firstChild || null;
|
||||
// workaround firefox 1.0.X bug
|
||||
if(!element) {
|
||||
element = document.createElement(elementName);
|
||||
for(attr in arguments[1])
|
||||
element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
|
||||
}
|
||||
if(element.tagName.toUpperCase() != elementName)
|
||||
element = parentElement.getElementsByTagName(elementName)[0];
|
||||
}
|
||||
}
|
||||
|
||||
// text, or array of children
|
||||
if(arguments[2])
|
||||
this._children(element, arguments[2]);
|
||||
|
||||
return element;
|
||||
},
|
||||
_text: function(text) {
|
||||
return document.createTextNode(text);
|
||||
},
|
||||
|
||||
ATTR_MAP: {
|
||||
'className': 'class',
|
||||
'htmlFor': 'for'
|
||||
},
|
||||
|
||||
_attributes: function(attributes) {
|
||||
var attrs = [];
|
||||
for(attribute in attributes)
|
||||
attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
|
||||
'="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'"') + '"');
|
||||
return attrs.join(" ");
|
||||
},
|
||||
_children: function(element, children) {
|
||||
if(children.tagName) {
|
||||
element.appendChild(children);
|
||||
return;
|
||||
}
|
||||
if(typeof children=='object') { // array can hold nodes and text
|
||||
children.flatten().each( function(e) {
|
||||
if(typeof e=='object')
|
||||
element.appendChild(e)
|
||||
else
|
||||
if(Builder._isStringOrNumber(e))
|
||||
element.appendChild(Builder._text(e));
|
||||
});
|
||||
} else
|
||||
if(Builder._isStringOrNumber(children))
|
||||
element.appendChild(Builder._text(children));
|
||||
},
|
||||
_isStringOrNumber: function(param) {
|
||||
return(typeof param=='string' || typeof param=='number');
|
||||
},
|
||||
build: function(html) {
|
||||
var element = this.node('div');
|
||||
$(element).update(html.strip());
|
||||
return element.down();
|
||||
},
|
||||
dump: function(scope) {
|
||||
if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
|
||||
|
||||
var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
|
||||
"BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
|
||||
"FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
|
||||
"KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
|
||||
"PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
|
||||
"TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
|
||||
|
||||
tags.each( function(tag){
|
||||
scope[tag] = function() {
|
||||
return Builder.node.apply(Builder, [tag].concat($A(arguments)));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
965
bbb-lti/web-app/js/prototype/controls.js
vendored
Normal file
@ -0,0 +1,965 @@
|
||||
// script.aculo.us controls.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
|
||||
|
||||
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
// Rob Wills
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
// Autocompleter.Base handles all the autocompletion functionality
|
||||
// that's independent of the data source for autocompletion. This
|
||||
// includes drawing the autocompletion menu, observing keyboard
|
||||
// and mouse events, and similar.
|
||||
//
|
||||
// Specific autocompleters need to provide, at the very least,
|
||||
// a getUpdatedChoices function that will be invoked every time
|
||||
// the text inside the monitored textbox changes. This method
|
||||
// should get the text for which to provide autocompletion by
|
||||
// invoking this.getToken(), NOT by directly accessing
|
||||
// this.element.value. This is to allow incremental tokenized
|
||||
// autocompletion. Specific auto-completion logic (AJAX, etc)
|
||||
// belongs in getUpdatedChoices.
|
||||
//
|
||||
// Tokenized incremental autocompletion is enabled automatically
|
||||
// when an autocompleter is instantiated with the 'tokens' option
|
||||
// in the options parameter, e.g.:
|
||||
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
|
||||
// will incrementally autocomplete with a comma as the token.
|
||||
// Additionally, ',' in the above example can be replaced with
|
||||
// a token array, e.g. { tokens: [',', '\n'] } which
|
||||
// enables autocompletion on multiple tokens. This is most
|
||||
// useful when one of the tokens is \n (a newline), as it
|
||||
// allows smart autocompletion after linebreaks.
|
||||
|
||||
if(typeof Effect == 'undefined')
|
||||
throw("controls.js requires including script.aculo.us' effects.js library");
|
||||
|
||||
var Autocompleter = { }
|
||||
Autocompleter.Base = Class.create({
|
||||
baseInitialize: function(element, update, options) {
|
||||
element = $(element)
|
||||
this.element = element;
|
||||
this.update = $(update);
|
||||
this.hasFocus = false;
|
||||
this.changed = false;
|
||||
this.active = false;
|
||||
this.index = 0;
|
||||
this.entryCount = 0;
|
||||
this.oldElementValue = this.element.value;
|
||||
|
||||
if(this.setOptions)
|
||||
this.setOptions(options);
|
||||
else
|
||||
this.options = options || { };
|
||||
|
||||
this.options.paramName = this.options.paramName || this.element.name;
|
||||
this.options.tokens = this.options.tokens || [];
|
||||
this.options.frequency = this.options.frequency || 0.4;
|
||||
this.options.minChars = this.options.minChars || 1;
|
||||
this.options.onShow = this.options.onShow ||
|
||||
function(element, update){
|
||||
if(!update.style.position || update.style.position=='absolute') {
|
||||
update.style.position = 'absolute';
|
||||
Position.clone(element, update, {
|
||||
setHeight: false,
|
||||
offsetTop: element.offsetHeight
|
||||
});
|
||||
}
|
||||
Effect.Appear(update,{duration:0.15});
|
||||
};
|
||||
this.options.onHide = this.options.onHide ||
|
||||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
||||
|
||||
if(typeof(this.options.tokens) == 'string')
|
||||
this.options.tokens = new Array(this.options.tokens);
|
||||
// Force carriage returns as token delimiters anyway
|
||||
if (!this.options.tokens.include('\n'))
|
||||
this.options.tokens.push('\n');
|
||||
|
||||
this.observer = null;
|
||||
|
||||
this.element.setAttribute('autocomplete','off');
|
||||
|
||||
Element.hide(this.update);
|
||||
|
||||
Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
|
||||
Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
|
||||
if(!this.iefix &&
|
||||
(Prototype.Browser.IE) &&
|
||||
(Element.getStyle(this.update, 'position')=='absolute')) {
|
||||
new Insertion.After(this.update,
|
||||
'<iframe id="' + this.update.id + '_iefix" '+
|
||||
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
||||
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
||||
this.iefix = $(this.update.id+'_iefix');
|
||||
}
|
||||
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
|
||||
},
|
||||
|
||||
fixIEOverlapping: function() {
|
||||
Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
|
||||
this.iefix.style.zIndex = 1;
|
||||
this.update.style.zIndex = 2;
|
||||
Element.show(this.iefix);
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this.stopIndicator();
|
||||
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
|
||||
if(this.iefix) Element.hide(this.iefix);
|
||||
},
|
||||
|
||||
startIndicator: function() {
|
||||
if(this.options.indicator) Element.show(this.options.indicator);
|
||||
},
|
||||
|
||||
stopIndicator: function() {
|
||||
if(this.options.indicator) Element.hide(this.options.indicator);
|
||||
},
|
||||
|
||||
onKeyPress: function(event) {
|
||||
if(this.active)
|
||||
switch(event.keyCode) {
|
||||
case Event.KEY_TAB:
|
||||
case Event.KEY_RETURN:
|
||||
this.selectEntry();
|
||||
Event.stop(event);
|
||||
case Event.KEY_ESC:
|
||||
this.hide();
|
||||
this.active = false;
|
||||
Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_LEFT:
|
||||
case Event.KEY_RIGHT:
|
||||
return;
|
||||
case Event.KEY_UP:
|
||||
this.markPrevious();
|
||||
this.render();
|
||||
if(Prototype.Browser.WebKit) Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_DOWN:
|
||||
this.markNext();
|
||||
this.render();
|
||||
if(Prototype.Browser.WebKit) Event.stop(event);
|
||||
return;
|
||||
}
|
||||
else
|
||||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
|
||||
(Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
|
||||
|
||||
this.changed = true;
|
||||
this.hasFocus = true;
|
||||
|
||||
if(this.observer) clearTimeout(this.observer);
|
||||
this.observer =
|
||||
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.changed = false;
|
||||
this.hasFocus = true;
|
||||
this.getUpdatedChoices();
|
||||
},
|
||||
|
||||
onHover: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
if(this.index != element.autocompleteIndex)
|
||||
{
|
||||
this.index = element.autocompleteIndex;
|
||||
this.render();
|
||||
}
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
onClick: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
this.index = element.autocompleteIndex;
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
},
|
||||
|
||||
onBlur: function(event) {
|
||||
// needed to make click events working
|
||||
setTimeout(this.hide.bind(this), 250);
|
||||
this.hasFocus = false;
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if(this.entryCount > 0) {
|
||||
for (var i = 0; i < this.entryCount; i++)
|
||||
this.index==i ?
|
||||
Element.addClassName(this.getEntry(i),"selected") :
|
||||
Element.removeClassName(this.getEntry(i),"selected");
|
||||
if(this.hasFocus) {
|
||||
this.show();
|
||||
this.active = true;
|
||||
}
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
markPrevious: function() {
|
||||
if(this.index > 0) this.index--
|
||||
else this.index = this.entryCount-1;
|
||||
this.getEntry(this.index).scrollIntoView(true);
|
||||
},
|
||||
|
||||
markNext: function() {
|
||||
if(this.index < this.entryCount-1) this.index++
|
||||
else this.index = 0;
|
||||
this.getEntry(this.index).scrollIntoView(false);
|
||||
},
|
||||
|
||||
getEntry: function(index) {
|
||||
return this.update.firstChild.childNodes[index];
|
||||
},
|
||||
|
||||
getCurrentEntry: function() {
|
||||
return this.getEntry(this.index);
|
||||
},
|
||||
|
||||
selectEntry: function() {
|
||||
this.active = false;
|
||||
this.updateElement(this.getCurrentEntry());
|
||||
},
|
||||
|
||||
updateElement: function(selectedElement) {
|
||||
if (this.options.updateElement) {
|
||||
this.options.updateElement(selectedElement);
|
||||
return;
|
||||
}
|
||||
var value = '';
|
||||
if (this.options.select) {
|
||||
var nodes = $(selectedElement).select('.' + this.options.select) || [];
|
||||
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
|
||||
} else
|
||||
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
||||
|
||||
var bounds = this.getTokenBounds();
|
||||
if (bounds[0] != -1) {
|
||||
var newValue = this.element.value.substr(0, bounds[0]);
|
||||
var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
|
||||
if (whitespace)
|
||||
newValue += whitespace[0];
|
||||
this.element.value = newValue + value + this.element.value.substr(bounds[1]);
|
||||
} else {
|
||||
this.element.value = value;
|
||||
}
|
||||
this.oldElementValue = this.element.value;
|
||||
this.element.focus();
|
||||
|
||||
if (this.options.afterUpdateElement)
|
||||
this.options.afterUpdateElement(this.element, selectedElement);
|
||||
},
|
||||
|
||||
updateChoices: function(choices) {
|
||||
if(!this.changed && this.hasFocus) {
|
||||
this.update.innerHTML = choices;
|
||||
Element.cleanWhitespace(this.update);
|
||||
Element.cleanWhitespace(this.update.down());
|
||||
|
||||
if(this.update.firstChild && this.update.down().childNodes) {
|
||||
this.entryCount =
|
||||
this.update.down().childNodes.length;
|
||||
for (var i = 0; i < this.entryCount; i++) {
|
||||
var entry = this.getEntry(i);
|
||||
entry.autocompleteIndex = i;
|
||||
this.addObservers(entry);
|
||||
}
|
||||
} else {
|
||||
this.entryCount = 0;
|
||||
}
|
||||
|
||||
this.stopIndicator();
|
||||
this.index = 0;
|
||||
|
||||
if(this.entryCount==1 && this.options.autoSelect) {
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addObservers: function(element) {
|
||||
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
|
||||
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
onObserverEvent: function() {
|
||||
this.changed = false;
|
||||
this.tokenBounds = null;
|
||||
if(this.getToken().length>=this.options.minChars) {
|
||||
this.getUpdatedChoices();
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
this.oldElementValue = this.element.value;
|
||||
},
|
||||
|
||||
getToken: function() {
|
||||
var bounds = this.getTokenBounds();
|
||||
return this.element.value.substring(bounds[0], bounds[1]).strip();
|
||||
},
|
||||
|
||||
getTokenBounds: function() {
|
||||
if (null != this.tokenBounds) return this.tokenBounds;
|
||||
var value = this.element.value;
|
||||
if (value.strip().empty()) return [-1, 0];
|
||||
var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
|
||||
var offset = (diff == this.oldElementValue.length ? 1 : 0);
|
||||
var prevTokenPos = -1, nextTokenPos = value.length;
|
||||
var tp;
|
||||
for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
|
||||
tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
|
||||
if (tp > prevTokenPos) prevTokenPos = tp;
|
||||
tp = value.indexOf(this.options.tokens[index], diff + offset);
|
||||
if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
|
||||
}
|
||||
return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
|
||||
}
|
||||
});
|
||||
|
||||
Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
|
||||
var boundary = Math.min(newS.length, oldS.length);
|
||||
for (var index = 0; index < boundary; ++index)
|
||||
if (newS[index] != oldS[index])
|
||||
return index;
|
||||
return boundary;
|
||||
};
|
||||
|
||||
Ajax.Autocompleter = Class.create(Autocompleter.Base, {
|
||||
initialize: function(element, update, url, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.asynchronous = true;
|
||||
this.options.onComplete = this.onComplete.bind(this);
|
||||
this.options.defaultParams = this.options.parameters || null;
|
||||
this.url = url;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
this.startIndicator();
|
||||
|
||||
var entry = encodeURIComponent(this.options.paramName) + '=' +
|
||||
encodeURIComponent(this.getToken());
|
||||
|
||||
this.options.parameters = this.options.callback ?
|
||||
this.options.callback(this.element, entry) : entry;
|
||||
|
||||
if(this.options.defaultParams)
|
||||
this.options.parameters += '&' + this.options.defaultParams;
|
||||
|
||||
new Ajax.Request(this.url, this.options);
|
||||
},
|
||||
|
||||
onComplete: function(request) {
|
||||
this.updateChoices(request.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
// The local array autocompleter. Used when you'd prefer to
|
||||
// inject an array of autocompletion options into the page, rather
|
||||
// than sending out Ajax queries, which can be quite slow sometimes.
|
||||
//
|
||||
// The constructor takes four parameters. The first two are, as usual,
|
||||
// the id of the monitored textbox, and id of the autocompletion menu.
|
||||
// The third is the array you want to autocomplete from, and the fourth
|
||||
// is the options block.
|
||||
//
|
||||
// Extra local autocompletion options:
|
||||
// - choices - How many autocompletion choices to offer
|
||||
//
|
||||
// - partialSearch - If false, the autocompleter will match entered
|
||||
// text only at the beginning of strings in the
|
||||
// autocomplete array. Defaults to true, which will
|
||||
// match text at the beginning of any *word* in the
|
||||
// strings in the autocomplete array. If you want to
|
||||
// search anywhere in the string, additionally set
|
||||
// the option fullSearch to true (default: off).
|
||||
//
|
||||
// - fullSsearch - Search anywhere in autocomplete array strings.
|
||||
//
|
||||
// - partialChars - How many characters to enter before triggering
|
||||
// a partial match (unlike minChars, which defines
|
||||
// how many characters are required to do any match
|
||||
// at all). Defaults to 2.
|
||||
//
|
||||
// - ignoreCase - Whether to ignore case when autocompleting.
|
||||
// Defaults to true.
|
||||
//
|
||||
// It's possible to pass in a custom function as the 'selector'
|
||||
// option, if you prefer to write your own autocompletion logic.
|
||||
// In that case, the other options above will not apply unless
|
||||
// you support them.
|
||||
|
||||
Autocompleter.Local = Class.create(Autocompleter.Base, {
|
||||
initialize: function(element, update, array, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.array = array;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
this.updateChoices(this.options.selector(this));
|
||||
},
|
||||
|
||||
setOptions: function(options) {
|
||||
this.options = Object.extend({
|
||||
choices: 10,
|
||||
partialSearch: true,
|
||||
partialChars: 2,
|
||||
ignoreCase: true,
|
||||
fullSearch: false,
|
||||
selector: function(instance) {
|
||||
var ret = []; // Beginning matches
|
||||
var partial = []; // Inside matches
|
||||
var entry = instance.getToken();
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < instance.options.array.length &&
|
||||
ret.length < instance.options.choices ; i++) {
|
||||
|
||||
var elem = instance.options.array[i];
|
||||
var foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase()) :
|
||||
elem.indexOf(entry);
|
||||
|
||||
while (foundPos != -1) {
|
||||
if (foundPos == 0 && elem.length != entry.length) {
|
||||
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
||||
elem.substr(entry.length) + "</li>");
|
||||
break;
|
||||
} else if (entry.length >= instance.options.partialChars &&
|
||||
instance.options.partialSearch && foundPos != -1) {
|
||||
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
|
||||
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
|
||||
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
|
||||
foundPos + entry.length) + "</li>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
|
||||
elem.indexOf(entry, foundPos + 1);
|
||||
|
||||
}
|
||||
}
|
||||
if (partial.length)
|
||||
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
|
||||
return "<ul>" + ret.join('') + "</ul>";
|
||||
}
|
||||
}, options || { });
|
||||
}
|
||||
});
|
||||
|
||||
// AJAX in-place editor and collection editor
|
||||
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
|
||||
|
||||
// Use this if you notice weird scrolling problems on some browsers,
|
||||
// the DOM might be a bit confused when this gets called so do this
|
||||
// waits 1 ms (with setTimeout) until it does the activation
|
||||
Field.scrollFreeActivate = function(field) {
|
||||
setTimeout(function() {
|
||||
Field.activate(field);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
Ajax.InPlaceEditor = Class.create({
|
||||
initialize: function(element, url, options) {
|
||||
this.url = url;
|
||||
this.element = element = $(element);
|
||||
this.prepareOptions();
|
||||
this._controls = { };
|
||||
arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
|
||||
Object.extend(this.options, options || { });
|
||||
if (!this.options.formId && this.element.id) {
|
||||
this.options.formId = this.element.id + '-inplaceeditor';
|
||||
if ($(this.options.formId))
|
||||
this.options.formId = '';
|
||||
}
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl = $(this.options.externalControl);
|
||||
if (!this.options.externalControl)
|
||||
this.options.externalControlOnly = false;
|
||||
this._originalBackground = this.element.getStyle('background-color') || 'transparent';
|
||||
this.element.title = this.options.clickToEditText;
|
||||
this._boundCancelHandler = this.handleFormCancellation.bind(this);
|
||||
this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
|
||||
this._boundFailureHandler = this.handleAJAXFailure.bind(this);
|
||||
this._boundSubmitHandler = this.handleFormSubmission.bind(this);
|
||||
this._boundWrapperHandler = this.wrapUp.bind(this);
|
||||
this.registerListeners();
|
||||
},
|
||||
checkForEscapeOrReturn: function(e) {
|
||||
if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
|
||||
if (Event.KEY_ESC == e.keyCode)
|
||||
this.handleFormCancellation(e);
|
||||
else if (Event.KEY_RETURN == e.keyCode)
|
||||
this.handleFormSubmission(e);
|
||||
},
|
||||
createControl: function(mode, handler, extraClasses) {
|
||||
var control = this.options[mode + 'Control'];
|
||||
var text = this.options[mode + 'Text'];
|
||||
if ('button' == control) {
|
||||
var btn = document.createElement('input');
|
||||
btn.type = 'submit';
|
||||
btn.value = text;
|
||||
btn.className = 'editor_' + mode + '_button';
|
||||
if ('cancel' == mode)
|
||||
btn.onclick = this._boundCancelHandler;
|
||||
this._form.appendChild(btn);
|
||||
this._controls[mode] = btn;
|
||||
} else if ('link' == control) {
|
||||
var link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.appendChild(document.createTextNode(text));
|
||||
link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
|
||||
link.className = 'editor_' + mode + '_link';
|
||||
if (extraClasses)
|
||||
link.className += ' ' + extraClasses;
|
||||
this._form.appendChild(link);
|
||||
this._controls[mode] = link;
|
||||
}
|
||||
},
|
||||
createEditField: function() {
|
||||
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
|
||||
var fld;
|
||||
if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
|
||||
fld = document.createElement('input');
|
||||
fld.type = 'text';
|
||||
var size = this.options.size || this.options.cols || 0;
|
||||
if (0 < size) fld.size = size;
|
||||
} else {
|
||||
fld = document.createElement('textarea');
|
||||
fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
|
||||
fld.cols = this.options.cols || 40;
|
||||
}
|
||||
fld.name = this.options.paramName;
|
||||
fld.value = text; // No HTML breaks conversion anymore
|
||||
fld.className = 'editor_field';
|
||||
if (this.options.submitOnBlur)
|
||||
fld.onblur = this._boundSubmitHandler;
|
||||
this._controls.editor = fld;
|
||||
if (this.options.loadTextURL)
|
||||
this.loadExternalText();
|
||||
this._form.appendChild(this._controls.editor);
|
||||
},
|
||||
createForm: function() {
|
||||
var ipe = this;
|
||||
function addText(mode, condition) {
|
||||
var text = ipe.options['text' + mode + 'Controls'];
|
||||
if (!text || condition === false) return;
|
||||
ipe._form.appendChild(document.createTextNode(text));
|
||||
};
|
||||
this._form = $(document.createElement('form'));
|
||||
this._form.id = this.options.formId;
|
||||
this._form.addClassName(this.options.formClassName);
|
||||
this._form.onsubmit = this._boundSubmitHandler;
|
||||
this.createEditField();
|
||||
if ('textarea' == this._controls.editor.tagName.toLowerCase())
|
||||
this._form.appendChild(document.createElement('br'));
|
||||
if (this.options.onFormCustomization)
|
||||
this.options.onFormCustomization(this, this._form);
|
||||
addText('Before', this.options.okControl || this.options.cancelControl);
|
||||
this.createControl('ok', this._boundSubmitHandler);
|
||||
addText('Between', this.options.okControl && this.options.cancelControl);
|
||||
this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
|
||||
addText('After', this.options.okControl || this.options.cancelControl);
|
||||
},
|
||||
destroy: function() {
|
||||
if (this._oldInnerHTML)
|
||||
this.element.innerHTML = this._oldInnerHTML;
|
||||
this.leaveEditMode();
|
||||
this.unregisterListeners();
|
||||
},
|
||||
enterEditMode: function(e) {
|
||||
if (this._saving || this._editing) return;
|
||||
this._editing = true;
|
||||
this.triggerCallback('onEnterEditMode');
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.hide();
|
||||
this.element.hide();
|
||||
this.createForm();
|
||||
this.element.parentNode.insertBefore(this._form, this.element);
|
||||
if (!this.options.loadTextURL)
|
||||
this.postProcessEditField();
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
enterHover: function(e) {
|
||||
if (this.options.hoverClassName)
|
||||
this.element.addClassName(this.options.hoverClassName);
|
||||
if (this._saving) return;
|
||||
this.triggerCallback('onEnterHover');
|
||||
},
|
||||
getText: function() {
|
||||
return this.element.innerHTML;
|
||||
},
|
||||
handleAJAXFailure: function(transport) {
|
||||
this.triggerCallback('onFailure', transport);
|
||||
if (this._oldInnerHTML) {
|
||||
this.element.innerHTML = this._oldInnerHTML;
|
||||
this._oldInnerHTML = null;
|
||||
}
|
||||
},
|
||||
handleFormCancellation: function(e) {
|
||||
this.wrapUp();
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
handleFormSubmission: function(e) {
|
||||
var form = this._form;
|
||||
var value = $F(this._controls.editor);
|
||||
this.prepareSubmission();
|
||||
var params = this.options.callback(form, value) || '';
|
||||
if (Object.isString(params))
|
||||
params = params.toQueryParams();
|
||||
params.editorId = this.element.id;
|
||||
if (this.options.htmlResponse) {
|
||||
var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: params,
|
||||
onComplete: this._boundWrapperHandler,
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Updater({ success: this.element }, this.url, options);
|
||||
} else {
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: params,
|
||||
onComplete: this._boundWrapperHandler,
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Request(this.url, options);
|
||||
}
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
leaveEditMode: function() {
|
||||
this.element.removeClassName(this.options.savingClassName);
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.element.style.backgroundColor = this._originalBackground;
|
||||
this.element.show();
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.show();
|
||||
this._saving = false;
|
||||
this._editing = false;
|
||||
this._oldInnerHTML = null;
|
||||
this.triggerCallback('onLeaveEditMode');
|
||||
},
|
||||
leaveHover: function(e) {
|
||||
if (this.options.hoverClassName)
|
||||
this.element.removeClassName(this.options.hoverClassName);
|
||||
if (this._saving) return;
|
||||
this.triggerCallback('onLeaveHover');
|
||||
},
|
||||
loadExternalText: function() {
|
||||
this._form.addClassName(this.options.loadingClassName);
|
||||
this._controls.editor.disabled = true;
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
this._form.removeClassName(this.options.loadingClassName);
|
||||
var text = transport.responseText;
|
||||
if (this.options.stripLoadedTextTags)
|
||||
text = text.stripTags();
|
||||
this._controls.editor.value = text;
|
||||
this._controls.editor.disabled = false;
|
||||
this.postProcessEditField();
|
||||
}.bind(this),
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Request(this.options.loadTextURL, options);
|
||||
},
|
||||
postProcessEditField: function() {
|
||||
var fpc = this.options.fieldPostCreation;
|
||||
if (fpc)
|
||||
$(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
|
||||
},
|
||||
prepareOptions: function() {
|
||||
this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
|
||||
Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
|
||||
[this._extraDefaultOptions].flatten().compact().each(function(defs) {
|
||||
Object.extend(this.options, defs);
|
||||
}.bind(this));
|
||||
},
|
||||
prepareSubmission: function() {
|
||||
this._saving = true;
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.showSaving();
|
||||
},
|
||||
registerListeners: function() {
|
||||
this._listeners = { };
|
||||
var listener;
|
||||
$H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
|
||||
listener = this[pair.value].bind(this);
|
||||
this._listeners[pair.key] = listener;
|
||||
if (!this.options.externalControlOnly)
|
||||
this.element.observe(pair.key, listener);
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.observe(pair.key, listener);
|
||||
}.bind(this));
|
||||
},
|
||||
removeForm: function() {
|
||||
if (!this._form) return;
|
||||
this._form.remove();
|
||||
this._form = null;
|
||||
this._controls = { };
|
||||
},
|
||||
showSaving: function() {
|
||||
this._oldInnerHTML = this.element.innerHTML;
|
||||
this.element.innerHTML = this.options.savingText;
|
||||
this.element.addClassName(this.options.savingClassName);
|
||||
this.element.style.backgroundColor = this._originalBackground;
|
||||
this.element.show();
|
||||
},
|
||||
triggerCallback: function(cbName, arg) {
|
||||
if ('function' == typeof this.options[cbName]) {
|
||||
this.options[cbName](this, arg);
|
||||
}
|
||||
},
|
||||
unregisterListeners: function() {
|
||||
$H(this._listeners).each(function(pair) {
|
||||
if (!this.options.externalControlOnly)
|
||||
this.element.stopObserving(pair.key, pair.value);
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.stopObserving(pair.key, pair.value);
|
||||
}.bind(this));
|
||||
},
|
||||
wrapUp: function(transport) {
|
||||
this.leaveEditMode();
|
||||
// Can't use triggerCallback due to backward compatibility: requires
|
||||
// binding + direct element
|
||||
this._boundComplete(transport, this.element);
|
||||
}
|
||||
});
|
||||
|
||||
Object.extend(Ajax.InPlaceEditor.prototype, {
|
||||
dispose: Ajax.InPlaceEditor.prototype.destroy
|
||||
});
|
||||
|
||||
Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
|
||||
initialize: function($super, element, url, options) {
|
||||
this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
|
||||
$super(element, url, options);
|
||||
},
|
||||
|
||||
createEditField: function() {
|
||||
var list = document.createElement('select');
|
||||
list.name = this.options.paramName;
|
||||
list.size = 1;
|
||||
this._controls.editor = list;
|
||||
this._collection = this.options.collection || [];
|
||||
if (this.options.loadCollectionURL)
|
||||
this.loadCollection();
|
||||
else
|
||||
this.checkForExternalText();
|
||||
this._form.appendChild(this._controls.editor);
|
||||
},
|
||||
|
||||
loadCollection: function() {
|
||||
this._form.addClassName(this.options.loadingClassName);
|
||||
this.showLoadingText(this.options.loadingCollectionText);
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
var js = transport.responseText.strip();
|
||||
if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
|
||||
throw 'Server returned an invalid collection representation.';
|
||||
this._collection = eval(js);
|
||||
this.checkForExternalText();
|
||||
}.bind(this),
|
||||
onFailure: this.onFailure
|
||||
});
|
||||
new Ajax.Request(this.options.loadCollectionURL, options);
|
||||
},
|
||||
|
||||
showLoadingText: function(text) {
|
||||
this._controls.editor.disabled = true;
|
||||
var tempOption = this._controls.editor.firstChild;
|
||||
if (!tempOption) {
|
||||
tempOption = document.createElement('option');
|
||||
tempOption.value = '';
|
||||
this._controls.editor.appendChild(tempOption);
|
||||
tempOption.selected = true;
|
||||
}
|
||||
tempOption.update((text || '').stripScripts().stripTags());
|
||||
},
|
||||
|
||||
checkForExternalText: function() {
|
||||
this._text = this.getText();
|
||||
if (this.options.loadTextURL)
|
||||
this.loadExternalText();
|
||||
else
|
||||
this.buildOptionList();
|
||||
},
|
||||
|
||||
loadExternalText: function() {
|
||||
this.showLoadingText(this.options.loadingText);
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
this._text = transport.responseText.strip();
|
||||
this.buildOptionList();
|
||||
}.bind(this),
|
||||
onFailure: this.onFailure
|
||||
});
|
||||
new Ajax.Request(this.options.loadTextURL, options);
|
||||
},
|
||||
|
||||
buildOptionList: function() {
|
||||
this._form.removeClassName(this.options.loadingClassName);
|
||||
this._collection = this._collection.map(function(entry) {
|
||||
return 2 === entry.length ? entry : [entry, entry].flatten();
|
||||
});
|
||||
var marker = ('value' in this.options) ? this.options.value : this._text;
|
||||
var textFound = this._collection.any(function(entry) {
|
||||
return entry[0] == marker;
|
||||
}.bind(this));
|
||||
this._controls.editor.update('');
|
||||
var option;
|
||||
this._collection.each(function(entry, index) {
|
||||
option = document.createElement('option');
|
||||
option.value = entry[0];
|
||||
option.selected = textFound ? entry[0] == marker : 0 == index;
|
||||
option.appendChild(document.createTextNode(entry[1]));
|
||||
this._controls.editor.appendChild(option);
|
||||
}.bind(this));
|
||||
this._controls.editor.disabled = false;
|
||||
Field.scrollFreeActivate(this._controls.editor);
|
||||
}
|
||||
});
|
||||
|
||||
//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
|
||||
//**** This only exists for a while, in order to let ****
|
||||
//**** users adapt to the new API. Read up on the new ****
|
||||
//**** API and convert your code to it ASAP! ****
|
||||
|
||||
Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
|
||||
if (!options) return;
|
||||
function fallback(name, expr) {
|
||||
if (name in options || expr === undefined) return;
|
||||
options[name] = expr;
|
||||
};
|
||||
fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
|
||||
options.cancelLink == options.cancelButton == false ? false : undefined)));
|
||||
fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
|
||||
options.okLink == options.okButton == false ? false : undefined)));
|
||||
fallback('highlightColor', options.highlightcolor);
|
||||
fallback('highlightEndColor', options.highlightendcolor);
|
||||
};
|
||||
|
||||
Object.extend(Ajax.InPlaceEditor, {
|
||||
DefaultOptions: {
|
||||
ajaxOptions: { },
|
||||
autoRows: 3, // Use when multi-line w/ rows == 1
|
||||
cancelControl: 'link', // 'link'|'button'|false
|
||||
cancelText: 'cancel',
|
||||
clickToEditText: 'Click to edit',
|
||||
externalControl: null, // id|elt
|
||||
externalControlOnly: false,
|
||||
fieldPostCreation: 'activate', // 'activate'|'focus'|false
|
||||
formClassName: 'inplaceeditor-form',
|
||||
formId: null, // id|elt
|
||||
highlightColor: '#ffff99',
|
||||
highlightEndColor: '#ffffff',
|
||||
hoverClassName: '',
|
||||
htmlResponse: true,
|
||||
loadingClassName: 'inplaceeditor-loading',
|
||||
loadingText: 'Loading...',
|
||||
okControl: 'button', // 'link'|'button'|false
|
||||
okText: 'ok',
|
||||
paramName: 'value',
|
||||
rows: 1, // If 1 and multi-line, uses autoRows
|
||||
savingClassName: 'inplaceeditor-saving',
|
||||
savingText: 'Saving...',
|
||||
size: 0,
|
||||
stripLoadedTextTags: false,
|
||||
submitOnBlur: false,
|
||||
textAfterControls: '',
|
||||
textBeforeControls: '',
|
||||
textBetweenControls: ''
|
||||
},
|
||||
DefaultCallbacks: {
|
||||
callback: function(form) {
|
||||
return Form.serialize(form);
|
||||
},
|
||||
onComplete: function(transport, element) {
|
||||
// For backward compatibility, this one is bound to the IPE, and passes
|
||||
// the element directly. It was too often customized, so we don't break it.
|
||||
new Effect.Highlight(element, {
|
||||
startcolor: this.options.highlightColor, keepBackgroundImage: true });
|
||||
},
|
||||
onEnterEditMode: null,
|
||||
onEnterHover: function(ipe) {
|
||||
ipe.element.style.backgroundColor = ipe.options.highlightColor;
|
||||
if (ipe._effect)
|
||||
ipe._effect.cancel();
|
||||
},
|
||||
onFailure: function(transport, ipe) {
|
||||
alert('Error communication with the server: ' + transport.responseText.stripTags());
|
||||
},
|
||||
onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
|
||||
onLeaveEditMode: null,
|
||||
onLeaveHover: function(ipe) {
|
||||
ipe._effect = new Effect.Highlight(ipe.element, {
|
||||
startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
|
||||
restorecolor: ipe._originalBackground, keepBackgroundImage: true
|
||||
});
|
||||
}
|
||||
},
|
||||
Listeners: {
|
||||
click: 'enterEditMode',
|
||||
keydown: 'checkForEscapeOrReturn',
|
||||
mouseover: 'enterHover',
|
||||
mouseout: 'leaveHover'
|
||||
}
|
||||
});
|
||||
|
||||
Ajax.InPlaceCollectionEditor.DefaultOptions = {
|
||||
loadingCollectionText: 'Loading options...'
|
||||
};
|
||||
|
||||
// Delayed observer, like Form.Element.Observer,
|
||||
// but waits for delay after last key input
|
||||
// Ideal for live-search fields
|
||||
|
||||
Form.Element.DelayedObserver = Class.create({
|
||||
initialize: function(element, delay, callback) {
|
||||
this.delay = delay || 0.5;
|
||||
this.element = $(element);
|
||||
this.callback = callback;
|
||||
this.timer = null;
|
||||
this.lastValue = $F(this.element);
|
||||
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
|
||||
},
|
||||
delayedListener: function(event) {
|
||||
if(this.lastValue == $F(this.element)) return;
|
||||
if(this.timer) clearTimeout(this.timer);
|
||||
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
|
||||
this.lastValue = $F(this.element);
|
||||
},
|
||||
onTimerEvent: function() {
|
||||
this.timer = null;
|
||||
this.callback(this.element, $F(this.element));
|
||||
}
|
||||
});
|
974
bbb-lti/web-app/js/prototype/dragdrop.js
vendored
Normal file
@ -0,0 +1,974 @@
|
||||
// script.aculo.us dragdrop.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
|
||||
|
||||
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
if(Object.isUndefined(Effect))
|
||||
throw("dragdrop.js requires including script.aculo.us' effects.js library");
|
||||
|
||||
var Droppables = {
|
||||
drops: [],
|
||||
|
||||
remove: function(element) {
|
||||
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
|
||||
},
|
||||
|
||||
add: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
greedy: true,
|
||||
hoverclass: null,
|
||||
tree: false
|
||||
}, arguments[1] || { });
|
||||
|
||||
// cache containers
|
||||
if(options.containment) {
|
||||
options._containers = [];
|
||||
var containment = options.containment;
|
||||
if(Object.isArray(containment)) {
|
||||
containment.each( function(c) { options._containers.push($(c)) });
|
||||
} else {
|
||||
options._containers.push($(containment));
|
||||
}
|
||||
}
|
||||
|
||||
if(options.accept) options.accept = [options.accept].flatten();
|
||||
|
||||
Element.makePositioned(element); // fix IE
|
||||
options.element = element;
|
||||
|
||||
this.drops.push(options);
|
||||
},
|
||||
|
||||
findDeepestChild: function(drops) {
|
||||
deepest = drops[0];
|
||||
|
||||
for (i = 1; i < drops.length; ++i)
|
||||
if (Element.isParent(drops[i].element, deepest.element))
|
||||
deepest = drops[i];
|
||||
|
||||
return deepest;
|
||||
},
|
||||
|
||||
isContained: function(element, drop) {
|
||||
var containmentNode;
|
||||
if(drop.tree) {
|
||||
containmentNode = element.treeNode;
|
||||
} else {
|
||||
containmentNode = element.parentNode;
|
||||
}
|
||||
return drop._containers.detect(function(c) { return containmentNode == c });
|
||||
},
|
||||
|
||||
isAffected: function(point, element, drop) {
|
||||
return (
|
||||
(drop.element!=element) &&
|
||||
((!drop._containers) ||
|
||||
this.isContained(element, drop)) &&
|
||||
((!drop.accept) ||
|
||||
(Element.classNames(element).detect(
|
||||
function(v) { return drop.accept.include(v) } ) )) &&
|
||||
Position.within(drop.element, point[0], point[1]) );
|
||||
},
|
||||
|
||||
deactivate: function(drop) {
|
||||
if(drop.hoverclass)
|
||||
Element.removeClassName(drop.element, drop.hoverclass);
|
||||
this.last_active = null;
|
||||
},
|
||||
|
||||
activate: function(drop) {
|
||||
if(drop.hoverclass)
|
||||
Element.addClassName(drop.element, drop.hoverclass);
|
||||
this.last_active = drop;
|
||||
},
|
||||
|
||||
show: function(point, element) {
|
||||
if(!this.drops.length) return;
|
||||
var drop, affected = [];
|
||||
|
||||
this.drops.each( function(drop) {
|
||||
if(Droppables.isAffected(point, element, drop))
|
||||
affected.push(drop);
|
||||
});
|
||||
|
||||
if(affected.length>0)
|
||||
drop = Droppables.findDeepestChild(affected);
|
||||
|
||||
if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
|
||||
if (drop) {
|
||||
Position.within(drop.element, point[0], point[1]);
|
||||
if(drop.onHover)
|
||||
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
|
||||
|
||||
if (drop != this.last_active) Droppables.activate(drop);
|
||||
}
|
||||
},
|
||||
|
||||
fire: function(event, element) {
|
||||
if(!this.last_active) return;
|
||||
Position.prepare();
|
||||
|
||||
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
|
||||
if (this.last_active.onDrop) {
|
||||
this.last_active.onDrop(element, this.last_active.element, event);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
if(this.last_active)
|
||||
this.deactivate(this.last_active);
|
||||
}
|
||||
}
|
||||
|
||||
var Draggables = {
|
||||
drags: [],
|
||||
observers: [],
|
||||
|
||||
register: function(draggable) {
|
||||
if(this.drags.length == 0) {
|
||||
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
||||
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
|
||||
this.eventKeypress = this.keyPress.bindAsEventListener(this);
|
||||
|
||||
Event.observe(document, "mouseup", this.eventMouseUp);
|
||||
Event.observe(document, "mousemove", this.eventMouseMove);
|
||||
Event.observe(document, "keypress", this.eventKeypress);
|
||||
}
|
||||
this.drags.push(draggable);
|
||||
},
|
||||
|
||||
unregister: function(draggable) {
|
||||
this.drags = this.drags.reject(function(d) { return d==draggable });
|
||||
if(this.drags.length == 0) {
|
||||
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
||||
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
||||
Event.stopObserving(document, "keypress", this.eventKeypress);
|
||||
}
|
||||
},
|
||||
|
||||
activate: function(draggable) {
|
||||
if(draggable.options.delay) {
|
||||
this._timeout = setTimeout(function() {
|
||||
Draggables._timeout = null;
|
||||
window.focus();
|
||||
Draggables.activeDraggable = draggable;
|
||||
}.bind(this), draggable.options.delay);
|
||||
} else {
|
||||
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
|
||||
this.activeDraggable = draggable;
|
||||
}
|
||||
},
|
||||
|
||||
deactivate: function() {
|
||||
this.activeDraggable = null;
|
||||
},
|
||||
|
||||
updateDrag: function(event) {
|
||||
if(!this.activeDraggable) return;
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
// Mozilla-based browsers fire successive mousemove events with
|
||||
// the same coordinates, prevent needless redrawing (moz bug?)
|
||||
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
|
||||
this._lastPointer = pointer;
|
||||
|
||||
this.activeDraggable.updateDrag(event, pointer);
|
||||
},
|
||||
|
||||
endDrag: function(event) {
|
||||
if(this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
}
|
||||
if(!this.activeDraggable) return;
|
||||
this._lastPointer = null;
|
||||
this.activeDraggable.endDrag(event);
|
||||
this.activeDraggable = null;
|
||||
},
|
||||
|
||||
keyPress: function(event) {
|
||||
if(this.activeDraggable)
|
||||
this.activeDraggable.keyPress(event);
|
||||
},
|
||||
|
||||
addObserver: function(observer) {
|
||||
this.observers.push(observer);
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
|
||||
removeObserver: function(element) { // element instead of observer fixes mem leaks
|
||||
this.observers = this.observers.reject( function(o) { return o.element==element });
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
|
||||
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
|
||||
if(this[eventName+'Count'] > 0)
|
||||
this.observers.each( function(o) {
|
||||
if(o[eventName]) o[eventName](eventName, draggable, event);
|
||||
});
|
||||
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
|
||||
},
|
||||
|
||||
_cacheObserverCallbacks: function() {
|
||||
['onStart','onEnd','onDrag'].each( function(eventName) {
|
||||
Draggables[eventName+'Count'] = Draggables.observers.select(
|
||||
function(o) { return o[eventName]; }
|
||||
).length;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Draggable = Class.create({
|
||||
initialize: function(element) {
|
||||
var defaults = {
|
||||
handle: false,
|
||||
reverteffect: function(element, top_offset, left_offset) {
|
||||
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
||||
new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
|
||||
queue: {scope:'_draggable', position:'end'}
|
||||
});
|
||||
},
|
||||
endeffect: function(element) {
|
||||
var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
|
||||
queue: {scope:'_draggable', position:'end'},
|
||||
afterFinish: function(){
|
||||
Draggable._dragging[element] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
zindex: 1000,
|
||||
revert: false,
|
||||
quiet: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||
delay: 0
|
||||
};
|
||||
|
||||
if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
|
||||
Object.extend(defaults, {
|
||||
starteffect: function(element) {
|
||||
element._opacity = Element.getOpacity(element);
|
||||
Draggable._dragging[element] = true;
|
||||
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
|
||||
}
|
||||
});
|
||||
|
||||
var options = Object.extend(defaults, arguments[1] || { });
|
||||
|
||||
this.element = $(element);
|
||||
|
||||
if(options.handle && Object.isString(options.handle))
|
||||
this.handle = this.element.down('.'+options.handle, 0);
|
||||
|
||||
if(!this.handle) this.handle = $(options.handle);
|
||||
if(!this.handle) this.handle = this.element;
|
||||
|
||||
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
|
||||
options.scroll = $(options.scroll);
|
||||
this._isScrollChild = Element.childOf(this.element, options.scroll);
|
||||
}
|
||||
|
||||
Element.makePositioned(this.element); // fix IE
|
||||
|
||||
this.options = options;
|
||||
this.dragging = false;
|
||||
|
||||
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
|
||||
Event.observe(this.handle, "mousedown", this.eventMouseDown);
|
||||
|
||||
Draggables.register(this);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
|
||||
Draggables.unregister(this);
|
||||
},
|
||||
|
||||
currentDelta: function() {
|
||||
return([
|
||||
parseInt(Element.getStyle(this.element,'left') || '0'),
|
||||
parseInt(Element.getStyle(this.element,'top') || '0')]);
|
||||
},
|
||||
|
||||
initDrag: function(event) {
|
||||
if(!Object.isUndefined(Draggable._dragging[this.element]) &&
|
||||
Draggable._dragging[this.element]) return;
|
||||
if(Event.isLeftClick(event)) {
|
||||
// abort on form elements, fixes a Firefox issue
|
||||
var src = Event.element(event);
|
||||
if((tag_name = src.tagName.toUpperCase()) && (
|
||||
tag_name=='INPUT' ||
|
||||
tag_name=='SELECT' ||
|
||||
tag_name=='OPTION' ||
|
||||
tag_name=='BUTTON' ||
|
||||
tag_name=='TEXTAREA')) return;
|
||||
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
|
||||
|
||||
Draggables.activate(this);
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
|
||||
startDrag: function(event) {
|
||||
this.dragging = true;
|
||||
if(!this.delta)
|
||||
this.delta = this.currentDelta();
|
||||
|
||||
if(this.options.zindex) {
|
||||
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
|
||||
this.element.style.zIndex = this.options.zindex;
|
||||
}
|
||||
|
||||
if(this.options.ghosting) {
|
||||
this._clone = this.element.cloneNode(true);
|
||||
this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
|
||||
if (!this.element._originallyAbsolute)
|
||||
Position.absolutize(this.element);
|
||||
this.element.parentNode.insertBefore(this._clone, this.element);
|
||||
}
|
||||
|
||||
if(this.options.scroll) {
|
||||
if (this.options.scroll == window) {
|
||||
var where = this._getWindowScroll(this.options.scroll);
|
||||
this.originalScrollLeft = where.left;
|
||||
this.originalScrollTop = where.top;
|
||||
} else {
|
||||
this.originalScrollLeft = this.options.scroll.scrollLeft;
|
||||
this.originalScrollTop = this.options.scroll.scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
Draggables.notify('onStart', this, event);
|
||||
|
||||
if(this.options.starteffect) this.options.starteffect(this.element);
|
||||
},
|
||||
|
||||
updateDrag: function(event, pointer) {
|
||||
if(!this.dragging) this.startDrag(event);
|
||||
|
||||
if(!this.options.quiet){
|
||||
Position.prepare();
|
||||
Droppables.show(pointer, this.element);
|
||||
}
|
||||
|
||||
Draggables.notify('onDrag', this, event);
|
||||
|
||||
this.draw(pointer);
|
||||
if(this.options.change) this.options.change(this);
|
||||
|
||||
if(this.options.scroll) {
|
||||
this.stopScrolling();
|
||||
|
||||
var p;
|
||||
if (this.options.scroll == window) {
|
||||
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
|
||||
} else {
|
||||
p = Position.page(this.options.scroll);
|
||||
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
|
||||
p[1] += this.options.scroll.scrollTop + Position.deltaY;
|
||||
p.push(p[0]+this.options.scroll.offsetWidth);
|
||||
p.push(p[1]+this.options.scroll.offsetHeight);
|
||||
}
|
||||
var speed = [0,0];
|
||||
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
|
||||
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
|
||||
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
|
||||
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
|
||||
this.startScrolling(speed);
|
||||
}
|
||||
|
||||
// fix AppleWebKit rendering
|
||||
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
|
||||
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
finishDrag: function(event, success) {
|
||||
this.dragging = false;
|
||||
|
||||
if(this.options.quiet){
|
||||
Position.prepare();
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
Droppables.show(pointer, this.element);
|
||||
}
|
||||
|
||||
if(this.options.ghosting) {
|
||||
if (!this.element._originallyAbsolute)
|
||||
Position.relativize(this.element);
|
||||
delete this.element._originallyAbsolute;
|
||||
Element.remove(this._clone);
|
||||
this._clone = null;
|
||||
}
|
||||
|
||||
var dropped = false;
|
||||
if(success) {
|
||||
dropped = Droppables.fire(event, this.element);
|
||||
if (!dropped) dropped = false;
|
||||
}
|
||||
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
|
||||
Draggables.notify('onEnd', this, event);
|
||||
|
||||
var revert = this.options.revert;
|
||||
if(revert && Object.isFunction(revert)) revert = revert(this.element);
|
||||
|
||||
var d = this.currentDelta();
|
||||
if(revert && this.options.reverteffect) {
|
||||
if (dropped == 0 || revert != 'failure')
|
||||
this.options.reverteffect(this.element,
|
||||
d[1]-this.delta[1], d[0]-this.delta[0]);
|
||||
} else {
|
||||
this.delta = d;
|
||||
}
|
||||
|
||||
if(this.options.zindex)
|
||||
this.element.style.zIndex = this.originalZ;
|
||||
|
||||
if(this.options.endeffect)
|
||||
this.options.endeffect(this.element);
|
||||
|
||||
Draggables.deactivate(this);
|
||||
Droppables.reset();
|
||||
},
|
||||
|
||||
keyPress: function(event) {
|
||||
if(event.keyCode!=Event.KEY_ESC) return;
|
||||
this.finishDrag(event, false);
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
endDrag: function(event) {
|
||||
if(!this.dragging) return;
|
||||
this.stopScrolling();
|
||||
this.finishDrag(event, true);
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
draw: function(point) {
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
if(this.options.ghosting) {
|
||||
var r = Position.realOffset(this.element);
|
||||
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
|
||||
}
|
||||
|
||||
var d = this.currentDelta();
|
||||
pos[0] -= d[0]; pos[1] -= d[1];
|
||||
|
||||
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
|
||||
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
|
||||
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
|
||||
}
|
||||
|
||||
var p = [0,1].map(function(i){
|
||||
return (point[i]-pos[i]-this.offset[i])
|
||||
}.bind(this));
|
||||
|
||||
if(this.options.snap) {
|
||||
if(Object.isFunction(this.options.snap)) {
|
||||
p = this.options.snap(p[0],p[1],this);
|
||||
} else {
|
||||
if(Object.isArray(this.options.snap)) {
|
||||
p = p.map( function(v, i) {
|
||||
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
|
||||
} else {
|
||||
p = p.map( function(v) {
|
||||
return (v/this.options.snap).round()*this.options.snap }.bind(this))
|
||||
}
|
||||
}}
|
||||
|
||||
var style = this.element.style;
|
||||
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
|
||||
style.left = p[0] + "px";
|
||||
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
||||
style.top = p[1] + "px";
|
||||
|
||||
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
||||
},
|
||||
|
||||
stopScrolling: function() {
|
||||
if(this.scrollInterval) {
|
||||
clearInterval(this.scrollInterval);
|
||||
this.scrollInterval = null;
|
||||
Draggables._lastScrollPointer = null;
|
||||
}
|
||||
},
|
||||
|
||||
startScrolling: function(speed) {
|
||||
if(!(speed[0] || speed[1])) return;
|
||||
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
|
||||
this.lastScrolled = new Date();
|
||||
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
|
||||
},
|
||||
|
||||
scroll: function() {
|
||||
var current = new Date();
|
||||
var delta = current - this.lastScrolled;
|
||||
this.lastScrolled = current;
|
||||
if(this.options.scroll == window) {
|
||||
with (this._getWindowScroll(this.options.scroll)) {
|
||||
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
|
||||
var d = delta / 1000;
|
||||
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
|
||||
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
|
||||
}
|
||||
|
||||
Position.prepare();
|
||||
Droppables.show(Draggables._lastPointer, this.element);
|
||||
Draggables.notify('onDrag', this);
|
||||
if (this._isScrollChild) {
|
||||
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
|
||||
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
|
||||
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
|
||||
if (Draggables._lastScrollPointer[0] < 0)
|
||||
Draggables._lastScrollPointer[0] = 0;
|
||||
if (Draggables._lastScrollPointer[1] < 0)
|
||||
Draggables._lastScrollPointer[1] = 0;
|
||||
this.draw(Draggables._lastScrollPointer);
|
||||
}
|
||||
|
||||
if(this.options.change) this.options.change(this);
|
||||
},
|
||||
|
||||
_getWindowScroll: function(w) {
|
||||
var T, L, W, H;
|
||||
with (w.document) {
|
||||
if (w.document.documentElement && documentElement.scrollTop) {
|
||||
T = documentElement.scrollTop;
|
||||
L = documentElement.scrollLeft;
|
||||
} else if (w.document.body) {
|
||||
T = body.scrollTop;
|
||||
L = body.scrollLeft;
|
||||
}
|
||||
if (w.innerWidth) {
|
||||
W = w.innerWidth;
|
||||
H = w.innerHeight;
|
||||
} else if (w.document.documentElement && documentElement.clientWidth) {
|
||||
W = documentElement.clientWidth;
|
||||
H = documentElement.clientHeight;
|
||||
} else {
|
||||
W = body.offsetWidth;
|
||||
H = body.offsetHeight
|
||||
}
|
||||
}
|
||||
return { top: T, left: L, width: W, height: H };
|
||||
}
|
||||
});
|
||||
|
||||
Draggable._dragging = { };
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var SortableObserver = Class.create({
|
||||
initialize: function(element, observer) {
|
||||
this.element = $(element);
|
||||
this.observer = observer;
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
|
||||
onStart: function() {
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
|
||||
onEnd: function() {
|
||||
Sortable.unmark();
|
||||
if(this.lastValue != Sortable.serialize(this.element))
|
||||
this.observer(this.element)
|
||||
}
|
||||
});
|
||||
|
||||
var Sortable = {
|
||||
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
|
||||
|
||||
sortables: { },
|
||||
|
||||
_findRootElement: function(element) {
|
||||
while (element.tagName.toUpperCase() != "BODY") {
|
||||
if(element.id && Sortable.sortables[element.id]) return element;
|
||||
element = element.parentNode;
|
||||
}
|
||||
},
|
||||
|
||||
options: function(element) {
|
||||
element = Sortable._findRootElement($(element));
|
||||
if(!element) return;
|
||||
return Sortable.sortables[element.id];
|
||||
},
|
||||
|
||||
destroy: function(element){
|
||||
var s = Sortable.options(element);
|
||||
|
||||
if(s) {
|
||||
Draggables.removeObserver(s.element);
|
||||
s.droppables.each(function(d){ Droppables.remove(d) });
|
||||
s.draggables.invoke('destroy');
|
||||
|
||||
delete Sortable.sortables[s.element.id];
|
||||
}
|
||||
},
|
||||
|
||||
create: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
element: element,
|
||||
tag: 'li', // assumes li children, override with tag: 'tagname'
|
||||
dropOnEmpty: false,
|
||||
tree: false,
|
||||
treeTag: 'ul',
|
||||
overlap: 'vertical', // one of 'vertical', 'horizontal'
|
||||
constraint: 'vertical', // one of 'vertical', 'horizontal', false
|
||||
containment: element, // also takes array of elements (or id's); or false
|
||||
handle: false, // or a CSS class
|
||||
only: false,
|
||||
delay: 0,
|
||||
hoverclass: null,
|
||||
ghosting: false,
|
||||
quiet: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
format: this.SERIALIZE_RULE,
|
||||
|
||||
// these take arrays of elements or ids and can be
|
||||
// used for better initialization performance
|
||||
elements: false,
|
||||
handles: false,
|
||||
|
||||
onChange: Prototype.emptyFunction,
|
||||
onUpdate: Prototype.emptyFunction
|
||||
}, arguments[1] || { });
|
||||
|
||||
// clear any old sortable with same element
|
||||
this.destroy(element);
|
||||
|
||||
// build options for the draggables
|
||||
var options_for_draggable = {
|
||||
revert: true,
|
||||
quiet: options.quiet,
|
||||
scroll: options.scroll,
|
||||
scrollSpeed: options.scrollSpeed,
|
||||
scrollSensitivity: options.scrollSensitivity,
|
||||
delay: options.delay,
|
||||
ghosting: options.ghosting,
|
||||
constraint: options.constraint,
|
||||
handle: options.handle };
|
||||
|
||||
if(options.starteffect)
|
||||
options_for_draggable.starteffect = options.starteffect;
|
||||
|
||||
if(options.reverteffect)
|
||||
options_for_draggable.reverteffect = options.reverteffect;
|
||||
else
|
||||
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
|
||||
element.style.top = 0;
|
||||
element.style.left = 0;
|
||||
};
|
||||
|
||||
if(options.endeffect)
|
||||
options_for_draggable.endeffect = options.endeffect;
|
||||
|
||||
if(options.zindex)
|
||||
options_for_draggable.zindex = options.zindex;
|
||||
|
||||
// build options for the droppables
|
||||
var options_for_droppable = {
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
tree: options.tree,
|
||||
hoverclass: options.hoverclass,
|
||||
onHover: Sortable.onHover
|
||||
}
|
||||
|
||||
var options_for_tree = {
|
||||
onHover: Sortable.onEmptyHover,
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
hoverclass: options.hoverclass
|
||||
}
|
||||
|
||||
// fix for gecko engine
|
||||
Element.cleanWhitespace(element);
|
||||
|
||||
options.draggables = [];
|
||||
options.droppables = [];
|
||||
|
||||
// drop on empty handling
|
||||
if(options.dropOnEmpty || options.tree) {
|
||||
Droppables.add(element, options_for_tree);
|
||||
options.droppables.push(element);
|
||||
}
|
||||
|
||||
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
|
||||
var handle = options.handles ? $(options.handles[i]) :
|
||||
(options.handle ? $(e).select('.' + options.handle)[0] : e);
|
||||
options.draggables.push(
|
||||
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
|
||||
Droppables.add(e, options_for_droppable);
|
||||
if(options.tree) e.treeNode = element;
|
||||
options.droppables.push(e);
|
||||
});
|
||||
|
||||
if(options.tree) {
|
||||
(Sortable.findTreeElements(element, options) || []).each( function(e) {
|
||||
Droppables.add(e, options_for_tree);
|
||||
e.treeNode = element;
|
||||
options.droppables.push(e);
|
||||
});
|
||||
}
|
||||
|
||||
// keep reference
|
||||
this.sortables[element.id] = options;
|
||||
|
||||
// for onupdate
|
||||
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
|
||||
|
||||
},
|
||||
|
||||
// return all suitable-for-sortable elements in a guaranteed order
|
||||
findElements: function(element, options) {
|
||||
return Element.findChildren(
|
||||
element, options.only, options.tree ? true : false, options.tag);
|
||||
},
|
||||
|
||||
findTreeElements: function(element, options) {
|
||||
return Element.findChildren(
|
||||
element, options.only, options.tree ? true : false, options.treeTag);
|
||||
},
|
||||
|
||||
onHover: function(element, dropon, overlap) {
|
||||
if(Element.isParent(dropon, element)) return;
|
||||
|
||||
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
|
||||
return;
|
||||
} else if(overlap>0.5) {
|
||||
Sortable.mark(dropon, 'before');
|
||||
if(dropon.previousSibling != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, dropon);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
} else {
|
||||
Sortable.mark(dropon, 'after');
|
||||
var nextElement = dropon.nextSibling || null;
|
||||
if(nextElement != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, nextElement);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onEmptyHover: function(element, dropon, overlap) {
|
||||
var oldParentNode = element.parentNode;
|
||||
var droponOptions = Sortable.options(dropon);
|
||||
|
||||
if(!Element.isParent(dropon, element)) {
|
||||
var index;
|
||||
|
||||
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
|
||||
var child = null;
|
||||
|
||||
if(children) {
|
||||
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
|
||||
|
||||
for (index = 0; index < children.length; index += 1) {
|
||||
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
|
||||
offset -= Element.offsetSize (children[index], droponOptions.overlap);
|
||||
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
|
||||
child = index + 1 < children.length ? children[index + 1] : null;
|
||||
break;
|
||||
} else {
|
||||
child = children[index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dropon.insertBefore(element, child);
|
||||
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
droponOptions.onChange(element);
|
||||
}
|
||||
},
|
||||
|
||||
unmark: function() {
|
||||
if(Sortable._marker) Sortable._marker.hide();
|
||||
},
|
||||
|
||||
mark: function(dropon, position) {
|
||||
// mark on ghosting only
|
||||
var sortable = Sortable.options(dropon.parentNode);
|
||||
if(sortable && !sortable.ghosting) return;
|
||||
|
||||
if(!Sortable._marker) {
|
||||
Sortable._marker =
|
||||
($('dropmarker') || Element.extend(document.createElement('DIV'))).
|
||||
hide().addClassName('dropmarker').setStyle({position:'absolute'});
|
||||
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||
}
|
||||
var offsets = Position.cumulativeOffset(dropon);
|
||||
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
|
||||
|
||||
if(position=='after')
|
||||
if(sortable.overlap == 'horizontal')
|
||||
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
|
||||
else
|
||||
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
|
||||
|
||||
Sortable._marker.show();
|
||||
},
|
||||
|
||||
_tree: function(element, options, parent) {
|
||||
var children = Sortable.findElements(element, options) || [];
|
||||
|
||||
for (var i = 0; i < children.length; ++i) {
|
||||
var match = children[i].id.match(options.format);
|
||||
|
||||
if (!match) continue;
|
||||
|
||||
var child = {
|
||||
id: encodeURIComponent(match ? match[1] : null),
|
||||
element: element,
|
||||
parent: parent,
|
||||
children: [],
|
||||
position: parent.children.length,
|
||||
container: $(children[i]).down(options.treeTag)
|
||||
}
|
||||
|
||||
/* Get the element containing the children and recurse over it */
|
||||
if (child.container)
|
||||
this._tree(child.container, options, child)
|
||||
|
||||
parent.children.push (child);
|
||||
}
|
||||
|
||||
return parent;
|
||||
},
|
||||
|
||||
tree: function(element) {
|
||||
element = $(element);
|
||||
var sortableOptions = this.options(element);
|
||||
var options = Object.extend({
|
||||
tag: sortableOptions.tag,
|
||||
treeTag: sortableOptions.treeTag,
|
||||
only: sortableOptions.only,
|
||||
name: element.id,
|
||||
format: sortableOptions.format
|
||||
}, arguments[1] || { });
|
||||
|
||||
var root = {
|
||||
id: null,
|
||||
parent: null,
|
||||
children: [],
|
||||
container: element,
|
||||
position: 0
|
||||
}
|
||||
|
||||
return Sortable._tree(element, options, root);
|
||||
},
|
||||
|
||||
/* Construct a [i] index for a particular node */
|
||||
_constructIndex: function(node) {
|
||||
var index = '';
|
||||
do {
|
||||
if (node.id) index = '[' + node.position + ']' + index;
|
||||
} while ((node = node.parent) != null);
|
||||
return index;
|
||||
},
|
||||
|
||||
sequence: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend(this.options(element), arguments[1] || { });
|
||||
|
||||
return $(this.findElements(element, options) || []).map( function(item) {
|
||||
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
|
||||
});
|
||||
},
|
||||
|
||||
setSequence: function(element, new_sequence) {
|
||||
element = $(element);
|
||||
var options = Object.extend(this.options(element), arguments[2] || { });
|
||||
|
||||
var nodeMap = { };
|
||||
this.findElements(element, options).each( function(n) {
|
||||
if (n.id.match(options.format))
|
||||
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
|
||||
n.parentNode.removeChild(n);
|
||||
});
|
||||
|
||||
new_sequence.each(function(ident) {
|
||||
var n = nodeMap[ident];
|
||||
if (n) {
|
||||
n[1].appendChild(n[0]);
|
||||
delete nodeMap[ident];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
serialize: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend(Sortable.options(element), arguments[1] || { });
|
||||
var name = encodeURIComponent(
|
||||
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
|
||||
|
||||
if (options.tree) {
|
||||
return Sortable.tree(element, arguments[1]).children.map( function (item) {
|
||||
return [name + Sortable._constructIndex(item) + "[id]=" +
|
||||
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
|
||||
}).flatten().join('&');
|
||||
} else {
|
||||
return Sortable.sequence(element, arguments[1]).map( function(item) {
|
||||
return name + "[]=" + encodeURIComponent(item);
|
||||
}).join('&');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if child is contained within element
|
||||
Element.isParent = function(child, element) {
|
||||
if (!child.parentNode || child == element) return false;
|
||||
if (child.parentNode == element) return true;
|
||||
return Element.isParent(child.parentNode, element);
|
||||
}
|
||||
|
||||
Element.findChildren = function(element, only, recursive, tagName) {
|
||||
if(!element.hasChildNodes()) return null;
|
||||
tagName = tagName.toUpperCase();
|
||||
if(only) only = [only].flatten();
|
||||
var elements = [];
|
||||
$A(element.childNodes).each( function(e) {
|
||||
if(e.tagName && e.tagName.toUpperCase()==tagName &&
|
||||
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
|
||||
elements.push(e);
|
||||
if(recursive) {
|
||||
var grandchildren = Element.findChildren(e, only, recursive, tagName);
|
||||
if(grandchildren) elements.push(grandchildren);
|
||||
}
|
||||
});
|
||||
|
||||
return (elements.length>0 ? elements.flatten() : []);
|
||||
}
|
||||
|
||||
Element.offsetSize = function (element, type) {
|
||||
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
|
||||
}
|
1122
bbb-lti/web-app/js/prototype/effects.js
vendored
Normal file
4184
bbb-lti/web-app/js/prototype/prototype.js
vendored
Normal file
2691
bbb-lti/web-app/js/prototype/rico.js
vendored
Normal file
58
bbb-lti/web-app/js/prototype/scriptaculous.js
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
// script.aculo.us scriptaculous.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
|
||||
|
||||
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
var Scriptaculous = {
|
||||
Version: '1.8.0',
|
||||
require: function(libraryName) {
|
||||
// inserting via DOM fails in Safari 2.0, so brute force approach
|
||||
document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
|
||||
},
|
||||
REQUIRED_PROTOTYPE: '1.6.0',
|
||||
load: function() {
|
||||
function convertVersionString(versionString){
|
||||
var r = versionString.split('.');
|
||||
return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
|
||||
}
|
||||
|
||||
if((typeof Prototype=='undefined') ||
|
||||
(typeof Element == 'undefined') ||
|
||||
(typeof Element.Methods=='undefined') ||
|
||||
(convertVersionString(Prototype.Version) <
|
||||
convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
|
||||
throw("script.aculo.us requires the Prototype JavaScript framework >= " +
|
||||
Scriptaculous.REQUIRED_PROTOTYPE);
|
||||
|
||||
$A(document.getElementsByTagName("script")).findAll( function(s) {
|
||||
return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
|
||||
}).each( function(s) {
|
||||
var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
|
||||
var includes = s.src.match(/\?.*load=([a-z,]*)/);
|
||||
(includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
|
||||
function(include) { Scriptaculous.require(path+include+'.js') });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Scriptaculous.load();
|
275
bbb-lti/web-app/js/prototype/slider.js
vendored
Normal file
@ -0,0 +1,275 @@
|
||||
// script.aculo.us slider.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
|
||||
|
||||
// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
if (!Control) var Control = { };
|
||||
|
||||
// options:
|
||||
// axis: 'vertical', or 'horizontal' (default)
|
||||
//
|
||||
// callbacks:
|
||||
// onChange(value)
|
||||
// onSlide(value)
|
||||
Control.Slider = Class.create({
|
||||
initialize: function(handle, track, options) {
|
||||
var slider = this;
|
||||
|
||||
if (Object.isArray(handle)) {
|
||||
this.handles = handle.collect( function(e) { return $(e) });
|
||||
} else {
|
||||
this.handles = [$(handle)];
|
||||
}
|
||||
|
||||
this.track = $(track);
|
||||
this.options = options || { };
|
||||
|
||||
this.axis = this.options.axis || 'horizontal';
|
||||
this.increment = this.options.increment || 1;
|
||||
this.step = parseInt(this.options.step || '1');
|
||||
this.range = this.options.range || $R(0,1);
|
||||
|
||||
this.value = 0; // assure backwards compat
|
||||
this.values = this.handles.map( function() { return 0 });
|
||||
this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
|
||||
this.options.startSpan = $(this.options.startSpan || null);
|
||||
this.options.endSpan = $(this.options.endSpan || null);
|
||||
|
||||
this.restricted = this.options.restricted || false;
|
||||
|
||||
this.maximum = this.options.maximum || this.range.end;
|
||||
this.minimum = this.options.minimum || this.range.start;
|
||||
|
||||
// Will be used to align the handle onto the track, if necessary
|
||||
this.alignX = parseInt(this.options.alignX || '0');
|
||||
this.alignY = parseInt(this.options.alignY || '0');
|
||||
|
||||
this.trackLength = this.maximumOffset() - this.minimumOffset();
|
||||
|
||||
this.handleLength = this.isVertical() ?
|
||||
(this.handles[0].offsetHeight != 0 ?
|
||||
this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
|
||||
(this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
|
||||
this.handles[0].style.width.replace(/px$/,""));
|
||||
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
this.disabled = false;
|
||||
|
||||
if (this.options.disabled) this.setDisabled();
|
||||
|
||||
// Allowed values array
|
||||
this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
|
||||
if (this.allowedValues) {
|
||||
this.minimum = this.allowedValues.min();
|
||||
this.maximum = this.allowedValues.max();
|
||||
}
|
||||
|
||||
this.eventMouseDown = this.startDrag.bindAsEventListener(this);
|
||||
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
||||
this.eventMouseMove = this.update.bindAsEventListener(this);
|
||||
|
||||
// Initialize handles in reverse (make sure first handle is active)
|
||||
this.handles.each( function(h,i) {
|
||||
i = slider.handles.length-1-i;
|
||||
slider.setValue(parseFloat(
|
||||
(Object.isArray(slider.options.sliderValue) ?
|
||||
slider.options.sliderValue[i] : slider.options.sliderValue) ||
|
||||
slider.range.start), i);
|
||||
h.makePositioned().observe("mousedown", slider.eventMouseDown);
|
||||
});
|
||||
|
||||
this.track.observe("mousedown", this.eventMouseDown);
|
||||
document.observe("mouseup", this.eventMouseUp);
|
||||
document.observe("mousemove", this.eventMouseMove);
|
||||
|
||||
this.initialized = true;
|
||||
},
|
||||
dispose: function() {
|
||||
var slider = this;
|
||||
Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
|
||||
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
||||
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
||||
this.handles.each( function(h) {
|
||||
Event.stopObserving(h, "mousedown", slider.eventMouseDown);
|
||||
});
|
||||
},
|
||||
setDisabled: function(){
|
||||
this.disabled = true;
|
||||
},
|
||||
setEnabled: function(){
|
||||
this.disabled = false;
|
||||
},
|
||||
getNearestValue: function(value){
|
||||
if (this.allowedValues){
|
||||
if (value >= this.allowedValues.max()) return(this.allowedValues.max());
|
||||
if (value <= this.allowedValues.min()) return(this.allowedValues.min());
|
||||
|
||||
var offset = Math.abs(this.allowedValues[0] - value);
|
||||
var newValue = this.allowedValues[0];
|
||||
this.allowedValues.each( function(v) {
|
||||
var currentOffset = Math.abs(v - value);
|
||||
if (currentOffset <= offset){
|
||||
newValue = v;
|
||||
offset = currentOffset;
|
||||
}
|
||||
});
|
||||
return newValue;
|
||||
}
|
||||
if (value > this.range.end) return this.range.end;
|
||||
if (value < this.range.start) return this.range.start;
|
||||
return value;
|
||||
},
|
||||
setValue: function(sliderValue, handleIdx){
|
||||
if (!this.active) {
|
||||
this.activeHandleIdx = handleIdx || 0;
|
||||
this.activeHandle = this.handles[this.activeHandleIdx];
|
||||
this.updateStyles();
|
||||
}
|
||||
handleIdx = handleIdx || this.activeHandleIdx || 0;
|
||||
if (this.initialized && this.restricted) {
|
||||
if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
|
||||
sliderValue = this.values[handleIdx-1];
|
||||
if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
|
||||
sliderValue = this.values[handleIdx+1];
|
||||
}
|
||||
sliderValue = this.getNearestValue(sliderValue);
|
||||
this.values[handleIdx] = sliderValue;
|
||||
this.value = this.values[0]; // assure backwards compat
|
||||
|
||||
this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
|
||||
this.translateToPx(sliderValue);
|
||||
|
||||
this.drawSpans();
|
||||
if (!this.dragging || !this.event) this.updateFinished();
|
||||
},
|
||||
setValueBy: function(delta, handleIdx) {
|
||||
this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
|
||||
handleIdx || this.activeHandleIdx || 0);
|
||||
},
|
||||
translateToPx: function(value) {
|
||||
return Math.round(
|
||||
((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
|
||||
(value - this.range.start)) + "px";
|
||||
},
|
||||
translateToValue: function(offset) {
|
||||
return ((offset/(this.trackLength-this.handleLength) *
|
||||
(this.range.end-this.range.start)) + this.range.start);
|
||||
},
|
||||
getRange: function(range) {
|
||||
var v = this.values.sortBy(Prototype.K);
|
||||
range = range || 0;
|
||||
return $R(v[range],v[range+1]);
|
||||
},
|
||||
minimumOffset: function(){
|
||||
return(this.isVertical() ? this.alignY : this.alignX);
|
||||
},
|
||||
maximumOffset: function(){
|
||||
return(this.isVertical() ?
|
||||
(this.track.offsetHeight != 0 ? this.track.offsetHeight :
|
||||
this.track.style.height.replace(/px$/,"")) - this.alignY :
|
||||
(this.track.offsetWidth != 0 ? this.track.offsetWidth :
|
||||
this.track.style.width.replace(/px$/,"")) - this.alignX);
|
||||
},
|
||||
isVertical: function(){
|
||||
return (this.axis == 'vertical');
|
||||
},
|
||||
drawSpans: function() {
|
||||
var slider = this;
|
||||
if (this.spans)
|
||||
$R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
|
||||
if (this.options.startSpan)
|
||||
this.setSpan(this.options.startSpan,
|
||||
$R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
|
||||
if (this.options.endSpan)
|
||||
this.setSpan(this.options.endSpan,
|
||||
$R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
|
||||
},
|
||||
setSpan: function(span, range) {
|
||||
if (this.isVertical()) {
|
||||
span.style.top = this.translateToPx(range.start);
|
||||
span.style.height = this.translateToPx(range.end - range.start + this.range.start);
|
||||
} else {
|
||||
span.style.left = this.translateToPx(range.start);
|
||||
span.style.width = this.translateToPx(range.end - range.start + this.range.start);
|
||||
}
|
||||
},
|
||||
updateStyles: function() {
|
||||
this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
|
||||
Element.addClassName(this.activeHandle, 'selected');
|
||||
},
|
||||
startDrag: function(event) {
|
||||
if (Event.isLeftClick(event)) {
|
||||
if (!this.disabled){
|
||||
this.active = true;
|
||||
|
||||
var handle = Event.element(event);
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var track = handle;
|
||||
if (track==this.track) {
|
||||
var offsets = Position.cumulativeOffset(this.track);
|
||||
this.event = event;
|
||||
this.setValue(this.translateToValue(
|
||||
(this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
|
||||
));
|
||||
var offsets = Position.cumulativeOffset(this.activeHandle);
|
||||
this.offsetX = (pointer[0] - offsets[0]);
|
||||
this.offsetY = (pointer[1] - offsets[1]);
|
||||
} else {
|
||||
// find the handle (prevents issues with Safari)
|
||||
while((this.handles.indexOf(handle) == -1) && handle.parentNode)
|
||||
handle = handle.parentNode;
|
||||
|
||||
if (this.handles.indexOf(handle)!=-1) {
|
||||
this.activeHandle = handle;
|
||||
this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
|
||||
this.updateStyles();
|
||||
|
||||
var offsets = Position.cumulativeOffset(this.activeHandle);
|
||||
this.offsetX = (pointer[0] - offsets[0]);
|
||||
this.offsetY = (pointer[1] - offsets[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
update: function(event) {
|
||||
if (this.active) {
|
||||
if (!this.dragging) this.dragging = true;
|
||||
this.draw(event);
|
||||
if (Prototype.Browser.WebKit) window.scrollBy(0,0);
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
draw: function(event) {
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var offsets = Position.cumulativeOffset(this.track);
|
||||
pointer[0] -= this.offsetX + offsets[0];
|
||||
pointer[1] -= this.offsetY + offsets[1];
|
||||
this.event = event;
|
||||
this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
|
||||
if (this.initialized && this.options.onSlide)
|
||||
this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
|
||||
},
|
||||
endDrag: function(event) {
|
||||
if (this.active && this.dragging) {
|
||||
this.finishDrag(event, true);
|
||||
Event.stop(event);
|
||||
}
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
},
|
||||
finishDrag: function(event, success) {
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
this.updateFinished();
|
||||
},
|
||||
updateFinished: function() {
|
||||
if (this.initialized && this.options.onChange)
|
||||
this.options.onChange(this.values.length>1 ? this.values : this.value, this);
|
||||
this.event = null;
|
||||
}
|
||||
});
|
55
bbb-lti/web-app/js/prototype/sound.js
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
// script.aculo.us sound.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
|
||||
|
||||
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
//
|
||||
// Based on code created by Jules Gravinese (http://www.webveteran.com/)
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
Sound = {
|
||||
tracks: {},
|
||||
_enabled: true,
|
||||
template:
|
||||
new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
|
||||
enable: function(){
|
||||
Sound._enabled = true;
|
||||
},
|
||||
disable: function(){
|
||||
Sound._enabled = false;
|
||||
},
|
||||
play: function(url){
|
||||
if(!Sound._enabled) return;
|
||||
var options = Object.extend({
|
||||
track: 'global', url: url, replace: false
|
||||
}, arguments[1] || {});
|
||||
|
||||
if(options.replace && this.tracks[options.track]) {
|
||||
$R(0, this.tracks[options.track].id).each(function(id){
|
||||
var sound = $('sound_'+options.track+'_'+id);
|
||||
sound.Stop && sound.Stop();
|
||||
sound.remove();
|
||||
})
|
||||
this.tracks[options.track] = null;
|
||||
}
|
||||
|
||||
if(!this.tracks[options.track])
|
||||
this.tracks[options.track] = { id: 0 }
|
||||
else
|
||||
this.tracks[options.track].id++;
|
||||
|
||||
options.id = this.tracks[options.track].id;
|
||||
$$('body')[0].insert(
|
||||
Prototype.Browser.IE ? new Element('bgsound',{
|
||||
id: 'sound_'+options.track+'_'+options.id,
|
||||
src: options.url, loop: 1, autostart: true
|
||||
}) : Sound.template.evaluate(options));
|
||||
}
|
||||
};
|
||||
|
||||
if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
|
||||
if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
|
||||
Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
|
||||
else
|
||||
Sound.play = function(){}
|
||||
}
|
568
bbb-lti/web-app/js/prototype/unittest.js
vendored
Normal file
@ -0,0 +1,568 @@
|
||||
// script.aculo.us unittest.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
|
||||
|
||||
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
|
||||
// (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
// experimental, Firefox-only
|
||||
Event.simulateMouse = function(element, eventName) {
|
||||
var options = Object.extend({
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
buttons: 0,
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
metaKey: false
|
||||
}, arguments[2] || {});
|
||||
var oEvent = document.createEvent("MouseEvents");
|
||||
oEvent.initMouseEvent(eventName, true, true, document.defaultView,
|
||||
options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
|
||||
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
|
||||
|
||||
if(this.mark) Element.remove(this.mark);
|
||||
this.mark = document.createElement('div');
|
||||
this.mark.appendChild(document.createTextNode(" "));
|
||||
document.body.appendChild(this.mark);
|
||||
this.mark.style.position = 'absolute';
|
||||
this.mark.style.top = options.pointerY + "px";
|
||||
this.mark.style.left = options.pointerX + "px";
|
||||
this.mark.style.width = "5px";
|
||||
this.mark.style.height = "5px;";
|
||||
this.mark.style.borderTop = "1px solid red;"
|
||||
this.mark.style.borderLeft = "1px solid red;"
|
||||
|
||||
if(this.step)
|
||||
alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
|
||||
|
||||
$(element).dispatchEvent(oEvent);
|
||||
};
|
||||
|
||||
// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
|
||||
// You need to downgrade to 1.0.4 for now to get this working
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
|
||||
Event.simulateKey = function(element, eventName) {
|
||||
var options = Object.extend({
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
metaKey: false,
|
||||
keyCode: 0,
|
||||
charCode: 0
|
||||
}, arguments[2] || {});
|
||||
|
||||
var oEvent = document.createEvent("KeyEvents");
|
||||
oEvent.initKeyEvent(eventName, true, true, window,
|
||||
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
|
||||
options.keyCode, options.charCode );
|
||||
$(element).dispatchEvent(oEvent);
|
||||
};
|
||||
|
||||
Event.simulateKeys = function(element, command) {
|
||||
for(var i=0; i<command.length; i++) {
|
||||
Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
|
||||
}
|
||||
};
|
||||
|
||||
var Test = {}
|
||||
Test.Unit = {};
|
||||
|
||||
// security exception workaround
|
||||
Test.Unit.inspect = Object.inspect;
|
||||
|
||||
Test.Unit.Logger = Class.create();
|
||||
Test.Unit.Logger.prototype = {
|
||||
initialize: function(log) {
|
||||
this.log = $(log);
|
||||
if (this.log) {
|
||||
this._createLogTable();
|
||||
}
|
||||
},
|
||||
start: function(testName) {
|
||||
if (!this.log) return;
|
||||
this.testName = testName;
|
||||
this.lastLogLine = document.createElement('tr');
|
||||
this.statusCell = document.createElement('td');
|
||||
this.nameCell = document.createElement('td');
|
||||
this.nameCell.className = "nameCell";
|
||||
this.nameCell.appendChild(document.createTextNode(testName));
|
||||
this.messageCell = document.createElement('td');
|
||||
this.lastLogLine.appendChild(this.statusCell);
|
||||
this.lastLogLine.appendChild(this.nameCell);
|
||||
this.lastLogLine.appendChild(this.messageCell);
|
||||
this.loglines.appendChild(this.lastLogLine);
|
||||
},
|
||||
finish: function(status, summary) {
|
||||
if (!this.log) return;
|
||||
this.lastLogLine.className = status;
|
||||
this.statusCell.innerHTML = status;
|
||||
this.messageCell.innerHTML = this._toHTML(summary);
|
||||
this.addLinksToResults();
|
||||
},
|
||||
message: function(message) {
|
||||
if (!this.log) return;
|
||||
this.messageCell.innerHTML = this._toHTML(message);
|
||||
},
|
||||
summary: function(summary) {
|
||||
if (!this.log) return;
|
||||
this.logsummary.innerHTML = this._toHTML(summary);
|
||||
},
|
||||
_createLogTable: function() {
|
||||
this.log.innerHTML =
|
||||
'<div id="logsummary"></div>' +
|
||||
'<table id="logtable">' +
|
||||
'<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
|
||||
'<tbody id="loglines"></tbody>' +
|
||||
'</table>';
|
||||
this.logsummary = $('logsummary')
|
||||
this.loglines = $('loglines');
|
||||
},
|
||||
_toHTML: function(txt) {
|
||||
return txt.escapeHTML().replace(/\n/g,"<br/>");
|
||||
},
|
||||
addLinksToResults: function(){
|
||||
$$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
|
||||
td.title = "Run only this test"
|
||||
Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
|
||||
});
|
||||
$$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
|
||||
td.title = "Run all tests"
|
||||
Event.observe(td, 'click', function(){ window.location.search = "";});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Test.Unit.Runner = Class.create();
|
||||
Test.Unit.Runner.prototype = {
|
||||
initialize: function(testcases) {
|
||||
this.options = Object.extend({
|
||||
testLog: 'testlog'
|
||||
}, arguments[1] || {});
|
||||
this.options.resultsURL = this.parseResultsURLQueryParameter();
|
||||
this.options.tests = this.parseTestsQueryParameter();
|
||||
if (this.options.testLog) {
|
||||
this.options.testLog = $(this.options.testLog) || null;
|
||||
}
|
||||
if(this.options.tests) {
|
||||
this.tests = [];
|
||||
for(var i = 0; i < this.options.tests.length; i++) {
|
||||
if(/^test/.test(this.options.tests[i])) {
|
||||
this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.options.test) {
|
||||
this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
|
||||
} else {
|
||||
this.tests = [];
|
||||
for(var testcase in testcases) {
|
||||
if(/^test/.test(testcase)) {
|
||||
this.tests.push(
|
||||
new Test.Unit.Testcase(
|
||||
this.options.context ? ' -> ' + this.options.titles[testcase] : testcase,
|
||||
testcases[testcase], testcases["setup"], testcases["teardown"]
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.currentTest = 0;
|
||||
this.logger = new Test.Unit.Logger(this.options.testLog);
|
||||
setTimeout(this.runTests.bind(this), 1000);
|
||||
},
|
||||
parseResultsURLQueryParameter: function() {
|
||||
return window.location.search.parseQuery()["resultsURL"];
|
||||
},
|
||||
parseTestsQueryParameter: function(){
|
||||
if (window.location.search.parseQuery()["tests"]){
|
||||
return window.location.search.parseQuery()["tests"].split(',');
|
||||
};
|
||||
},
|
||||
// Returns:
|
||||
// "ERROR" if there was an error,
|
||||
// "FAILURE" if there was a failure, or
|
||||
// "SUCCESS" if there was neither
|
||||
getResult: function() {
|
||||
var hasFailure = false;
|
||||
for(var i=0;i<this.tests.length;i++) {
|
||||
if (this.tests[i].errors > 0) {
|
||||
return "ERROR";
|
||||
}
|
||||
if (this.tests[i].failures > 0) {
|
||||
hasFailure = true;
|
||||
}
|
||||
}
|
||||
if (hasFailure) {
|
||||
return "FAILURE";
|
||||
} else {
|
||||
return "SUCCESS";
|
||||
}
|
||||
},
|
||||
postResults: function() {
|
||||
if (this.options.resultsURL) {
|
||||
new Ajax.Request(this.options.resultsURL,
|
||||
{ method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
|
||||
}
|
||||
},
|
||||
runTests: function() {
|
||||
var test = this.tests[this.currentTest];
|
||||
if (!test) {
|
||||
// finished!
|
||||
this.postResults();
|
||||
this.logger.summary(this.summary());
|
||||
return;
|
||||
}
|
||||
if(!test.isWaiting) {
|
||||
this.logger.start(test.name);
|
||||
}
|
||||
test.run();
|
||||
if(test.isWaiting) {
|
||||
this.logger.message("Waiting for " + test.timeToWait + "ms");
|
||||
setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
|
||||
} else {
|
||||
this.logger.finish(test.status(), test.summary());
|
||||
this.currentTest++;
|
||||
// tail recursive, hopefully the browser will skip the stackframe
|
||||
this.runTests();
|
||||
}
|
||||
},
|
||||
summary: function() {
|
||||
var assertions = 0;
|
||||
var failures = 0;
|
||||
var errors = 0;
|
||||
var messages = [];
|
||||
for(var i=0;i<this.tests.length;i++) {
|
||||
assertions += this.tests[i].assertions;
|
||||
failures += this.tests[i].failures;
|
||||
errors += this.tests[i].errors;
|
||||
}
|
||||
return (
|
||||
(this.options.context ? this.options.context + ': ': '') +
|
||||
this.tests.length + " tests, " +
|
||||
assertions + " assertions, " +
|
||||
failures + " failures, " +
|
||||
errors + " errors");
|
||||
}
|
||||
}
|
||||
|
||||
Test.Unit.Assertions = Class.create();
|
||||
Test.Unit.Assertions.prototype = {
|
||||
initialize: function() {
|
||||
this.assertions = 0;
|
||||
this.failures = 0;
|
||||
this.errors = 0;
|
||||
this.messages = [];
|
||||
},
|
||||
summary: function() {
|
||||
return (
|
||||
this.assertions + " assertions, " +
|
||||
this.failures + " failures, " +
|
||||
this.errors + " errors" + "\n" +
|
||||
this.messages.join("\n"));
|
||||
},
|
||||
pass: function() {
|
||||
this.assertions++;
|
||||
},
|
||||
fail: function(message) {
|
||||
this.failures++;
|
||||
this.messages.push("Failure: " + message);
|
||||
},
|
||||
info: function(message) {
|
||||
this.messages.push("Info: " + message);
|
||||
},
|
||||
error: function(error) {
|
||||
this.errors++;
|
||||
this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
|
||||
},
|
||||
status: function() {
|
||||
if (this.failures > 0) return 'failed';
|
||||
if (this.errors > 0) return 'error';
|
||||
return 'passed';
|
||||
},
|
||||
assert: function(expression) {
|
||||
var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
|
||||
try { expression ? this.pass() :
|
||||
this.fail(message); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertEqual: function(expected, actual) {
|
||||
var message = arguments[2] || "assertEqual";
|
||||
try { (expected == actual) ? this.pass() :
|
||||
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
|
||||
'", actual "' + Test.Unit.inspect(actual) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertInspect: function(expected, actual) {
|
||||
var message = arguments[2] || "assertInspect";
|
||||
try { (expected == actual.inspect()) ? this.pass() :
|
||||
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
|
||||
'", actual "' + Test.Unit.inspect(actual) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertEnumEqual: function(expected, actual) {
|
||||
var message = arguments[2] || "assertEnumEqual";
|
||||
try { $A(expected).length == $A(actual).length &&
|
||||
expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
|
||||
this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
|
||||
', actual ' + Test.Unit.inspect(actual)); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNotEqual: function(expected, actual) {
|
||||
var message = arguments[2] || "assertNotEqual";
|
||||
try { (expected != actual) ? this.pass() :
|
||||
this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertIdentical: function(expected, actual) {
|
||||
var message = arguments[2] || "assertIdentical";
|
||||
try { (expected === actual) ? this.pass() :
|
||||
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
|
||||
'", actual "' + Test.Unit.inspect(actual) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNotIdentical: function(expected, actual) {
|
||||
var message = arguments[2] || "assertNotIdentical";
|
||||
try { !(expected === actual) ? this.pass() :
|
||||
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
|
||||
'", actual "' + Test.Unit.inspect(actual) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNull: function(obj) {
|
||||
var message = arguments[1] || 'assertNull'
|
||||
try { (obj==null) ? this.pass() :
|
||||
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertMatch: function(expected, actual) {
|
||||
var message = arguments[2] || 'assertMatch';
|
||||
var regex = new RegExp(expected);
|
||||
try { (regex.exec(actual)) ? this.pass() :
|
||||
this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertHidden: function(element) {
|
||||
var message = arguments[1] || 'assertHidden';
|
||||
this.assertEqual("none", element.style.display, message);
|
||||
},
|
||||
assertNotNull: function(object) {
|
||||
var message = arguments[1] || 'assertNotNull';
|
||||
this.assert(object != null, message);
|
||||
},
|
||||
assertType: function(expected, actual) {
|
||||
var message = arguments[2] || 'assertType';
|
||||
try {
|
||||
(actual.constructor == expected) ? this.pass() :
|
||||
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
|
||||
'", actual "' + (actual.constructor) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNotOfType: function(expected, actual) {
|
||||
var message = arguments[2] || 'assertNotOfType';
|
||||
try {
|
||||
(actual.constructor != expected) ? this.pass() :
|
||||
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
|
||||
'", actual "' + (actual.constructor) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertInstanceOf: function(expected, actual) {
|
||||
var message = arguments[2] || 'assertInstanceOf';
|
||||
try {
|
||||
(actual instanceof expected) ? this.pass() :
|
||||
this.fail(message + ": object was not an instance of the expected type"); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNotInstanceOf: function(expected, actual) {
|
||||
var message = arguments[2] || 'assertNotInstanceOf';
|
||||
try {
|
||||
!(actual instanceof expected) ? this.pass() :
|
||||
this.fail(message + ": object was an instance of the not expected type"); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertRespondsTo: function(method, obj) {
|
||||
var message = arguments[2] || 'assertRespondsTo';
|
||||
try {
|
||||
(obj[method] && typeof obj[method] == 'function') ? this.pass() :
|
||||
this.fail(message + ": object doesn't respond to [" + method + "]"); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertReturnsTrue: function(method, obj) {
|
||||
var message = arguments[2] || 'assertReturnsTrue';
|
||||
try {
|
||||
var m = obj[method];
|
||||
if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
|
||||
m() ? this.pass() :
|
||||
this.fail(message + ": method returned false"); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertReturnsFalse: function(method, obj) {
|
||||
var message = arguments[2] || 'assertReturnsFalse';
|
||||
try {
|
||||
var m = obj[method];
|
||||
if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
|
||||
!m() ? this.pass() :
|
||||
this.fail(message + ": method returned true"); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertRaise: function(exceptionName, method) {
|
||||
var message = arguments[2] || 'assertRaise';
|
||||
try {
|
||||
method();
|
||||
this.fail(message + ": exception expected but none was raised"); }
|
||||
catch(e) {
|
||||
((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e);
|
||||
}
|
||||
},
|
||||
assertElementsMatch: function() {
|
||||
var expressions = $A(arguments), elements = $A(expressions.shift());
|
||||
if (elements.length != expressions.length) {
|
||||
this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
|
||||
return false;
|
||||
}
|
||||
elements.zip(expressions).all(function(pair, index) {
|
||||
var element = $(pair.first()), expression = pair.last();
|
||||
if (element.match(expression)) return true;
|
||||
this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
|
||||
}.bind(this)) && this.pass();
|
||||
},
|
||||
assertElementMatches: function(element, expression) {
|
||||
this.assertElementsMatch([element], expression);
|
||||
},
|
||||
benchmark: function(operation, iterations) {
|
||||
var startAt = new Date();
|
||||
(iterations || 1).times(operation);
|
||||
var timeTaken = ((new Date())-startAt);
|
||||
this.info((arguments[2] || 'Operation') + ' finished ' +
|
||||
iterations + ' iterations in ' + (timeTaken/1000)+'s' );
|
||||
return timeTaken;
|
||||
},
|
||||
_isVisible: function(element) {
|
||||
element = $(element);
|
||||
if(!element.parentNode) return true;
|
||||
this.assertNotNull(element);
|
||||
if(element.style && Element.getStyle(element, 'display') == 'none')
|
||||
return false;
|
||||
|
||||
return this._isVisible(element.parentNode);
|
||||
},
|
||||
assertNotVisible: function(element) {
|
||||
this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
|
||||
},
|
||||
assertVisible: function(element) {
|
||||
this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
|
||||
},
|
||||
benchmark: function(operation, iterations) {
|
||||
var startAt = new Date();
|
||||
(iterations || 1).times(operation);
|
||||
var timeTaken = ((new Date())-startAt);
|
||||
this.info((arguments[2] || 'Operation') + ' finished ' +
|
||||
iterations + ' iterations in ' + (timeTaken/1000)+'s' );
|
||||
return timeTaken;
|
||||
}
|
||||
}
|
||||
|
||||
Test.Unit.Testcase = Class.create();
|
||||
Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
|
||||
initialize: function(name, test, setup, teardown) {
|
||||
Test.Unit.Assertions.prototype.initialize.bind(this)();
|
||||
this.name = name;
|
||||
|
||||
if(typeof test == 'string') {
|
||||
test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
|
||||
test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
|
||||
this.test = function() {
|
||||
eval('with(this){'+test+'}');
|
||||
}
|
||||
} else {
|
||||
this.test = test || function() {};
|
||||
}
|
||||
|
||||
this.setup = setup || function() {};
|
||||
this.teardown = teardown || function() {};
|
||||
this.isWaiting = false;
|
||||
this.timeToWait = 1000;
|
||||
},
|
||||
wait: function(time, nextPart) {
|
||||
this.isWaiting = true;
|
||||
this.test = nextPart;
|
||||
this.timeToWait = time;
|
||||
},
|
||||
run: function() {
|
||||
try {
|
||||
try {
|
||||
if (!this.isWaiting) this.setup.bind(this)();
|
||||
this.isWaiting = false;
|
||||
this.test.bind(this)();
|
||||
} finally {
|
||||
if(!this.isWaiting) {
|
||||
this.teardown.bind(this)();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e) { this.error(e); }
|
||||
}
|
||||
});
|
||||
|
||||
// *EXPERIMENTAL* BDD-style testing to please non-technical folk
|
||||
// This draws many ideas from RSpec http://rspec.rubyforge.org/
|
||||
|
||||
Test.setupBDDExtensionMethods = function(){
|
||||
var METHODMAP = {
|
||||
shouldEqual: 'assertEqual',
|
||||
shouldNotEqual: 'assertNotEqual',
|
||||
shouldEqualEnum: 'assertEnumEqual',
|
||||
shouldBeA: 'assertType',
|
||||
shouldNotBeA: 'assertNotOfType',
|
||||
shouldBeAn: 'assertType',
|
||||
shouldNotBeAn: 'assertNotOfType',
|
||||
shouldBeNull: 'assertNull',
|
||||
shouldNotBeNull: 'assertNotNull',
|
||||
|
||||
shouldBe: 'assertReturnsTrue',
|
||||
shouldNotBe: 'assertReturnsFalse',
|
||||
shouldRespondTo: 'assertRespondsTo'
|
||||
};
|
||||
var makeAssertion = function(assertion, args, object) {
|
||||
this[assertion].apply(this,(args || []).concat([object]));
|
||||
}
|
||||
|
||||
Test.BDDMethods = {};
|
||||
$H(METHODMAP).each(function(pair) {
|
||||
Test.BDDMethods[pair.key] = function() {
|
||||
var args = $A(arguments);
|
||||
var scope = args.shift();
|
||||
makeAssertion.apply(scope, [pair.value, args, this]); };
|
||||
});
|
||||
|
||||
[Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
|
||||
function(p){ Object.extend(p, Test.BDDMethods) }
|
||||
);
|
||||
}
|
||||
|
||||
Test.context = function(name, spec, log){
|
||||
Test.setupBDDExtensionMethods();
|
||||
|
||||
var compiledSpec = {};
|
||||
var titles = {};
|
||||
for(specName in spec) {
|
||||
switch(specName){
|
||||
case "setup":
|
||||
case "teardown":
|
||||
compiledSpec[specName] = spec[specName];
|
||||
break;
|
||||
default:
|
||||
var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
|
||||
var body = spec[specName].toString().split('\n').slice(1);
|
||||
if(/^\{/.test(body[0])) body = body.slice(1);
|
||||
body.pop();
|
||||
body = body.map(function(statement){
|
||||
return statement.strip()
|
||||
});
|
||||
compiledSpec[testName] = body.join('\n');
|
||||
titles[testName] = specName;
|
||||
}
|
||||
}
|
||||
new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
|
||||
};
|