bbb-lti 0.1: BigBlueButton LTI interface, Initial commit

Signed-off-by: jfederico <jesus@123it.ca>
This commit is contained in:
jfederico 2012-10-11 11:52:41 -04:00
parent d674b36c94
commit 0c92ce768e
100 changed files with 19643 additions and 0 deletions

85
bbb-lti/.classpath Normal file
View 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>

View 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
View 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>

View File

@ -0,0 +1,9 @@
class BootStrap {
def init = { servletContext ->
log.debug "Bootstrapping bbb-lti"
}
def destroy = {
}
}

View 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'
}

View 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"
}
}
}

View File

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

View 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}

View File

@ -0,0 +1,4 @@
// Place your Spring DSL code here
beans = {
}

View File

@ -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
}
}

View 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

View 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

View 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

View 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

View 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

View 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=次へ

View 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

View 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.

View 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} не является допустимым числом

View 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

View 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

View File

@ -0,0 +1,12 @@
package org.bigbluebutton.web.services
class BigbluebuttonService {
boolean transactional = true
def url
def salt
def serviceMethod() {
}
}

View File

@ -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)
}
}

View 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>

View 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>

View 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
View 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
View 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
View 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
View 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="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;lti&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;GRAILS_HOME/dist/grails-bootstrap-1.1.1.jar&quot; path=&quot;3&quot; type=&quot;3&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;GRAILS_HOME/lib/groovy-all-1.6.3.jar&quot; path=&quot;3&quot; type=&quot;3&quot;/&gt;&#10;"/>
</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=&quot;${project_loc:lti}&quot; -Dserver.port=8080 -Dgrails.env=development"/>
</launchConfiguration>

73
bbb-lti/lti.tmproj Normal file
View 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>

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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";
}

View 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;
}

View 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();
}
}

View 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;
}

View 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;
}
}

View 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;
}

View 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;
}

View 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();
}
}

View 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;
}
}

View 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;
}

View 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);
}
}

View 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;
}

View 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());
}
}
}

View 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

View 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;
}

View 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 *= *([^;\"]*|\"([^\"]|\\\\\")*\")(;|$)");
}

View 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();
}
}

View 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";
}

View 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;
}
}

View 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("&lt;");
break;
case '>':
html.append("&gt;");
break;
case '&':
html.append("&amp;");
// This also takes care of numeric character references;
// for example &#169 becomes &amp;#169.
break;
case '"':
html.append("&quot;");
break;
default:
html.append(c);
break;
}
}
return html.toString();
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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();
}
}

View 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");
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,15 @@
import grails.test.*
class BigbluebuttonServiceTests extends GrailsUnitTestCase {
protected void setUp() {
super.setUp()
}
protected void tearDown() {
super.tearDown()
}
void testSomething() {
}
}

View File

@ -0,0 +1,15 @@
import grails.test.*
class LtiServiceTests extends GrailsUnitTestCase {
protected void setUp() {
super.setUp()
}
protected void tearDown() {
super.tearDown()
}
void testSomething() {
}
}

View File

@ -0,0 +1,15 @@
import grails.test.*
class ToolControllerTests extends ControllerUnitTestCase {
protected void setUp() {
super.setUp()
}
protected void tearDown() {
super.tearDown()
}
void testSomething() {
}
}

View 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>

View 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>

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

View 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 &lt;fmt:message&gt; action.
</description>
<name>prefix</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<description>
Loads a resource bundle and stores it in the named scoped variable or
the bundle configuration variable
</description>
<name>setBundle</name>
<tag-class>org.apache.taglibs.standard.tag.rt.fmt.SetBundleTag</tag-class>
<body-content>empty</body-content>
<attribute>
<description>
Resource bundle base name. This is the bundle's
fully-qualified resource name, which has the same
form as a fully-qualified class name, that is, it uses
"." as the package component separator and does not
have any file type (such as ".class" or ".properties")
suffix.
</description>
<name>basename</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Name of the exported scoped variable which stores
the i18n localization context of type
javax.servlet.jsp.jstl.fmt.LocalizationC
ontext.
</description>
<name>var</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<description>
Scope of var or the localization context
configuration variable.
</description>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
<tag>
<description>
Maps key to localized message and performs parametric replacement
</description>
<name>message</name>
<tag-class>org.apache.taglibs.standard.tag.rt.fmt.MessageTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description>
Message key to be looked up.
</description>
<name>key</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Localization context in whose resource
bundle the message key is looked up.
</description>
<name>bundle</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Name of the exported scoped variable
which stores the localized message.
</description>
<name>var</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<description>
Scope of var.
</description>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
<tag>
<description>
Supplies an argument for parametric replacement to a containing
&lt;message&gt; tag
</description>
<name>param</name>
<tag-class>org.apache.taglibs.standard.tag.rt.fmt.ParamTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description>
Argument used for parametric replacement.
</description>
<name>value</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<description>
Formats a numeric value as a number, currency, or percentage
</description>
<name>formatNumber</name>
<tag-class>org.apache.taglibs.standard.tag.rt.fmt.FormatNumberTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description>
Numeric value to be formatted.
</description>
<name>value</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Specifies whether the value is to be
formatted as number, currency, or
percentage.
</description>
<name>type</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Custom formatting pattern.
</description>
<name>pattern</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
ISO 4217 currency code. Applied only
when formatting currencies (i.e. if type is
equal to "currency"); ignored otherwise.
</description>
<name>currencyCode</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Currency symbol. Applied only when
formatting currencies (i.e. if type is equal
to "currency"); ignored otherwise.
</description>
<name>currencySymbol</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Specifies whether the formatted output
will contain any grouping separators.
</description>
<name>groupingUsed</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Maximum number of digits in the integer
portion of the formatted output.
</description>
<name>maxIntegerDigits</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Minimum number of digits in the integer
portion of the formatted output.
</description>
<name>minIntegerDigits</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Maximum number of digits in the
fractional portion of the formatted output.
</description>
<name>maxFractionDigits</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Minimum number of digits in the
fractional portion of the formatted output.
</description>
<name>minFractionDigits</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>
Name of the exported scoped variable
which stores the formatted result as a
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>

View 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>

View 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>

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View 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'});
}
});
}

File diff suppressed because one or more lines are too long

136
bbb-lti/web-app/js/prototype/builder.js vendored Normal file
View 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(/"/,'&quot;') + '"');
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
View 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
View 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

File diff suppressed because it is too large Load Diff

4184
bbb-lti/web-app/js/prototype/prototype.js vendored Normal file

File diff suppressed because it is too large Load Diff

2691
bbb-lti/web-app/js/prototype/rico.js vendored Normal file

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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
View 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 });
};