Merge branch 'screenshare-with-cursor-capture' of github.com:ritzalam/bigbluebutton

This commit is contained in:
Anton Georgiev 2016-03-05 16:50:13 +00:00
commit a0aebf3cdc
292 changed files with 21663 additions and 8 deletions

47
bbb-screenshare/.classpath Executable file
View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="app/src/main/java"/>
<classpathentry kind="src" path="app/src/main/scala"/>
<classpathentry kind="src" path="app/src/test/java"/>
<classpathentry kind="src" path="jws/webstart/src/main/java"/>
<classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="app/lib/aopalliance-1.0.jar"/>
<classpathentry kind="lib" path="app/lib/commons-fileupload-1.2.2.jar"/>
<classpathentry kind="lib" path="app/lib/commons-io-2.1.jar"/>
<classpathentry kind="lib" path="app/lib/commons-pool-1.5.6.jar"/>
<classpathentry kind="lib" path="app/lib/configgy-2.0.0.jar"/>
<classpathentry kind="lib" path="app/lib/easymock-2.4.jar"/>
<classpathentry kind="lib" path="app/lib/gson-1.7.1.jar"/>
<classpathentry kind="lib" path="app/lib/jcl-over-slf4j-1.7.9.jar"/>
<classpathentry kind="lib" path="app/lib/jedis-1.5.1.jar"/>
<classpathentry kind="lib" path="app/lib/jul-to-slf4j-1.7.9.jar"/>
<classpathentry kind="lib" path="app/lib/log4j-over-slf4j-1.7.9.jar"/>
<classpathentry kind="lib" path="app/lib/logback-classic-1.1.2.jar"/>
<classpathentry kind="lib" path="app/lib/logback-core-1.1.2.jar"/>
<classpathentry kind="lib" path="app/lib/mina-core-2.0.8.jar"/>
<classpathentry kind="lib" path="app/lib/mina-integration-beans-2.0.8.jar"/>
<classpathentry kind="lib" path="app/lib/mina-integration-jmx-2.0.8.jar"/>
<classpathentry kind="lib" path="app/lib/red5-io-1.0.6-SNAPSHOT.jar"/>
<classpathentry kind="lib" path="app/lib/red5-server-1.0.6-SNAPSHOT.jar"/>
<classpathentry kind="lib" path="app/lib/red5-server-common-1.0.6-SNAPSHOT.jar"/>
<classpathentry kind="lib" path="app/lib/scala-library-2.9.2.jar"/>
<classpathentry kind="lib" path="app/lib/servlet-api-2.5.jar"/>
<classpathentry kind="lib" path="app/lib/slf4j-api-1.7.9.jar"/>
<classpathentry kind="lib" path="app/lib/spring-aop-4.0.8.RELEASE.jar"/>
<classpathentry kind="lib" path="app/lib/spring-beans-4.0.8.RELEASE.jar"/>
<classpathentry kind="lib" path="app/lib/spring-context-4.0.8.RELEASE.jar"/>
<classpathentry kind="lib" path="app/lib/spring-core-4.0.8.RELEASE.jar"/>
<classpathentry kind="lib" path="app/lib/spring-web-4.0.8.RELEASE.jar"/>
<classpathentry kind="lib" path="app/lib/spring-webmvc-4.0.7.RELEASE.jar"/>
<classpathentry kind="lib" path="app/jws/lib/ffmpeg-linux-x86_64.jar"/>
<classpathentry kind="lib" path="app/jws/lib/ffmpeg-linux-x86.jar"/>
<classpathentry kind="lib" path="app/jws/lib/ffmpeg-macosx-x86_64.jar"/>
<classpathentry kind="lib" path="app/jws/lib/ffmpeg-windows-x86_64.jar"/>
<classpathentry kind="lib" path="app/jws/lib/ffmpeg-windows-x86.jar"/>
<classpathentry kind="lib" path="app/jws/lib/ffmpeg.jar"/>
<classpathentry kind="lib" path="app/jws/lib/javacpp.jar"/>
<classpathentry kind="lib" path="app/jws/lib/javacv-screenshare-0.0.1.jar"/>
<classpathentry kind="lib" path="app/jws/lib/javacv.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

10
bbb-screenshare/.gitignore vendored Executable file
View File

@ -0,0 +1,10 @@
.manager
.scala_dependencies
.classpath
.gradle/
.project
app/build/
app/lib/
jws/webstart/lib/
build/
/bin/

18
bbb-screenshare/.project Executable file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>s-bbb-screenshare</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.scala-ide.sdt.core.scalabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.scala-ide.sdt.core.scalanature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="output" path="build/classes/main"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="src" path="/common" combineaccessrules="false"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/commons-fileupload/commons-fileupload/jars/commons-fileupload-1.2.1.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/commons-io/commons-io/jars/commons-io-1.4.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/javax.servlet/servlet-api/jars/servlet-api-2.5.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/log4j-over-slf4j/jars/log4j-over-slf4j-1.5.6.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/logback-classic/jars/logback-classic-0.9.14.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/logback-core/jars/logback-core-0.9.14.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/net/lag/configgy/configgy/jars/configgy-1.5.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/org.apache.mina/mina-core/jars/mina-core-2.0.0-RC1.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/org.apache.mina/mina-integration-spring/jars/mina-integration-spring-1.1.7.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/org.scala-lang/scala-library/jars/scala-library-2.7.7.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/org/red5/red5/jars/red5-0.91.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/slf4j-api/jars/slf4j-api-1.5.6.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-aop/jars/spring-aop-3.0.0.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-beans/jars/spring-beans-3.0.0.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-context/jars/spring-context-3.0.0.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-core/jars/spring-core-3.0.0.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-web/jars/spring-web-3.0.0.jar"/>
<classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-webmvc/jars/spring-webmvc-2.5.6.jar"/>
</classpath>

160
bbb-screenshare/app/build.gradle Executable file
View File

@ -0,0 +1,160 @@
apply plugin: 'scala'
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse'
version = '0.8'
jar.enabled = true
def appName = 'bbb-screenshare'
archivesBaseName = appName
task resolveDeps(type: Copy) {
into('lib')
from configurations.default
from configurations.default.allArtifacts*.file
}
repositories {
mavenCentral()
mavenLocal()
add(new org.apache.ivy.plugins.resolver.ChainResolver()) {
name = 'remote'
returnFirst = true
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "googlecode"
addArtifactPattern "http://red5.googlecode.com/svn/repository/[artifact](-[revision]).[ext]"
addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "blindside-repos"
addArtifactPattern "http://blindside.googlecode.com/svn/repository/[artifact](-[revision]).[ext]"
addArtifactPattern "http://blindside.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "maven2-central"
m2compatible = true
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]"
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "testng_ibiblio_maven2"
m2compatible = true
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision])-jdk15.[ext]"
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision])-jdk15.[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "netty-dependency"
m2compatible = true
addArtifactPattern "http://repository.jboss.org/nexus/content/groups/public-jboss/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]"
addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]"
}
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "spring-bundles"
m2compatible = true
addArtifactPattern "http://repository.springsource.com/maven/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
addArtifactPattern "http://repository.springsource.com/maven/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
}
mavenRepo name: "jboss", urls: "http://repository.jboss.org/nexus/content/groups/public-jboss"
mavenRepo name: "sonatype-snapshot", urls: "http://oss.sonatype.org/content/repositories/snapshots"
mavenRepo name: "sonatype-releases", urls: "http://oss.sonatype.org/content/repositories/releases"
}
}
dependencies {
// Servlet
providedCompile 'javax.servlet:servlet-api:2.5@jar'
// Mina
providedCompile 'org.apache.mina:mina-core:2.0.8@jar'
providedCompile 'org.apache.mina:mina-integration-beans:2.0.8@jar'
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.8@jar'
// Spring
providedCompile 'org.springframework:spring-web:4.0.8.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:4.0.8.RELEASE@jar'
providedCompile 'org.springframework:spring-context:4.0.8.RELEASE@jar'
providedCompile 'org.springframework:spring-core:4.0.8.RELEASE@jar'
// Red5
providedCompile 'org.red5:red5-server:1.0.6-SNAPSHOT@jar'
providedCompile 'org.red5:red5-server-common:1.0.6-SNAPSHOT@jar'
providedCompile 'org.red5:red5-io:1.0.6-SNAPSHOT@jar'
// Logging
providedCompile 'ch.qos.logback:logback-core:1.1.2@jar'
providedCompile 'ch.qos.logback:logback-classic:1.1.2@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.9@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.9@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.9@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.9@jar'
// Needed for the JVM shutdown hook but needs to be put into red5/lib dir.
// Otherwise we get exception on aop utils class not found.
providedCompile 'org.springframework:spring-aop:4.0.8.RELEASE@jar'
providedCompile 'aopalliance:aopalliance:1.0@jar'
// Testing
//compile 'org.testng:testng:5.8@jar'
compile 'org.easymock:easymock:2.4@jar'
// Testing
//testRuntime 'org/testng:testng:5.8@jar'
testRuntime 'org.easymock:easymock:2.4@jar'
// Tunnelling servlet
compile 'org.springframework:spring-webmvc:4.0.7.RELEASE@jar'
// Need to put commons-fileupload and commons-io in red5/lib dir. Otherwise, we get an
// java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileItemFactory or
// java.lang.NoClassDefFoundError: org/apache/commons/io/output/DeferredFileOutputStream
// ralam (Feb 27, 2013)
providedCompile 'commons-fileupload:commons-fileupload:1.2.2@jar'
providedCompile 'commons-io:commons-io:2.1@jar'
// Libraries needed to run the scala tools
scalaTools 'org.scala-lang:scala-compiler:2.9.2'
scalaTools 'org.scala-lang:scala-library:2.9.2'
// workaround for http://issues.gradle.org/browse/GRADLE-1273
//compileScala.classpath = sourceSets.main.compileClasspath + files(sourceSets.main.classesDir)
//compileTestScala.classpath = sourceSets.test.compileClasspath + files(sourceSets.test.classesDir)
// Libraries needed for scala api
compile 'org.scala-lang:scala-library:2.9.2'
compile 'net.lag:configgy:2.0.0@jar'
//redis
compile 'redis.clients:jedis:1.5.1'
providedCompile 'commons-pool:commons-pool:1.5.6'
compile 'com.google.code.gson:gson:1.7.1'
}
test {
useTestNG()
}
war.doLast {
ant.unzip(src: war.archivePath, dest: "$buildDir/screenshare")
}
task deploy() << {
def red5AppsDir = '/usr/share/red5/webapps'
def screenshareDir = new File("${red5AppsDir}/screenshare")
println "Deleting $screenshareDir"
ant.delete(dir: screenshareDir)
ant.mkdir(dir: screenshareDir)
ant.copy(todir: screenshareDir) {
fileset(dir: "$buildDir/screenshare")
}
def jwsLibDir = new File("${red5AppsDir}/screenshare/lib")
ant.mkdir(dir: jwsLibDir)
ant.copy(todir: jwsLibDir) {
fileset(dir: "jws/lib")
}
ant.copy(todir: screenshareDir) {
fileset(file: "jws/screenshare.jnlp")
}
}

4
bbb-screenshare/app/deploy.sh Executable file
View File

@ -0,0 +1,4 @@
sudo chmod -R 777 /usr/share/red5/webapps
gradle clean war deploy
sudo chmod -R 777 /usr/share/red5/webapps

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+" codebase="." href="">
<!--
Keep href empty. Otherwise this jnlp file will always be cached.
http://www.coderanch.com/t/284889/JSP/java/Caching-JNLP
-->
<information>
<title>BigBlueButton Screen Share</title>
<vendor>BigBlueButton</vendor>
</information>
<resources>
<j2se version="1.7+" href="http://java.sun.com/products/autodl/j2se"/>
<jar href="$$jnlpUrl/lib/javacv-screenshare-0.0.1.jar" main="true" />
<jar href="$$jnlpUrl/lib/javacv.jar" />
<jar href="$$jnlpUrl/lib/javacpp.jar" />
<jar href="$$jnlpUrl/lib/ffmpeg.jar" />
</resources>
<resources os="Windows" arch="amd64">
<nativelib href="$$jnlpUrl/lib/ffmpeg-windows-x86_64.jar" download="eager"/>
</resources>
<resources os="Windows" arch="x86">
<nativelib href="$$jnlpUrl/lib/ffmpeg-windows-x86.jar" download="eager"/>
</resources>
<resources os="Linux" arch="x86_64 amd64">
<nativelib href="$$jnlpUrl/lib/ffmpeg-linux-x86_64.jar" download="eager"/>
</resources>
<resources os="Linux" arch="x86 i386 i486 i586 i686">
<nativelib href="$$jnlpUrl/lib/ffmpeg-linux-x86.jar" download="eager"/>
</resources>
<resources os="Mac OS X">
<nativelib href="$$jnlpUrl/lib/ffmpeg-macosx-x86_64.jar" download="eager"/>
</resources>
<application-desc
name="Desktop Sharing Demo Application"
main-class="org.bigbluebutton.screenshare.client.DeskshareMain">
<argument>$$publishUrl</argument>
<argument>$$serverUrl</argument>
<argument>$$meetingId</argument>
<argument>$$streamId</argument>
<argument>$$fullScreen</argument>
<argument>$$codecOptions</argument>
<argument>$$errorMessage</argument>
</application-desc>
<security><all-permissions/></security>
<update check="always" policy="always"/>
</jnlp>

View File

@ -0,0 +1,642 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.jardiff;
import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;
/**
* JarDiff is able to create a jar file containing the delta between two
* jar files (old and new). The delta jar file can then be applied to the
* old jar file to reconstruct the new jar file.
* <p>
* Refer to the JNLP spec for details on how this is done.
*
* @version 1.13, 06/26/03
*/
public class JarDiff implements JarDiffConstants {
private static final int DEFAULT_READ_SIZE = 2048;
private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
private static ResourceBundle _resources = null;
// The JARDiff.java is the stand-along jardiff.jar tool. Thus, we do not
// depend on Globals.java and other stuff here. Instead, we use an explicit
// _debug flag.
private static boolean _debug;
public static ResourceBundle getResources() {
if (_resources == null) {
_resources = ResourceBundle.getBundle("jnlp/sample/jardiff/resources/strings");
}
return _resources;
}
/**
* Creates a patch from the two passed in files, writing the result
* to <code>os</code>.
*/
public static void createPatch(String oldPath, String newPath,
OutputStream os, boolean minimal) throws IOException{
JarFile2 oldJar = new JarFile2(oldPath);
JarFile2 newJar = new JarFile2(newPath);
try {
Iterator entries;
HashMap moved = new HashMap();
HashSet visited = new HashSet();
HashSet implicit = new HashSet();
HashSet moveSrc = new HashSet();
HashSet newEntries = new HashSet();
// FIRST PASS
// Go through the entries in new jar and
// determine which files are candidates for implicit moves
// ( files that has the same filename and same content in old.jar
// and new.jar )
// and for files that cannot be implicitly moved, we will either
// find out whether it is moved or new (modified)
entries = newJar.getJarEntries();
if (entries != null) {
while (entries.hasNext()) {
JarEntry newEntry = (JarEntry)entries.next();
String newname = newEntry.getName();
// Return best match of contents, will return a name match if possible
String oldname = oldJar.getBestMatch(newJar, newEntry);
if (oldname == null) {
// New or modified entry
if (_debug) {
System.out.println("NEW: "+ newname);
}
newEntries.add(newname);
} else {
// Content already exist - need to do a move
// Should do implicit move? Yes, if names are the same, and
// no move command already exist from oldJar
if (oldname.equals(newname) && !moveSrc.contains(oldname)) {
if (_debug) {
System.out.println(newname + " added to implicit set!");
}
implicit.add(newname);
} else {
// The 1.0.1/1.0 JarDiffPatcher cannot handle
// multiple MOVE command with same src.
// The work around here is if we are going to generate
// a MOVE command with duplicate src, we will
// instead add the target as a new file. This way
// the jardiff can be applied by 1.0.1/1.0
// JarDiffPatcher also.
if (!minimal && (implicit.contains(oldname) ||
moveSrc.contains(oldname) )) {
// generate non-minimal jardiff
// for backward compatibility
if (_debug) {
System.out.println("NEW: "+ newname);
}
newEntries.add(newname);
} else {
// Use newname as key, since they are unique
if (_debug) {
System.err.println("moved.put " + newname + " " + oldname);
}
moved.put(newname, oldname);
moveSrc.add(oldname);
}
// Check if this disables an implicit 'move <oldname> <oldname>'
if (implicit.contains(oldname) && minimal) {
if (_debug) {
System.err.println("implicit.remove " + oldname);
System.err.println("moved.put " + oldname + " " + oldname);
}
implicit.remove(oldname);
moved.put(oldname, oldname);
moveSrc.add(oldname);
}
}
}
}
} //if (entries != null)
// SECOND PASS: <deleted files> = <oldjarnames> - <implicitmoves> -
// <source of move commands> - <new or modified entries>
ArrayList deleted = new ArrayList();
entries = oldJar.getJarEntries();
if (entries != null) {
while (entries.hasNext()) {
JarEntry oldEntry = (JarEntry)entries.next();
String oldName = oldEntry.getName();
if (!implicit.contains(oldName) && !moveSrc.contains(oldName)
&& !newEntries.contains(oldName)) {
if (_debug) {
System.err.println("deleted.add " + oldName);
}
deleted.add(oldName);
}
}
}
//DEBUG
if (_debug) {
//DEBUG: print out moved map
entries = moved.keySet().iterator();
if (entries != null) {
System.out.println("MOVED MAP!!!");
while (entries.hasNext()) {
String newName = (String)entries.next();
String oldName = (String)moved.get(newName);
System.out.println("key is " + newName + " value is " + oldName);
}
}
//DEBUG: print out IMOVE map
entries = implicit.iterator();
if (entries != null) {
System.out.println("IMOVE MAP!!!");
while (entries.hasNext()) {
String newName = (String)entries.next();
System.out.println("key is " + newName);
}
}
}
JarOutputStream jos = new JarOutputStream(os);
// Write out all the MOVEs and REMOVEs
createIndex(jos, deleted, moved);
// Put in New and Modified entries
entries = newEntries.iterator();
if (entries != null) {
while (entries.hasNext()) {
String newName = (String)entries.next();
if (_debug) {
System.out.println("New File: " + newName);
}
writeEntry(jos, newJar.getEntryByName(newName), newJar);
}
}
jos.finish();
jos.close();
} catch (IOException ioE){
throw ioE;
} finally {
try {
oldJar.getJarFile().close();
} catch (IOException e1) {
//ignore
}
try {
newJar.getJarFile().close();
} catch (IOException e1) {
//ignore
}
} // finally
}
/**
* Writes the index file out to <code>jos</code>.
* <code>oldEntries</code> gives the names of the files that were removed,
* <code>movedMap</code> maps from the new name to the old name.
*/
private static void createIndex(JarOutputStream jos, List oldEntries,
Map movedMap) throws
IOException {
StringWriter writer = new StringWriter();
writer.write(VERSION_HEADER);
writer.write("\r\n");
// Write out entries that have been removed
for (int counter = 0; counter < oldEntries.size(); counter++) {
String name = (String)oldEntries.get(counter);
writer.write(REMOVE_COMMAND);
writer.write(" ");
writeEscapedString(writer, name);
writer.write("\r\n");
}
// And those that have moved
Iterator names = movedMap.keySet().iterator();
if (names != null) {
while (names.hasNext()) {
String newName = (String)names.next();
String oldName = (String)movedMap.get(newName);
writer.write(MOVE_COMMAND);
writer.write(" ");
writeEscapedString(writer, oldName);
writer.write(" ");
writeEscapedString(writer, newName);
writer.write("\r\n");
}
}
JarEntry je = new JarEntry(INDEX_NAME);
byte[] bytes = writer.toString().getBytes("UTF-8");
writer.close();
jos.putNextEntry(je);
jos.write(bytes, 0, bytes.length);
}
private static void writeEscapedString(Writer writer, String string)
throws IOException {
int index = 0;
int last = 0;
char[] chars = null;
while ((index = string.indexOf(' ', index)) != -1) {
if (last != index) {
if (chars == null) {
chars = string.toCharArray();
}
writer.write(chars, last, index - last);
}
last = index;
index++;
writer.write('\\');
}
if (last != 0) {
writer.write(chars, last, chars.length - last);
}
else {
// no spaces
writer.write(string);
}
}
private static void writeEntry(JarOutputStream jos, JarEntry entry,
JarFile2 file) throws IOException {
writeEntry(jos, entry, file.getJarFile().getInputStream(entry));
}
private static void writeEntry(JarOutputStream jos, JarEntry entry,
InputStream data) throws IOException {
jos.putNextEntry(entry);
try {
// Read the entry
int size = data.read(newBytes);
while (size != -1) {
jos.write(newBytes, 0, size);
size = data.read(newBytes);
}
} catch(IOException ioE) {
throw ioE;
} finally {
try {
data.close();
} catch(IOException e){
//Ignore
}
}
}
/**
* JarFile2 wraps a JarFile providing some convenience methods.
*/
private static class JarFile2 {
private JarFile _jar;
private List _entries;
private HashMap _nameToEntryMap;
private HashMap _crcToEntryMap;
public JarFile2(String path) throws IOException {
_jar = new JarFile(new File(path));
index();
}
public JarFile getJarFile() {
return _jar;
}
public Iterator getJarEntries() {
return _entries.iterator();
}
public JarEntry getEntryByName(String name) {
return (JarEntry)_nameToEntryMap.get(name);
}
/**
* Returns true if the two InputStreams differ.
*/
private static boolean differs(InputStream oldIS, InputStream newIS)
throws IOException {
int newSize = 0;
int oldSize;
int total = 0;
boolean retVal = false;
try{
while (newSize != -1) {
newSize = newIS.read(newBytes);
oldSize = oldIS.read(oldBytes);
if (newSize != oldSize) {
if (_debug) {
System.out.println("\tread sizes differ: " + newSize +
" " + oldSize + " total " + total);
}
retVal = true;
break;
}
if (newSize > 0) {
while (--newSize >= 0) {
total++;
if (newBytes[newSize] != oldBytes[newSize]) {
if (_debug) {
System.out.println("\tbytes differ at " +
total);
}
retVal = true;
break;
}
if ( retVal ) {
//Jump out
break;
}
newSize = 0;
}
}
}
} catch(IOException ioE){
throw ioE;
} finally {
try {
oldIS.close();
} catch(IOException e){
//Ignore
}
try {
newIS.close();
} catch(IOException e){
//Ignore
}
}
return retVal;
}
public String getBestMatch(JarFile2 file, JarEntry entry) throws IOException {
// check for same name and same content, return name if found
if (contains(file, entry)) {
return (entry.getName());
}
// return name of same content file or null
return (hasSameContent(file,entry));
}
public boolean contains(JarFile2 f, JarEntry e) throws IOException {
JarEntry thisEntry = getEntryByName(e.getName());
// Look up name in 'this' Jar2File - if not exist return false
if (thisEntry == null)
return false;
// Check CRC - if no match - return false
if (thisEntry.getCrc() != e.getCrc())
return false;
// Check contents - if no match - return false
InputStream oldIS = getJarFile().getInputStream(thisEntry);
InputStream newIS = f.getJarFile().getInputStream(e);
boolean retValue = differs(oldIS, newIS);
return !retValue;
}
public String hasSameContent(JarFile2 file, JarEntry entry) throws
IOException {
String thisName = null;
Long crcL = new Long(entry.getCrc());
// check if this jar contains files with the passed in entry's crc
if (_crcToEntryMap.containsKey(crcL)) {
// get the Linked List with files with the crc
LinkedList ll = (LinkedList)_crcToEntryMap.get(crcL);
// go through the list and check for content match
ListIterator li = ll.listIterator(0);
if (li != null) {
while (li.hasNext()) {
JarEntry thisEntry = (JarEntry)li.next();
// check for content match
InputStream oldIS = getJarFile().getInputStream(thisEntry);
InputStream newIS = file.getJarFile().getInputStream(entry);
if (!differs(oldIS, newIS)) {
thisName = thisEntry.getName();
return thisName;
}
}
}
}
return thisName;
}
private void index() throws IOException {
Enumeration entries = _jar.entries();
_nameToEntryMap = new HashMap();
_crcToEntryMap = new HashMap();
_entries = new ArrayList();
if (_debug) {
System.out.println("indexing: " + _jar.getName());
}
if (entries != null) {
while (entries.hasMoreElements()) {
JarEntry entry = (JarEntry)entries.nextElement();
long crc = entry.getCrc();
Long crcL = new Long(crc);
if (_debug) {
System.out.println("\t" + entry.getName() + " CRC " +
crc);
}
_nameToEntryMap.put(entry.getName(), entry);
_entries.add(entry);
// generate the CRC to entries map
if (_crcToEntryMap.containsKey(crcL)) {
// key exist, add the entry to the correcponding
// linked list
// get the linked list
LinkedList ll = (LinkedList)_crcToEntryMap.get(crcL);
// put in the new entry
ll.add(entry);
// put it back in the hash map
_crcToEntryMap.put(crcL, ll);
} else {
// create a new entry in the hashmap for the new key
// first create the linked list and put in the new
// entry
LinkedList ll = new LinkedList();
ll.add(entry);
// create the new entry in the hashmap
_crcToEntryMap.put(crcL, ll);
}
}
}
}
}
private static void showHelp() {
System.out.println("JarDiff: [-nonminimal (for backward compatibility with 1.0.1/1.0] [-creatediff | -applydiff] [-output file] old.jar new.jar");
}
// -creatediff -applydiff -debug -output file
public static void main(String[] args) throws IOException {
boolean diff = true;
boolean minimal = true;
String outputFile = "out.jardiff";
for (int counter = 0; counter < args.length; counter++) {
// for backward compatibilty with 1.0.1/1.0
if (args[counter].equals("-nonminimal") ||
args[counter].equals("-n")) {
minimal = false;
}
else if (args[counter].equals("-creatediff") ||
args[counter].equals("-c")) {
diff = true;
}
else if (args[counter].equals("-applydiff") ||
args[counter].equals("-a")) {
diff = false;
}
else if (args[counter].equals("-debug") ||
args[counter].equals("-d")) {
_debug = true;
}
else if (args[counter].equals("-output") ||
args[counter].equals("-o")) {
if (++counter < args.length) {
outputFile = args[counter];
}
}
else if (args[counter].equals("-applydiff") ||
args[counter].equals("-a")) {
diff = false;
}
else {
if ((counter + 2) != args.length) {
showHelp();
System.exit(0);
}
if (diff) {
try {
OutputStream os = new FileOutputStream(outputFile);
JarDiff.createPatch(args[counter],
args[counter + 1], os, minimal);
os.close();
} catch (IOException ioe) {
try {
System.out.println(getResources().getString("jardiff.error.create") + " " + ioe);
} catch (MissingResourceException mre) {
}
}
}
else {
try {
OutputStream os = new FileOutputStream(outputFile);
new JarDiffPatcher().applyPatch(
null,
args[counter],
args[counter + 1],
os);
os.close();
} catch (IOException ioe) {
try {
System.out.println(getResources().getString("jardiff.error.apply") + " " + ioe);
} catch (MissingResourceException mre) {
}
}
}
System.exit(0);
}
}
showHelp();
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.jardiff;
import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;
/**
* Constants used by creating patch and applying patch for JarDiff.
*
* @version 1.8, 06/26/03
*/
public interface JarDiffConstants {
public final String VERSION_HEADER = "version 1.0";
public final String INDEX_NAME = "META-INF/INDEX.JD";
public final String REMOVE_COMMAND = "remove";
public final String MOVE_COMMAND = "move";
}

View File

@ -0,0 +1,329 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.jardiff;
import java.io.*;
import java.util.*;
import java.net.URL;
import java.util.jar.*;
import java.util.zip.*;
/**
* JarDiff is able to create a jar file containing the delta between two
* jar files (old and new). The delta jar file can then be applied to the
* old jar file to reconstruct the new jar file.
* <p>
* Refer to the JNLP spec for details on how this is done.
*
* @version 1.11, 06/26/03
*/
public class JarDiffPatcher implements JarDiffConstants, Patcher {
private static final int DEFAULT_READ_SIZE = 2048;
private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
private static ResourceBundle _resources = JarDiff.getResources();
public static ResourceBundle getResources() {
return JarDiff.getResources();
}
public void applyPatch(Patcher.PatchDelegate delegate, String oldJarPath,
String jarDiffPath, OutputStream result) throws IOException {
File oldFile = new File(oldJarPath);
File diffFile = new File(jarDiffPath);
JarOutputStream jos = new JarOutputStream(result);
JarFile oldJar = new JarFile(oldFile);
JarFile jarDiff = new JarFile(diffFile);
Set ignoreSet = new HashSet();
Map renameMap = new HashMap();
determineNameMapping(jarDiff, ignoreSet, renameMap);
// get all keys in renameMap
Object[] keys = renameMap.keySet().toArray();
// Files to implicit move
Set oldjarNames = new HashSet();
Enumeration oldEntries = oldJar.entries();
if (oldEntries != null) {
while (oldEntries.hasMoreElements()) {
oldjarNames.add(((JarEntry)oldEntries.nextElement()).getName());
}
}
// size depends on the three parameters below, which is
// basically the counter for each loop that do the actual
// writes to the output file
// since oldjarNames.size() changes in the first two loop
// below, we need to adjust the size accordingly also when
// oldjarNames.size() changes
double size = oldjarNames.size() + keys.length + jarDiff.size();
double currentEntry = 0;
// Handle all remove commands
oldjarNames.removeAll(ignoreSet);
size -= ignoreSet.size();
// Add content from JARDiff
Enumeration entries = jarDiff.entries();
if (entries != null) {
while (entries.hasMoreElements()) {
JarEntry entry = (JarEntry)entries.nextElement();
if (!INDEX_NAME.equals(entry.getName())) {
updateDelegate(delegate, currentEntry, size);
currentEntry++;
writeEntry(jos, entry, jarDiff);
// Remove entry from oldjarNames since no implicit
//move is needed
boolean wasInOld = oldjarNames.remove(entry.getName());
// Update progress counters. If it was in old, we do
// not need an implicit move, so adjust total size.
if (wasInOld) size--;
}
else {
// no write is done, decrement size
size--;
}
}
}
// go through the renameMap and apply move for each entry
for (int j = 0; j < keys.length; j++) {
// Apply move <oldName> <newName> command
String newName = (String)keys[j];
String oldName = (String)renameMap.get(newName);
// Get source JarEntry
JarEntry oldEntry = oldJar.getJarEntry(oldName);
if (oldEntry == null) {
String moveCmd = MOVE_COMMAND + oldName + " " + newName;
handleException("jardiff.error.badmove", moveCmd);
}
// Create dest JarEntry
JarEntry newEntry = new JarEntry(newName);
newEntry.setTime(oldEntry.getTime());
newEntry.setSize(oldEntry.getSize());
newEntry.setCompressedSize(oldEntry.getCompressedSize());
newEntry.setCrc(oldEntry.getCrc());
newEntry.setMethod(oldEntry.getMethod());
newEntry.setExtra(oldEntry.getExtra());
newEntry.setComment(oldEntry.getComment());
updateDelegate(delegate, currentEntry, size);
currentEntry++;
writeEntry(jos, newEntry, oldJar.getInputStream(oldEntry));
// Remove entry from oldjarNames since no implicit
//move is needed
boolean wasInOld = oldjarNames.remove(oldName);
// Update progress counters. If it was in old, we do
// not need an implicit move, so adjust total size.
if (wasInOld) size--;
}
// implicit move
Iterator iEntries = oldjarNames.iterator();
if (iEntries != null) {
while (iEntries.hasNext()) {
String name = (String)iEntries.next();
JarEntry entry = oldJar.getJarEntry(name);
updateDelegate(delegate, currentEntry, size);
currentEntry++;
writeEntry(jos, entry, oldJar);
}
}
updateDelegate(delegate, currentEntry, size);
jos.finish();
}
private void updateDelegate(Patcher.PatchDelegate delegate, double currentSize, double size) {
if (delegate != null) {
delegate.patching((int)(currentSize/size));
}
}
private void determineNameMapping(JarFile jarDiff, Set ignoreSet,
Map renameMap) throws IOException {
InputStream is = jarDiff.getInputStream(jarDiff.getEntry(INDEX_NAME));
if (is == null) {
handleException("jardiff.error.noindex", null);
}
LineNumberReader indexReader = new LineNumberReader
(new InputStreamReader(is, "UTF-8"));
String line = indexReader.readLine();
if (line == null || !line.equals(VERSION_HEADER)) {
handleException("jardiff.error.badheader", line);
}
while ((line = indexReader.readLine()) != null) {
if (line.startsWith(REMOVE_COMMAND)) {
List sub = getSubpaths(line.substring(REMOVE_COMMAND.
length()));
if (sub.size() != 1) {
handleException("jardiff.error.badremove", line);
}
ignoreSet.add(sub.get(0));
}
else if (line.startsWith(MOVE_COMMAND)) {
List sub = getSubpaths(line.substring(MOVE_COMMAND.length()));
if (sub.size() != 2) {
handleException("jardiff.error.badmove", line);
}
// target of move should be the key
if (renameMap.put(sub.get(1), sub.get(0)) != null) {
// invalid move - should not move to same target twice
handleException("jardiff.error.badmove", line);
}
}
else if (line.length() > 0) {
handleException("jardiff.error.badcommand", line);
}
}
}
private void handleException(String errorMsg, String line) throws IOException {
try {
throw new IOException(getResources().getString(errorMsg) + " " + line);
} catch (MissingResourceException mre) {
System.err.println("Fatal error: " + errorMsg);
new Throwable().printStackTrace(System.err);
System.exit(-1);
}
}
private List getSubpaths(String path) {
int index = 0;
int length = path.length();
ArrayList sub = new ArrayList();
while (index < length) {
while (index < length && Character.isWhitespace
(path.charAt(index))) {
index++;
}
if (index < length) {
int start = index;
int last = start;
String subString = null;
while (index < length) {
char aChar = path.charAt(index);
if (aChar == '\\' && (index + 1) < length &&
path.charAt(index + 1) == ' ') {
if (subString == null) {
subString = path.substring(last, index);
}
else {
subString += path.substring(last, index);
}
last = ++index;
}
else if (Character.isWhitespace(aChar)) {
break;
}
index++;
}
if (last != index) {
if (subString == null) {
subString = path.substring(last, index);
}
else {
subString += path.substring(last, index);
}
}
sub.add(subString);
}
}
return sub;
}
private void writeEntry(JarOutputStream jos, JarEntry entry,
JarFile file) throws IOException {
writeEntry(jos, entry, file.getInputStream(entry));
}
private void writeEntry(JarOutputStream jos, JarEntry entry,
InputStream data) throws IOException {
//Create a new ZipEntry to clear the compressed size. 5079423
jos.putNextEntry(new ZipEntry(entry.getName()));
// Read the entry
int size = data.read(newBytes);
while (size != -1) {
jos.write(newBytes, 0, size);
size = data.read(newBytes);
}
data.close();
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.jardiff;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
/**
* Patcher describes the necessary method to apply and create deltas.
*
* @version 1.8, 01/23/03
*/
public interface Patcher {
/**
* Applies a patch previously created with <code>createPatch</code>.
* Pass in a delegate to be notified of the status of the patch.
*/
public void applyPatch(PatchDelegate delegate, String oldJarPath,
String deltaPath, OutputStream result) throws IOException;
/**
* Callback used when patching a file.
*/
public interface PatchDelegate {
public void patching(int percentDone);
}
}

View File

@ -0,0 +1,41 @@
#
# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# -Redistribution of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# -Redistribution in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# Neither the name of Oracle nor the names of contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# This software is provided "AS IS," without a warranty of any kind. ALL
# EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
# ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
# OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
# AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
# AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
# DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
# REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
# INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
# OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
# EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
#
# You acknowledge that this software is not designed, licensed or intended
# for use in the design, construction, operation or maintenance of any
# nuclear facility.
#
jardiff.error.create=Unable to successfully create
jardiff.error.apply=Unable to successfully apply
jardiff.error.noindex=Invalid jardiff, no index!
jardiff.error.badheader=Invalid jardiff header:
jardiff.error.badremove=Invalid remove command:
jardiff.error.badmove=Invalid move command:
jardiff.error.badcommand=Invalid command:

View File

@ -0,0 +1,227 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
import java.io.File;
import java.util.ArrayList;
import javax.servlet.*;
import javax.servlet.http.*;
/**
* The DownloadRequest incapsulates all the data in a request
* SQE: We need to address query string
*/
public class DownloadRequest {
// Arguments
private static final String ARG_ARCH = "arch";
private static final String ARG_OS = "os";
private static final String ARG_LOCALE = "locale";
private static final String ARG_VERSION_ID = "version-id";
private static final String ARG_CURRENT_VERSION_ID = "current-version-id";
private static final String ARG_PLATFORM_VERSION_ID = "platform-version-id";
private static final String ARG_KNOWN_PLATFORMS = "known-platforms";
private static final String TEST_JRE = "TestJRE";
private String _path = null;
private String _version = null;
private String _currentVersionId = null;
private String[] _os = null;
private String[] _arch = null;
private String[] _locale = null;
private String[] _knownPlatforms = null;
private String _query = null;
private String _testJRE = null;
private boolean _isPlatformRequest = false;
private ServletContext _context = null;
private String _encoding = null;
private HttpServletRequest _httpRequest = null;
// HTTP Compression RFC 2616 : Standard headers
public static final String ACCEPT_ENCODING = "accept-encoding";
// Contruct Request object based on HTTP request
public DownloadRequest(HttpServletRequest request) {
this((ServletContext)null, request);
}
public DownloadRequest(ServletContext context, HttpServletRequest request) {
_context = context;
_httpRequest = request;
_path = request.getRequestURI();
_encoding = request.getHeader(ACCEPT_ENCODING);
String context_path = request.getContextPath();
if (context_path != null) _path = _path.substring(context_path.length());
if (_path == null) _path = request.getServletPath(); // This works for *.<ext> invocations
if (_path == null) _path = "/"; // No path given
_path = _path.trim();
if (_context != null && !_path.endsWith("/")) {
String realPath = _context.getRealPath(_path);
// fix for 4474021 - getRealPath might returns NULL
if (realPath != null) {
File f = new File(realPath);
if (f != null && f.exists() && f.isDirectory()) {
_path += "/";
}
}
}
// Append default file for a directory
if (_path.endsWith("/")) _path += "launch.jnlp";
_version = getParameter(request, ARG_VERSION_ID);
_currentVersionId = getParameter(request, ARG_CURRENT_VERSION_ID);
_os = getParameterList(request, ARG_OS);
_arch = getParameterList(request, ARG_ARCH);
_locale = getParameterList(request, ARG_LOCALE);
_knownPlatforms = getParameterList(request, ARG_KNOWN_PLATFORMS);
String platformVersion = getParameter(request, ARG_PLATFORM_VERSION_ID);
_isPlatformRequest = (platformVersion != null);
if (_isPlatformRequest) _version = platformVersion;
_query = request.getQueryString();
_testJRE = getParameter(request, TEST_JRE);
}
/** Returns a DownloadRequest for the currentVersionId, that can be used
* to lookup the existing cached version
*/
private DownloadRequest(DownloadRequest dreq) {
_encoding = dreq._encoding;
_context = dreq._context;
_httpRequest = dreq._httpRequest;
_path = dreq._path;
_version = dreq._currentVersionId;
_currentVersionId = null;
_os = dreq._os;
_arch = dreq._arch;
_locale = dreq._locale;
_knownPlatforms = dreq._knownPlatforms;
_isPlatformRequest = dreq._isPlatformRequest;
_query = dreq._query;
_testJRE = dreq._testJRE;
}
private String getParameter(HttpServletRequest req, String key) {
String res = req.getParameter(key);
return (res == null) ? null : res.trim();
}
/** Converts a space delimitered string to a list of strings */
static private String[] getStringList(String str) {
if (str == null) return null;
ArrayList list = new ArrayList();
int i = 0;
int length = str.length();
StringBuffer sb = null;
while(i < length) {
char ch = str.charAt(i);
if (ch == ' ') {
// A space was hit. Add string to list
if (sb != null) {
list.add(sb.toString());
sb = null;
}
} else if (ch == '\\') {
// It is a delimiter. Add next character
if (i + 1 < length) {
ch = str.charAt(++i);
if (sb == null) sb = new StringBuffer();
sb.append(ch);
}
} else {
if (sb == null) sb = new StringBuffer();
sb.append(ch);
}
i++; // Next character
}
// Make sure to add the last part to the list too
if (sb != null) {
list.add(sb.toString());
}
if (list.size() == 0) return null;
String[] results = new String[list.size()];
return (String[])list.toArray(results);
}
/* Split parameter at spaces. Convert '\ ' insto a space */
private String[] getParameterList(HttpServletRequest req, String key) {
String res = req.getParameter(key);
return (res == null) ? null : getStringList(res.trim());
}
// Query
public String getPath() { return _path; }
public String getVersion() { return _version; }
public String getCurrentVersionId() { return _currentVersionId; }
public String getQuery() { return _query; }
public String getTestJRE() { return _testJRE; }
public String getEncoding() { return _encoding; }
public String[] getOS() { return _os; }
public String[] getArch() { return _arch; }
public String[] getLocale() { return _locale; }
public String[] getKnownPlatforms() { return _knownPlatforms; }
public boolean isPlatformRequest() { return _isPlatformRequest; }
public HttpServletRequest getHttpRequest() { return _httpRequest; }
/** Returns a DownloadRequest for the currentVersionId, that can be used
* to lookup the existing cached version
*/
DownloadRequest getFromDownloadRequest() {
return new DownloadRequest(this);
}
// Debug
public String toString() {
return "DownloadRequest[path=" + _path +
showEntry(" encoding=", _encoding) +
showEntry(" query=", _query) +
showEntry(" TestJRE=", _testJRE) +
showEntry(" version=", _version) +
showEntry(" currentVersionId=", _currentVersionId) +
showEntry(" os=", _os) +
showEntry(" arch=", _arch) +
showEntry(" locale=", _locale) +
showEntry(" knownPlatforms=", _knownPlatforms)
+ " isPlatformRequest=" + _isPlatformRequest + "]";
}
private String showEntry(String msg, String value) {
if (value == null) return "";
return msg + value;
}
private String showEntry(String msg, String[] value) {
if (value == null) return "";
return msg + java.util.Arrays.asList(value).toString();
}
}

View File

@ -0,0 +1,296 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
import java.io.*;
import java.util.*;
import java.net.URL;
import java.net.URLConnection;
import javax.servlet.http.HttpServletResponse;
/**
* A class used to encapsulate a file response, and
* factory methods to create some common types.
*/
abstract public class DownloadResponse {
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String HEADER_JNLP_VERSION = "x-java-jnlp-version-id";
private static final String JNLP_ERROR_MIMETYPE = "application/x-java-jnlp-error";
public static final int STS_00_OK = 0;
public static final int ERR_10_NO_RESOURCE = 10;
public static final int ERR_11_NO_VERSION = 11;
public static final int ERR_20_UNSUP_OS = 20;
public static final int ERR_21_UNSUP_ARCH = 21;
public static final int ERR_22_UNSUP_LOCALE = 22;
public static final int ERR_23_UNSUP_JRE = 23;
public static final int ERR_99_UNKNOWN = 99;
// HTTP Compression RFC 2616 : Standard headers
public static final String CONTENT_ENCODING = "content-encoding";
// HTTP Compression RFC 2616 : Standard header for HTTP/Pack200 Compression
public static final String GZIP_ENCODING = "gzip";
public static final String PACK200_GZIP_ENCODING = "pack200-gzip";
public DownloadResponse() { /* do nothing */ }
public String toString() { return getClass().getName(); }
/** Post information to an HttpResponse */
abstract void sendRespond(HttpServletResponse response) throws IOException;
/** Factory methods for error responses */
static DownloadResponse getNotFoundResponse() { return new NotFoundResponse(); }
static DownloadResponse getNoContentResponse() { return new NotFoundResponse(); }
static DownloadResponse getJnlpErrorResponse(int jnlpErrorCode) { return new JnlpErrorResponse(jnlpErrorCode); }
/** Factory method for file download responses */
static DownloadResponse getNotModifiedResponse() {
return new NotModifiedResponse();
}
static DownloadResponse getHeadRequestResponse(String mimeType,
String versionId, long lastModified, int contentLength) {
return new HeadRequestResponse(mimeType, versionId, lastModified,
contentLength);
}
static DownloadResponse getFileDownloadResponse(byte[] content, String mimeType, long timestamp, String versionId) {
return new ByteArrayFileDownloadResponse(content, mimeType, versionId, timestamp);
}
static DownloadResponse getFileDownloadResponse(URL resource, String mimeType, long timestamp, String versionId) {
return new ResourceFileDownloadResponse(resource, mimeType, versionId, timestamp);
}
static DownloadResponse getFileDownloadResponse(File file, String mimeType, long timestamp, String versionId) {
return new DiskFileDownloadResponse(file, mimeType, versionId, timestamp);
}
//
// Private classes implementing the various types
//
static private class NotModifiedResponse extends DownloadResponse {
public void sendRespond(HttpServletResponse response) throws
IOException {
response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
}
}
static private class NotFoundResponse extends DownloadResponse {
public void sendRespond(HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
static private class NoContentResponse extends DownloadResponse {
public void sendRespond(HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_NO_CONTENT);
}
}
static private class HeadRequestResponse extends DownloadResponse {
private String _mimeType;
private String _versionId;
private long _lastModified;
private int _contentLength;
HeadRequestResponse(String mimeType, String versionId,
long lastModified, int contentLength) {
_mimeType = mimeType;
_versionId = versionId;
_lastModified = lastModified;
_contentLength = contentLength;
}
/** Post information to an HttpResponse */
public void sendRespond(HttpServletResponse response) throws
IOException {
// Set header information
response.setContentType(_mimeType);
response.setContentLength(_contentLength);
if (_versionId != null) {
response.setHeader(HEADER_JNLP_VERSION, _versionId);
}
if (_lastModified != 0) {
response.setDateHeader(HEADER_LASTMOD, _lastModified);
}
response.sendError(HttpServletResponse.SC_OK);
}
}
static public class JnlpErrorResponse extends DownloadResponse {
private String _message;
public JnlpErrorResponse(int jnlpErrorCode) {
String msg = Integer.toString(jnlpErrorCode);
String dsc = "No description";
try {
dsc = JnlpDownloadServlet.getResourceBundle().getString("servlet.jnlp.err." + msg);
} catch (MissingResourceException mre) { /* ignore */}
_message = msg + " " + dsc;
}
public void sendRespond(HttpServletResponse response) throws IOException {
response.setContentType(JNLP_ERROR_MIMETYPE);
PrintWriter pw = response.getWriter();
pw.println(_message);
};
public String toString() { return super.toString() + "[" + _message + "]"; }
}
static private abstract class FileDownloadResponse extends DownloadResponse {
private String _mimeType;
private String _versionId;
private long _lastModified;
private String _fileName;
FileDownloadResponse(String mimeType, String versionId, long lastModified) {
_mimeType = mimeType;
_versionId = versionId;
_lastModified = lastModified;
_fileName = null;
}
FileDownloadResponse(String mimeType, String versionId, long lastModified, String fileName) {
_mimeType = mimeType;
_versionId = versionId;
_lastModified = lastModified;
_fileName = fileName;
}
/** Information about response */
String getMimeType() { return _mimeType; }
String getVersionId() { return _versionId; }
long getLastModified() { return _lastModified; }
abstract int getContentLength() throws IOException;
abstract InputStream getContent() throws IOException;
/** Post information to an HttpResponse */
public void sendRespond(HttpServletResponse response) throws IOException {
// Set header information
response.setContentType(getMimeType());
response.setContentLength(getContentLength());
if (getVersionId() != null) response.setHeader(HEADER_JNLP_VERSION, getVersionId());
if (getLastModified() != 0) response.setDateHeader(HEADER_LASTMOD, getLastModified());
if (_fileName != null) {
if (_fileName.endsWith(".pack.gz")) {
response.setHeader(CONTENT_ENCODING, PACK200_GZIP_ENCODING );
} else if (_fileName.endsWith(".gz")) {
response.setHeader(CONTENT_ENCODING, GZIP_ENCODING );
} else {
response.setHeader(CONTENT_ENCODING, null);
}
}
// Send contents
InputStream in = getContent();
OutputStream out = response.getOutputStream();
try {
byte[] bytes = new byte[32 * 1024];
int read;
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
} finally {
if (in != null) in.close();
}
}
protected String getArgString() {
long length = 0;
try {
length = getContentLength();
} catch(IOException ioe) { /* ignore */ }
return "Mimetype=" + getMimeType() +
" VersionId=" + getVersionId() +
" Timestamp=" + new Date(getLastModified()) +
" Length=" + length;
}
}
static private class ByteArrayFileDownloadResponse extends FileDownloadResponse {
private byte[] _content;
ByteArrayFileDownloadResponse(byte[] content, String mimeType, String versionId, long lastModified) {
super(mimeType, versionId, lastModified);
_content = content;
}
int getContentLength() { return _content.length; }
InputStream getContent() { return new ByteArrayInputStream(_content); }
public String toString() { return super.toString() + "[ " + getArgString() + "]"; }
}
static private class ResourceFileDownloadResponse extends FileDownloadResponse {
URL _url;
ResourceFileDownloadResponse(URL url, String mimeType, String versionId, long lastModified) {
super(mimeType, versionId, lastModified, url.toString());
_url= url;
}
int getContentLength() throws IOException {
return _url.openConnection().getContentLength();
}
InputStream getContent() throws IOException {
return _url.openConnection().getInputStream();
}
public String toString() { return super.toString() + "[ " + getArgString() + "]"; }
}
static private class DiskFileDownloadResponse extends FileDownloadResponse {
private File _file;
DiskFileDownloadResponse(File file, String mimeType, String versionId, long lastModified) {
super(mimeType, versionId, lastModified, file.getName());
_file = file;
}
int getContentLength() throws IOException {
return (int)_file.length();
}
InputStream getContent() throws IOException {
return new BufferedInputStream(new FileInputStream(_file));
}
public String toString() { return super.toString() + "[ " + getArgString() + "]"; }
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
/** An exception that holds a DownloadResponse object.
* This exception can be thrown with the content describing
* the message that should be returned in the HTTP respond
*/
public class ErrorResponseException extends Exception {
private DownloadResponse _downloadResponse;
public ErrorResponseException(DownloadResponse downloadResponse) {
_downloadResponse = downloadResponse;
}
public DownloadResponse getDownloadResponse() { return _downloadResponse; }
public String toString() { return _downloadResponse.toString(); }
}

View File

@ -0,0 +1,388 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
import java.io.*;
import java.util.*;
import jnlp.sample.jardiff.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import jnlp.sample.util.VersionString;
import java.net.URL;
/*
* A class that generates and caches information about JarDiff files
*
*/
public class JarDiffHandler {
final private Logger log = Red5LoggerFactory.getLogger(JarDiffHandler.class, "screenshare");
// Default size of download buffer
private static final int BUF_SIZE = 32 * 1024;
// Default JARDiff mime type
private static final String JARDIFF_MIMETYPE = "application/x-java-archive-diff";
/** List of all generated JARDiffs */
private HashMap _jarDiffEntries = null;
/** Reference to ServletContext and logger object */
private ServletContext _servletContext = null;
private String _jarDiffMimeType = null;
/* Contains information about a particular JARDiff entry */
private static class JarDiffKey implements Comparable{
private String _name; // Name of file
private String _fromVersionId; // From version
private String _toVersionId; // To version
private boolean _minimal; // True if this is a minimal jardiff
/** Constructor used to generate a query object */
public JarDiffKey(String name, String fromVersionId, String toVersionId, boolean minimal) {
_name = name;
_fromVersionId = fromVersionId;
_toVersionId = toVersionId;
_minimal = minimal;
}
// Query methods
public String getName() { return _name; }
public String getFromVersionId() { return _fromVersionId; }
public String getToVersionId() { return _toVersionId; }
public boolean isMinimal() { return _minimal; }
// Collection framework interface methods
public int compareTo(Object o) {
// All non JarDiff entries are less
if (!(o instanceof JarDiffKey)) return -1;
JarDiffKey other = (JarDiffKey)o;
int n = _name.compareTo(other.getName());
if (n != 0) return n;
n = _fromVersionId.compareTo(other.getFromVersionId());
if (n != 0) return n;
if (_minimal != other.isMinimal()) return -1;
return _toVersionId.compareTo(other.getToVersionId());
}
public boolean equals(Object o) {
return compareTo(o) == 0;
}
public int hashCode() {
return _name.hashCode() +
_fromVersionId.hashCode() +
_toVersionId.hashCode();
}
}
static private class JarDiffEntry {
private File _jardiffFile; // Location of JARDiff file
public JarDiffEntry(File jarDiffFile) {
_jardiffFile = jarDiffFile;
}
public File getJarDiffFile() { return _jardiffFile; }
}
/** Initialize JarDiff handler */
public JarDiffHandler(ServletContext servletContext) {
_jarDiffEntries = new HashMap();
_servletContext = servletContext;
_jarDiffMimeType = _servletContext.getMimeType("xyz.jardiff");
if (_jarDiffMimeType == null) _jarDiffMimeType = JARDIFF_MIMETYPE;
}
/** Returns a JarDiff for the given request */
public synchronized DownloadResponse getJarDiffEntry(ResourceCatalog catalog, DownloadRequest dreq, JnlpResource res) {
if (dreq.getCurrentVersionId() == null) return null;
// check whether the request is from javaws 1.0/1.0.1
// do not generate minimal jardiff if it is from 1.0/1.0.1
boolean doJarDiffWorkAround = isJavawsVersion(dreq, "1.0*");
// First do a lookup to find a match
JarDiffKey key = new JarDiffKey(res.getName(),
dreq.getCurrentVersionId(),
res.getReturnVersionId(),
!doJarDiffWorkAround);
JarDiffEntry entry = (JarDiffEntry)_jarDiffEntries.get(key);
// If entry is not found, then the querty has not been made.
if (entry == null) {
if (log.isInfoEnabled()) {
log.info("servlet.log.info.jardiff.gen",
res.getName(),
dreq.getCurrentVersionId(),
res.getReturnVersionId());
}
File f = generateJarDiff(catalog, dreq, res, doJarDiffWorkAround);
if (f == null) {
log.warn("servlet.log.warning.jardiff.failed",
res.getName(),
dreq.getCurrentVersionId(),
res.getReturnVersionId());
}
// Store entry in table
entry = new JarDiffEntry(f);
_jarDiffEntries.put(key, entry);
}
// Check for no JarDiff to return
if (entry.getJarDiffFile() == null) {
return null;
} else {
return DownloadResponse.getFileDownloadResponse(entry.getJarDiffFile(),
_jarDiffMimeType,
entry.getJarDiffFile().lastModified(),
res.getReturnVersionId());
}
}
public static boolean isJavawsVersion(DownloadRequest dreq, String version) {
String javawsAgent = "javaws";
String jwsVer = dreq.getHttpRequest().getHeader("User-Agent");
// check the request is coming from javaws
if (!jwsVer.startsWith("javaws-")) {
// this is the new style User-Agent string
// User-Agent: JNLP/1.0.1 javaws/1.4.2 (b28) J2SE/1.4.2
StringTokenizer st = new StringTokenizer(jwsVer);
while (st.hasMoreTokens()) {
String verString = st.nextToken();
int index = verString.indexOf(javawsAgent);
if (index != -1) {
verString = verString.substring(index + javawsAgent.length() + 1);
return VersionString.contains(version, verString);
}
}
return false;
}
// extract the version id from the download request
int startIndex = jwsVer.indexOf("-");
if (startIndex == -1) {
return false;
}
int endIndex = jwsVer.indexOf("/");
if (endIndex == -1 || endIndex < startIndex) {
return false;
}
String verId = jwsVer.substring(startIndex + 1, endIndex);
// check whether the versionString contains the versionId
return VersionString.contains(version, verId);
}
/** Download resource to the given file */
private boolean download(URL target, File file) {
log.debug("JarDiffHandler: Doing download");
boolean ret = true;
boolean delete = false;
// use bufferedstream for better performance
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(target.openStream());
out = new BufferedOutputStream(new FileOutputStream(file));
int read = 0;
int totalRead = 0;
byte[] buf = new byte[BUF_SIZE];
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
totalRead += read;
}
log.debug("total read: " + totalRead);
log.debug("Wrote URL " + target.toString() + " to file " + file);
} catch(IOException ioe) {
log.debug("Got exception while downloading resource: " + ioe);
ret = false;
if (file != null) delete = true;
} finally {
try {
in.close();
in = null;
} catch (IOException ioe) {
log.debug("Got exception while downloading resource: " + ioe);
}
try {
out.close();
out = null;
} catch (IOException ioe) {
log.debug("Got exception while downloading resource: " + ioe);
}
if (delete) {
file.delete();
}
}
return ret;
}
// fix for 4720897
// if the jar file resides in a war file, download it to a temp dir
// so it can be used to generate jardiff
private String getRealPath(String path) throws IOException{
URL fileURL = _servletContext.getResource(path);
File tempDir = (File)_servletContext.getAttribute("javax.servlet.context.tempdir");
// download file into temp dir
if (fileURL != null) {
File newFile = File.createTempFile("temp", ".jar", tempDir);
if (download(fileURL, newFile)) {
String filePath = newFile.getPath();
return filePath;
}
}
return null;
}
private File generateJarDiff(ResourceCatalog catalog, DownloadRequest dreq, JnlpResource res, boolean doJarDiffWorkAround) {
boolean del_old = false;
boolean del_new = false;
// Lookup up file for request version
DownloadRequest fromDreq = dreq.getFromDownloadRequest();
try {
JnlpResource fromRes = catalog.lookupResource(fromDreq);
/* Get file locations */
String newFilePath = _servletContext.getRealPath(res.getPath());
String oldFilePath = _servletContext.getRealPath(fromRes.getPath());
// fix for 4720897
if (newFilePath == null) {
newFilePath = getRealPath(res.getPath());
if (newFilePath != null) del_new = true;
}
if (oldFilePath == null) {
oldFilePath = getRealPath(fromRes.getPath());
if (oldFilePath != null) del_old = true;
}
if (newFilePath == null || oldFilePath == null) {
return null;
}
// Create temp. file to store JarDiff file in
File tempDir = (File)_servletContext.getAttribute("javax.servlet.context.tempdir");
// fix for 4653036: JarDiffHandler() should use javax.servlet.context.tempdir to store the jardiff
File outputFile = File.createTempFile("jnlp", ".jardiff", tempDir);
log.debug("Generating Jardiff between " + oldFilePath + " and " +
newFilePath + " Store in " + outputFile);
// Generate JarDiff
OutputStream os = new FileOutputStream(outputFile);
JarDiff.createPatch(oldFilePath, newFilePath, os, !doJarDiffWorkAround);
os.close();
try {
// Check that Jardiff is smaller, or return null
if (outputFile.length() >= (new File(newFilePath).length())) {
log.debug("JarDiff discarded - since it is bigger");
return null;
}
// Check that Jardiff is smaller than the packed version of
// the new file, if the file exists at all
File newFilePacked = new File(newFilePath + ".pack.gz");
if (newFilePacked.exists()) {
log.debug("generated jardiff size: " + outputFile.length());
log.debug("packed requesting file size: " + newFilePacked.length());
if (outputFile.length() >= newFilePacked.length()) {
log.debug("JarDiff discarded - packed version of requesting file is smaller");
return null;
}
}
log.debug("JarDiff generation succeeded");
return outputFile;
} finally {
// delete the temporarily downloaded file
if (del_new) {
new File(newFilePath).delete();
}
if (del_old) {
new File(oldFilePath).delete();
}
}
} catch(IOException ioe) {
log.debug("Failed to genereate jardiff", ioe);
return null;
} catch(ErrorResponseException ere) {
log.debug("Failed to genereate jardiff", ere);
return null;
}
}
}

View File

@ -0,0 +1,249 @@
package jnlp.sample.servlet;
import java.io.*;
import java.util.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.bigbluebutton.app.screenshare.IScreenShareApplication;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
/**
* This Servlet class is an implementation of JNLP Specification's
* Download Protocols.
*
* All requests to this servlet is in the form of HTTP GET commands.
* The parameters that are needed are:
* <ul>
* <li><code>arch</code>,
* <li><code>os</code>,
* <li><code>locale</code>,
* <li><code>version-id</code> or <code>platform-version-id</code>,
* <li><code>current-version-id</code>,
* <li>code>known-platforms</code>
* </ul>
* <p>
*
* @version 1.8 01/23/03
*/
public class JnlpDownloadServlet extends HttpServlet {
final private Logger log = Red5LoggerFactory.getLogger(JnlpDownloadServlet.class, "screenshare");
// Localization
private static ResourceBundle _resourceBundle = null;
// Servlet configuration
private static final String PARAM_JNLP_EXTENSION = "jnlp-extension";
private static final String PARAM_JAR_EXTENSION = "jar-extension";
private JnlpFileHandler _jnlpFileHandler = null;
private JarDiffHandler _jarDiffHandler = null;
private ResourceCatalog _resourceCatalog = null;
/** Initialize servlet */
public void init(ServletConfig config) throws ServletException {
super.init(config);
// Get extension from Servlet configuration, or use default
JnlpResource.setDefaultExtensions(
config.getInitParameter(PARAM_JNLP_EXTENSION),
config.getInitParameter(PARAM_JAR_EXTENSION));
_jnlpFileHandler = new JnlpFileHandler(config.getServletContext());
_jarDiffHandler = new JarDiffHandler(config.getServletContext());
_resourceCatalog = new ResourceCatalog(config.getServletContext());
}
public static synchronized ResourceBundle getResourceBundle() {
if (_resourceBundle == null) {
_resourceBundle = ResourceBundle.getBundle("jnlp/sample/servlet/resources/strings");
}
return _resourceBundle;
}
public void doHead(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
handleRequest(request, response, true);
}
/** We handle get requests too - eventhough the spec. only requeres POST requests */
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(request, response, false);
}
private void handleRequest(HttpServletRequest request,
HttpServletResponse response, boolean isHead) throws IOException {
String requestStr = request.getRequestURI();
if (request.getQueryString() != null) requestStr += "?" + request.getQueryString().trim();
// Parse HTTP request
DownloadRequest dreq = new DownloadRequest(getServletContext(), request);
if (log.isInfoEnabled()) {
log.info("servlet.log.info.request", requestStr);
log.info("servlet.log.info.useragent", request.getHeader("User-Agent"));
}
if (log.isDebugEnabled()) {
log.debug(dreq.toString());
}
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
// Check if it is a valid request
try {
// Check if the request is valid
validateRequest(dreq);
// Decide what resource to return
JnlpResource jnlpres = locateResource(dreq);
log.debug("JnlpResource: " + jnlpres);
if (log.isInfoEnabled()) {
log.info("servlet.log.info.goodrequest", jnlpres.getPath());
}
DownloadResponse dres = null;
if (isHead) {
int cl =
jnlpres.getResource().openConnection().getContentLength();
// head request response
dres = DownloadResponse.getHeadRequestResponse(
jnlpres.getMimeType(), jnlpres.getVersionId(),
jnlpres.getLastModified(), cl);
} else if (ifModifiedSince != -1 &&
(ifModifiedSince / 1000) >=
(jnlpres.getLastModified() / 1000)) {
// We divide the value returned by getLastModified here by 1000
// because if protocol is HTTP, last 3 digits will always be
// zero. However, if protocol is JNDI, that's not the case.
// so we divide the value by 1000 to remove the last 3 digits
// before comparison
// return 304 not modified if possible
log.debug("return 304 Not modified");
dres = DownloadResponse.getNotModifiedResponse();
} else {
// Return selected resource
dres = constructResponse(jnlpres, dreq);
}
dres.sendRespond(response);
} catch(ErrorResponseException ere) {
if (log.isInfoEnabled()) {
log.info("servlet.log.info.badrequest", requestStr);
}
if (log.isDebugEnabled()) {
log.debug("Response: "+ ere.toString());
}
// Return response from exception
ere.getDownloadResponse().sendRespond(response);
} catch(Throwable e) {
log.error("servlet.log.fatal.internalerror", e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
/** Make sure that it is a valid request. This is also the place to implement the
* reverse IP lookup
*/
private void validateRequest(DownloadRequest dreq) throws ErrorResponseException {
String path = dreq.getPath();
if (path.endsWith(ResourceCatalog.VERSION_XML_FILENAME) ||
path.indexOf("__") != -1 ) {
throw new ErrorResponseException(DownloadResponse.getNoContentResponse());
}
}
/** Interprets the download request and convert it into a resource that is
* part of the Web Archive.
*/
private JnlpResource locateResource(DownloadRequest dreq) throws IOException, ErrorResponseException {
if (dreq.getVersion() == null) {
return handleBasicDownload(dreq);
} else {
return handleVersionRequest(dreq);
}
}
private JnlpResource handleBasicDownload(DownloadRequest dreq) throws ErrorResponseException, IOException {
log.debug("Basic Protocol lookup");
// Do not return directory names for basic protocol
if (dreq.getPath() == null || dreq.getPath().endsWith("/")) {
throw new ErrorResponseException(DownloadResponse.getNoContentResponse());
}
// Lookup resource
JnlpResource jnlpres = new JnlpResource(getServletContext(), dreq.getPath());
if (!jnlpres.exists()) {
throw new ErrorResponseException(DownloadResponse.getNoContentResponse());
}
return jnlpres;
}
private JnlpResource handleVersionRequest(DownloadRequest dreq) throws IOException, ErrorResponseException {
log.debug("Version-based/Extension based lookup");
return _resourceCatalog.lookupResource(dreq);
}
/** Given a DownloadPath and a DownloadRequest, it constructs the data stream to return
* to the requester
*/
private DownloadResponse constructResponse(JnlpResource jnlpres, DownloadRequest dreq) throws IOException {
String path = jnlpres.getPath();
if (jnlpres.isJnlpFile()) {
// It is a JNLP file. It need to be macro-expanded, so it is handled differently
boolean supportQuery = JarDiffHandler.isJavawsVersion(dreq, "1.5+");
log.debug("SupportQuery in Href: " + supportQuery);
// only support query string in href for 1.5 and above
if (supportQuery) {
return _jnlpFileHandler.getJnlpFileEx(jnlpres, dreq);
} else {
return _jnlpFileHandler.getJnlpFile(jnlpres, dreq);
}
}
// Check if a JARDiff can be returned
if (dreq.getCurrentVersionId() != null && jnlpres.isJarFile()) {
DownloadResponse response = _jarDiffHandler.getJarDiffEntry(_resourceCatalog, dreq, jnlpres);
if (response != null) {
log.info("servlet.log.info.jardiff.response");
return response;
}
}
// check and see if we can use pack resource
JnlpResource jr = new JnlpResource(getServletContext(),
jnlpres.getName(),
jnlpres.getVersionId(),
jnlpres.getOSList(),
jnlpres.getArchList(),
jnlpres.getLocaleList(),
jnlpres.getPath(),
jnlpres.getReturnVersionId(),
dreq.getEncoding());
log.debug("Real resource returned: " + jr);
// Return WAR file resource
return DownloadResponse.getFileDownloadResponse(jr.getResource(),
jr.getMimeType(),
jr.getLastModified(),
jr.getReturnVersionId());
}
}

View File

@ -0,0 +1,495 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
import java.util.*;
import java.util.regex.*;
import java.net.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.bigbluebutton.app.screenshare.ScreenShareInfo;
import org.bigbluebutton.app.screenshare.ScreenShareInfoResponse;
import org.bigbluebutton.app.screenshare.server.servlet.JnlpConfigurator;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.w3c.dom.*;
/* The JNLP file handler implements a class that keeps
* track of JNLP files and their specializations
*/
public class JnlpFileHandler {
final private Logger log = Red5LoggerFactory.getLogger(JnlpFileHandler.class, "screenshare");
private static final String JNLP_MIME_TYPE = "application/x-java-jnlp-file";
private static final String HEADER_LASTMOD = "Last-Modified";
private ServletContext _servletContext;
private HashMap<String, JnlpFileEntry> _jnlpFiles = null;
private boolean hasConfigurator = false;
private JnlpConfigurator configurator = null;
/** Initialize JnlpFileHandler for the specific ServletContext */
public JnlpFileHandler(ServletContext servletContext) {
_servletContext = servletContext;
_jnlpFiles = new HashMap<String, JnlpFileEntry>();
}
private static class JnlpFileEntry {
// Response
DownloadResponse _response;
// Keeps track of cache is out of date
private long _lastModified;
// Constructor
JnlpFileEntry(DownloadResponse response, long lastmodfied) {
_response = response;
_lastModified = lastmodfied;
}
public DownloadResponse getResponse() { return _response; }
long getLastModified() { return _lastModified; }
}
/* Main method to lookup an entry */
public synchronized DownloadResponse getJnlpFile(JnlpResource jnlpres, DownloadRequest dreq)
throws IOException {
log.debug("In getJnlpFile");
String path = jnlpres.getPath();
URL resource = jnlpres.getResource();
long lastModified = jnlpres.getLastModified();
log.debug("lastModified: " + lastModified + " " + new Date(lastModified));
if (lastModified == 0) {
log.warn("servlet.log.warning.nolastmodified", path);
}
// fix for 4474854: use the request URL as key to look up jnlp file
// in hash map
String reqUrl = HttpUtils.getRequestURL(dreq.getHttpRequest()).toString();
// Read information from WAR file
long timeStamp = new java.util.Date().getTime();;
String mimeType = _servletContext.getMimeType(path);
if (mimeType == null) mimeType = JNLP_MIME_TYPE;
StringBuffer jnlpFileTemplate = new StringBuffer();
URLConnection conn = resource.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line = br.readLine();
if (line != null && line.startsWith("TS:")) {
timeStamp = parseTimeStamp(line.substring(3));
log.debug("Timestamp: " + timeStamp + " " + new Date(timeStamp));
if (timeStamp == 0) {
log.warn("servlet.log.warning.notimestamp", path);
timeStamp = lastModified;
}
line = br.readLine();
}
while(line != null) {
jnlpFileTemplate.append(line);
line = br.readLine();
}
if (! hasConfigurator) {
configurator = getConfigurator();
if (configurator != null) hasConfigurator = true;
}
String jnlpFileContent = specializeJnlpTemplate(dreq.getHttpRequest(), path, jnlpFileTemplate.toString());
// Convert to bytes as a UTF-8 encoding
byte[] byteContent = jnlpFileContent.getBytes("UTF-8");
timeStamp = new java.util.Date().getTime();
// Create entry
DownloadResponse resp = DownloadResponse.getFileDownloadResponse(byteContent,
mimeType,
timeStamp,
jnlpres.getReturnVersionId());
log.debug("JNLP: mime=[" + mimeType + "] timestamp=[" + timeStamp + "] version=[" + jnlpres.getReturnVersionId() + "]");
return resp;
}
/* Main method to lookup an entry (NEW for JavaWebStart 1.5+) */
public synchronized DownloadResponse getJnlpFileEx(JnlpResource jnlpres, DownloadRequest dreq)
throws IOException {
log.debug("In getJnlpFileEx");
String path = jnlpres.getPath();
URL resource = jnlpres.getResource();
long lastModified = jnlpres.getLastModified();
log.debug("lastModified: " + lastModified + " " + new Date(lastModified));
if (lastModified == 0) {
log.warn("servlet.log.warning.nolastmodified", path);
}
if (! hasConfigurator) {
configurator = getConfigurator();
if (configurator != null) hasConfigurator = true;
}
// fix for 4474854: use the request URL as key to look up jnlp file
// in hash map
String reqUrl = HttpUtils.getRequestURL(dreq.getHttpRequest()).toString();
// SQE: To support query string, we changed the hash key from Request URL to (Request URL + query string)
if (dreq.getQuery() != null)
reqUrl += dreq.getQuery();
// Read information from WAR file
long timeStamp = lastModified;
String mimeType = _servletContext.getMimeType(path);
if (mimeType == null) mimeType = JNLP_MIME_TYPE;
StringBuffer jnlpFileTemplate = new StringBuffer();
URLConnection conn = resource.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line = br.readLine();
if (line != null && line.startsWith("TS:")) {
timeStamp = parseTimeStamp(line.substring(3));
log.debug("Timestamp: " + timeStamp + " " + new Date(timeStamp));
if (timeStamp == 0) {
log.warn("servlet.log.warning.notimestamp", path);
timeStamp = lastModified;
}
line = br.readLine();
}
while(line != null) {
jnlpFileTemplate.append(line);
line = br.readLine();
}
String jnlpFileContent = specializeJnlpTemplate(dreq.getHttpRequest(), path, jnlpFileTemplate.toString());
/* SQE: We need to add query string back to href in jnlp file. We also need to handle JRE requirement for
* the test. We reconstruct the xml DOM object, modify the value, then regenerate the jnlpFileContent.
*/
String query = dreq.getQuery();
String testJRE = dreq.getTestJRE();
log.debug("Double check query string: " + query);
// For backward compatibility: Always check if the href value exists.
// Bug 4939273: We will retain the jnlp template structure and will NOT add href value. Above old
// approach to always check href value caused some test case not run.
if (query != null) {
byte [] cb = jnlpFileContent.getBytes("UTF-8");
ByteArrayInputStream bis = new ByteArrayInputStream(cb);
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(bis);
if (document != null && document.getNodeType() == Node.DOCUMENT_NODE) {
boolean modified = false;
Element root = document.getDocumentElement();
if (root.hasAttribute("href") && query != null) {
String href = root.getAttribute("href");
root.setAttribute("href", href + "?" + query);
modified = true;
}
// Update version value for j2se tag
if (testJRE != null) {
NodeList j2seNL = root.getElementsByTagName("j2se");
if (j2seNL != null) {
Element j2se = (Element) j2seNL.item(0);
String ver = j2se.getAttribute("version");
if (ver.length() > 0) {
j2se.setAttribute("version", testJRE);
modified = true;
}
}
}
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
DOMSource source = new DOMSource(document);
StringWriter sw = new StringWriter();
StreamResult result = new StreamResult(sw);
transformer.transform(source, result);
jnlpFileContent = sw.toString();
log.debug("Converted jnlpFileContent: " + jnlpFileContent);
// Since we modified the file on the fly, we always update the timestamp value with current time
if (modified) {
timeStamp = new java.util.Date().getTime();
log.debug("Last modified on the fly: " + timeStamp);
}
}
} catch (Exception e) {
log.debug(e.toString(), e);
}
}
// Convert to bytes as a UTF-8 encoding
byte[] byteContent = jnlpFileContent.getBytes("UTF-8");
// Create entry
DownloadResponse resp = DownloadResponse.getFileDownloadResponse(byteContent,
mimeType,
timeStamp,
jnlpres.getReturnVersionId());
return resp;
}
/* This method performs the following substituations
* $$name
* $$codebase
* $$context
*/
private String specializeJnlpTemplate(HttpServletRequest request, String respath, String jnlpTemplate) {
log.debug("Query string = [" + request.getQueryString().trim() + "]");
String errorMessage = "NO_ERRORS";
String authParam = request.getParameter("authToken");
String authToken = "unknown";
if (authParam != null && authParam != "") {
authToken = authParam.trim();
} else {
errorMessage = "MISSING_AUTHTOKEN";
}
String fullScreen = request.getParameter("fullScreen");
if (fullScreen != null && fullScreen != "") {
fullScreen = fullScreen.trim();
} else {
errorMessage = "MISSING_FULLSCREEN";
}
String meetingId = request.getParameter("meetingId");
if (meetingId != null && meetingId != "") {
meetingId = meetingId.trim();
} else {
errorMessage = "MISSING_MEETINGID";
}
ScreenShareInfo sInfo = configurator.getScreenShareInfo(meetingId, authToken);
String publishUrl = "unknown";
String streamId = "unknown";
if (sInfo == null) {
errorMessage = "ERROR_GETTING_INFO_USING_TOKEN";
} else {
publishUrl = sInfo.publishUrl;
streamId = sInfo.streamId;
}
String jnlpUrl = configurator.getJnlpUrl();
String codecOptions = configurator.getCodecOptions();
log.debug("Codec Options = [" + codecOptions + "]");
String urlprefix = getUrlPrefix(request);
int idx = respath.lastIndexOf('/'); //
String name = respath.substring(idx + 1); // Exclude /
String codebase = respath.substring(0, idx + 1); // Include /
jnlpTemplate = substitute(jnlpTemplate, "$$name", name);
jnlpTemplate = substitute(jnlpTemplate, "$$jnlpUrl", jnlpUrl);
jnlpTemplate = substitute(jnlpTemplate, "$$serverUrl", jnlpUrl);
jnlpTemplate = substitute(jnlpTemplate, "$$publishUrl", publishUrl);
jnlpTemplate = substitute(jnlpTemplate, "$$fullScreen", fullScreen);
jnlpTemplate = substitute(jnlpTemplate, "$$meetingId", meetingId);
jnlpTemplate = substitute(jnlpTemplate, "$$streamId", streamId);
jnlpTemplate = substitute(jnlpTemplate, "$$codecOptions", codecOptions);
jnlpTemplate = substitute(jnlpTemplate, "$$errorMessage", errorMessage);
// fix for 5039951: Add $$hostname macro
jnlpTemplate = substitute(jnlpTemplate, "$$hostname", request.getServerName());
jnlpTemplate = substitute(jnlpTemplate, "$$codebase", urlprefix + request.getContextPath() + codebase);
jnlpTemplate = substitute(jnlpTemplate, "$$context", urlprefix + request.getContextPath());
// fix for 6256326: add $$site macro to sample jnlp servlet
jnlpTemplate = substitute(jnlpTemplate, "$$site", urlprefix);
log.debug(jnlpTemplate);
return jnlpTemplate;
}
// This code is heavily inspired by the stuff in HttpUtils.getRequestURL
private String getUrlPrefix(HttpServletRequest req) {
StringBuffer url = new StringBuffer();
String scheme = req.getScheme();
int port = req.getServerPort();
url.append(scheme); // http, https
url.append("://");
url.append(req.getServerName());
if ((scheme.equals("http") && port != 80)
|| (scheme.equals("https") && port != 443)) {
url.append(':');
url.append(req.getServerPort());
}
return url.toString();
}
private String substitute(String target, String key, String value) {
int start = 0;
do {
int idx = target.indexOf(key, start);
if (idx == -1) return target;
target = target.substring(0, idx) + value + target.substring(idx + key.length());
start = idx + value.length();
} while(true);
}
/** Parses a ISO 8601 Timestamp. The format of the timestamp is:
*
* YYYY-MM-DD hh:mm:ss or YYYYMMDDhhmmss
*
* Hours (hh) is in 24h format. ss are optional. Time are by default relative
* to the current timezone. Timezone information can be specified
* by:
*
* - Appending a 'Z', e.g., 2001-12-19 12:00Z
* - Appending +hh:mm, +hhmm, +hh, -hh:mm -hhmm, -hh to
* indicate that the locale timezone used is either the specified
* amound before or after GMT. For example,
*
* 12:00Z = 13:00+1:00 = 0700-0500
*
* The method returns 0 if it cannot pass the string. Otherwise, it is
* the number of milliseconds size sometime in 1969.
*/
private long parseTimeStamp(String timestamp) {
int YYYY = 0;
int MM = 0;
int DD = 0;
int hh = 0;
int mm = 0;
int ss = 0;
timestamp = timestamp.trim();
try {
// Check what format is used
if (matchPattern("####-##-## ##:##", timestamp)) {
YYYY = getIntValue(timestamp, 0, 4);
MM = getIntValue(timestamp, 5, 7);
DD = getIntValue(timestamp, 8, 10);
hh = getIntValue(timestamp, 11, 13);
mm = getIntValue(timestamp, 14, 16);
timestamp = timestamp.substring(16);
if (matchPattern(":##", timestamp)) {
ss = getIntValue(timestamp, 1, 3);
timestamp = timestamp.substring(3);
}
} else if (matchPattern("############", timestamp)) {
YYYY = getIntValue(timestamp, 0, 4);
MM = getIntValue(timestamp, 4, 6);
DD = getIntValue(timestamp, 6, 8);
hh = getIntValue(timestamp, 8, 10);
mm = getIntValue(timestamp, 10, 12);
timestamp = timestamp.substring(12);
if (matchPattern("##", timestamp)) {
ss = getIntValue(timestamp, 0, 2);
timestamp = timestamp.substring(2);
}
} else {
// Unknown format
return 0;
}
} catch(NumberFormatException e) {
// Bad number
return 0;
}
String timezone = null;
// Remove timezone information
timestamp = timestamp.trim();
if (timestamp.equalsIgnoreCase("Z")) {
timezone ="GMT";
} else if (timestamp.startsWith("+") || timestamp.startsWith("-")) {
timezone = "GMT" + timestamp;
}
if (timezone == null) {
// Date is relative to current locale
Calendar cal = Calendar.getInstance();
cal.set(YYYY, MM - 1, DD, hh, mm, ss);
return cal.getTime().getTime();
} else {
// Date is relative to a timezone
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(timezone));
cal.set(YYYY, MM - 1, DD, hh, mm, ss);
return cal.getTime().getTime();
}
}
private int getIntValue(String key, int start, int end) {
return Integer.parseInt(key.substring(start, end));
}
private boolean matchPattern(String pattern, String key) {
// Key must be longer than pattern
if (key.length() < pattern.length()) return false;
for(int i = 0; i < pattern.length(); i++) {
char format = pattern.charAt(i);
char ch = key.charAt(i);
if (!((format == '#' && Character.isDigit(ch)) || (format == ch))) {
return false;
}
}
return true;
}
private JnlpConfigurator getConfigurator() {
JnlpConfigurator jnlpConfigurator = null;
if (_servletContext != null) {
//Grab a reference to the application context
ApplicationContext appCtx = (ApplicationContext) _servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
//Get the bean holding the parameter
jnlpConfigurator = (JnlpConfigurator) appCtx.getBean("jnlpConfigurator");
if (jnlpConfigurator != null) {
log.debug("JnlpConfigurator initialized.");
} else {
log.error("Failed to initialize JnlpConfigurator.");
}
} else {
log.error("Failed to initialize JnlpConfigurator. Context is undefined.");
}
return jnlpConfigurator;
}
}

View File

@ -0,0 +1,266 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
import javax.servlet.ServletContext;
import java.net.URL;
import java.io.File;
import java.io.IOException;
import java.net.URLConnection;
import java.util.*;
/**
* A JnlpResource encapsulate the information about a resource that is
* needed to process a JNLP Download Request.
*
* The pattern matching arguments are: name, version-id, os, arch, and locale.
*
* The outgoing arguments are:
* - path to resource in (WAR File)
* - product version-id (Version-id to return or null. Typically same as version-id above)
* - mime-type for content
* - lastModified date of WAR file resource
*
*/
public class JnlpResource {
private static final String JNLP_MIME_TYPE = "application/x-java-jnlp-file";
private static final String JAR_MIME_TYPE = "application/x-java-archive";
private static final String JAR_MIME_TYPE_NEW = "application/java-archive";
// Default extension for the JNLP file
private static final String JNLP_EXTENSION = ".jnlp";
private static final String JAR_EXTENSION = ".jar";
private static String _jnlpExtension = JNLP_EXTENSION;
private static String _jarExtension = JAR_EXTENSION;
public static void setDefaultExtensions(String jnlpExtension, String jarExtension) {
if (jnlpExtension != null && jnlpExtension.length() > 0) {
if (!jnlpExtension.startsWith(".")) jnlpExtension = "." + jnlpExtension;
_jnlpExtension = jnlpExtension;
}
if (jarExtension != null && jarExtension.length() > 0) {
if (!jarExtension .startsWith(".")) jarExtension = "." + jarExtension ;
_jarExtension = jarExtension;
}
}
/* Pattern matching arguments */
private String _name; // Name of resource with path (this is the same as path for non-version based)
private String _versionId; // Version-id for resource, or null if none
private String[] _osList; // List of OSes for which resource should be returned
private String[] _archList; // List of architectures for which the resource should be returned
private String[] _localeList; // List of locales for which the resource should be returned
/* Information used for reply */
private String _path; // Path to resource in WAR file (unique)
private URL _resource; // URL to resource in WAR file (unique - same as above really)
private long _lastModified; // Last modified in WAR file
private String _mimeType; // Mime-type for resource
private String _returnVersionId; // Version Id to return
private String _encoding; // Accept encoding
public JnlpResource(ServletContext context, String path) {
this(context, null, null, null, null, null, path, null);
}
public JnlpResource(ServletContext context,
String name,
String versionId,
String[] osList,
String[] archList,
String[] localeList,
String path,
String returnVersionId) {
this(context, name, versionId, osList, archList, localeList, path,
returnVersionId, null);
}
public JnlpResource(ServletContext context,
String name,
String versionId,
String[] osList,
String[] archList,
String[] localeList,
String path,
String returnVersionId,
String encoding) {
// Matching arguments
_encoding = encoding;
_name = name;
_versionId = versionId;
_osList = osList;
_archList = archList;
_localeList = localeList;
_returnVersionId = returnVersionId;
/* Check for existance and get last modified timestamp */
try {
String orig_path = path.trim();
String search_path = orig_path;
_resource = context.getResource(orig_path);
_mimeType = getMimeType(context, orig_path);
if (_resource != null) {
boolean found = false;
// pack200 compression
if (encoding != null && _mimeType != null &&
(_mimeType.compareTo(JAR_MIME_TYPE) == 0 || _mimeType.compareTo(JAR_MIME_TYPE_NEW) == 0) &&
encoding.toLowerCase().indexOf(DownloadResponse.PACK200_GZIP_ENCODING) > -1){
search_path = orig_path + ".pack.gz";
_resource = context.getResource(search_path);
// Get last modified time
if (_resource != null) {
_lastModified = getLastModified(context, _resource, search_path);
if (_lastModified != 0) {
_path = search_path;
found = true;
} else {
_resource = null;
}
}
}
// gzip compression
if (found == false && encoding != null &&
encoding.toLowerCase().indexOf(DownloadResponse.GZIP_ENCODING) > -1){
search_path = orig_path + ".gz";
_resource = context.getResource(search_path);
// Get last modified time
if (_resource != null) {
_lastModified = getLastModified(context, _resource, search_path);
if (_lastModified != 0) {
_path = search_path;
found = true;
} else {
_resource = null;
}
}
}
if (found == false) {
// no compression
search_path = orig_path;
_resource = context.getResource(search_path);
// Get last modified time
if (_resource != null) {
_lastModified = getLastModified(context, _resource, search_path);
if (_lastModified != 0) {
_path = search_path;
found = true;
} else {
_resource = null;
}
}
}
}
} catch(IOException ioe) {
_resource = null;
}
}
long getLastModified(ServletContext context, URL resource, String path) {
long lastModified = 0;
URLConnection conn;
try {
// Get last modified time
conn = resource.openConnection();
lastModified = conn.getLastModified();
} catch (Exception e) {
// do nothing
}
if (lastModified == 0) {
// Arguably a bug in the JRE will not set the lastModified for file URLs, and
// always return 0. This is a workaround for that problem.
String filepath = context.getRealPath(path);
if (filepath != null) {
File f = new File(filepath);
if (f.exists()) {
lastModified = f.lastModified();
}
}
}
return lastModified;
}
/* Get resource specific attributes */
public String getPath() { return _path; }
public URL getResource() { return _resource; }
public String getMimeType() { return _mimeType; }
public long getLastModified() { return _lastModified; }
public boolean exists() { return _resource != null; }
public boolean isJnlpFile() { return _path.endsWith(_jnlpExtension); }
public boolean isJarFile() { return _path.endsWith(_jarExtension); }
/* Get JNLP version specific attributes */
public String getName() { return _name; }
public String getVersionId() { return _versionId; }
public String[] getOSList() { return _osList; }
public String[] getArchList() { return _archList; }
public String[] getLocaleList() { return _localeList; }
public String getReturnVersionId() { return _returnVersionId; }
private String getMimeType(ServletContext context, String path) {
String mimeType = context.getMimeType(path);
if (mimeType != null) return mimeType;
if (path.endsWith(_jnlpExtension)) return JNLP_MIME_TYPE;
if (path.endsWith(_jarExtension)) return JAR_MIME_TYPE;
return "application/unknown";
}
/** Print info about an entry */
public String toString() {
return "JnlpResource[WAR Path: " + _path +
showEntry(" versionId=",_versionId) +
showEntry(" name=", _name) +
" lastModified=" + new Date(_lastModified) +
showEntry(" osList=", _osList) +
showEntry(" archList=", _archList) +
showEntry(" localeList=", _localeList) + "]" +
showEntry(" returnVersionId=", _returnVersionId) + "]";
}
private String showEntry(String msg, String value) {
if (value == null) return "";
return msg + value;
}
private String showEntry(String msg, String[] value) {
if (value == null) return "";
return msg + java.util.Arrays.asList(value).toString();
}
}

View File

@ -0,0 +1,200 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
import java.text.MessageFormat;
import java.util.*;
import java.io.*;
import javax.servlet.*;
/* A loging object used by the servlets */
public class Logger {
// Logging levels
public final static int NONE = 0;
public static final String NONE_KEY = "NONE";
public final static int FATAL = 1;
public static final String FATAL_KEY = "FATAL";
public final static int WARNING = 2;
public static final String WARNING_KEY = "WARNING";
public final static int INFORMATIONAL = 3;
public static final String INFORMATIONAL_KEY = "INFORMATIONAL";
public final static int DEBUG = 4;
public static final String DEBUG_KEY = "DEBUG";
// Configuration parameters
private final static String LOG_LEVEL = "logLevel";
private final static String LOG_PATH = "logPath";
private int _loggingLevel = FATAL;
private ServletContext _servletContext = null;
private String _logFile = null;
private String _servletName = null;
// Localization
ResourceBundle _resources = null;
/** Initialize logging object. It reads the logLevel and pathLevel init parameters.
* Default is logging level FATAL, and logging using the ServletContext.log
*/
public Logger(ServletConfig config, ResourceBundle resources) {
_resources = resources;
_servletContext = config.getServletContext();
_servletName = config.getServletName();
_logFile = config.getInitParameter(LOG_PATH);
if (_logFile != null) {
_logFile = _logFile.trim();
if (_logFile.length() == 0) _logFile = null;
}
String level = config.getInitParameter(LOG_LEVEL);
if (level != null) {
level = level.trim().toUpperCase();
if (level.equals(NONE_KEY)) _loggingLevel = NONE;
if (level.equals(FATAL_KEY)) _loggingLevel = FATAL;
if (level.equals(WARNING_KEY)) _loggingLevel = WARNING;
if (level.equals(INFORMATIONAL_KEY)) _loggingLevel = INFORMATIONAL;
if (level.equals(DEBUG_KEY)) _loggingLevel = DEBUG;
}
}
// Logging API. Fatal, Warning, and Informational are localized
public void addFatal(String key, Throwable throwable) {
logEvent(FATAL, getString(key), throwable);
}
public void addWarning(String key, String arg) {
logL10N(WARNING, key, arg, (Throwable)null);
}
public void addWarning(String key, String arg, Throwable t) {
logL10N(WARNING, key, arg, t);
}
public void addWarning(String key, String arg1, String arg2) {
logL10N(WARNING, key, arg1, arg2);
}
public void addWarning(String key, String arg1, String arg2, String arg3) {
logL10N(WARNING, key, arg1, arg2, arg3);
}
public void addInformational(String key) {
logEvent(INFORMATIONAL, getString(key), (Throwable)null);
}
public void addInformational(String key, String arg) {
logL10N(INFORMATIONAL, key, arg, (Throwable)null);
}
public void addInformational(String key, String arg1, String arg2, String arg3) {
logL10N(INFORMATIONAL, key, arg1, arg2, arg3);
}
// Debug messages are not localized
public void addDebug(String msg) { logEvent(DEBUG, msg, null); }
public void addDebug(String msg, Throwable throwable) {
logEvent(DEBUG, msg, throwable);
}
// Query to test for level
boolean isNoneLevel() { return _loggingLevel >= NONE; }
boolean isFatalevel() { return _loggingLevel >= FATAL; }
boolean isWarningLevel() { return _loggingLevel >= WARNING; }
boolean isInformationalLevel() { return _loggingLevel >= INFORMATIONAL; }
boolean isDebugLevel() { return _loggingLevel >= DEBUG; }
// Returns a string from the resources
private String getString(String key) {
try {
return _resources.getString(key);
} catch (MissingResourceException mre) {
return "Missing resource for: " + key;
}
}
private void logL10N(int level, String key, String arg, Throwable e) {
Object[] messageArguments = { arg };
logEvent(level, applyPattern(key, messageArguments), e);
}
private void logL10N(int level, String key, String arg1, String arg2) {
Object[] messageArguments = { arg1, arg2 };
logEvent(level, applyPattern(key, messageArguments), null);
}
private void logL10N(int level, String key, String arg1, String arg2, String arg3) {
Object[] messageArguments = { arg1, arg2, arg3 };
logEvent(level, applyPattern(key, messageArguments), null);
}
/** Helper function that applies the messageArguments to a message from the resource object */
private String applyPattern(String key, Object[] messageArguments) {
String message = getString(key);
MessageFormat formatter = new MessageFormat(message);
String output = formatter.format(message, messageArguments);
return output;
}
// The method that actually does the logging */
private synchronized void logEvent(int level, String string, Throwable throwable) {
// Check if the event should be logged
if (level > _loggingLevel) return;
if (_logFile != null) {
// No logfile specified, log using servlet context
PrintWriter pw = null;
try {
pw = new PrintWriter(new FileWriter(_logFile, true));
pw.println(_servletName + "(" + level + "): " + string);
if (throwable != null) {
throwable.printStackTrace(pw);
}
pw.close();
// Do a return here. An exception will cause a fall through to
// do _servletContex logging API
return;
} catch (IOException ioe) {
/* just ignore */
}
}
// Otherwise, write to servlet context log
if (throwable == null) {
_servletContext.log(string);
} else {
_servletContext.log(string, throwable);
}
}
}

View File

@ -0,0 +1,514 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.io.File;
import java.io.BufferedInputStream;
import javax.servlet.ServletContext;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import org.w3c.dom.*;
import jnlp.sample.util.VersionString;
import jnlp.sample.util.VersionID;
public class ResourceCatalog {
final private Logger log = Red5LoggerFactory.getLogger(ResourceCatalog.class, "screenshare");
public static final String VERSION_XML_FILENAME = "version.xml";
private ServletContext _servletContext = null;
private HashMap _entries;
/** Class to contain the information we know
* about a specific directory
*/
static private class PathEntries {
/* Version-based entries at this particular path */
private List _versionXmlList;
private List _directoryList;
private List _platformList;
/* Last time this entry was updated */
private long _lastModified; // Last modified time of entry;
public PathEntries(List versionXmlList, List directoryList, List platformList, long lastModified) {
_versionXmlList = versionXmlList;
_directoryList = directoryList;
_platformList = platformList;
_lastModified = lastModified;
}
public void setDirectoryList(List dirList) {
_directoryList = dirList;
}
public List getVersionXmlList() { return _versionXmlList; }
public List getDirectoryList() { return _directoryList; }
public List getPlatformList() { return _platformList; }
public long getLastModified() { return _lastModified; }
}
public ResourceCatalog(ServletContext servletContext) {
_entries = new HashMap();
_servletContext = servletContext;
}
public JnlpResource lookupResource(DownloadRequest dreq) throws ErrorResponseException {
// Split request up into path and name
String path = dreq.getPath();
String name = null;
String dir = null;
int idx = path.lastIndexOf('/');
if (idx == -1) {
name = path;
} else {
name = path.substring(idx + 1); // Exclude '/'
dir = path.substring(0, idx + 1); // Include '/'
}
// Lookup up already parsed entries, and san directory for entries if neccesary
PathEntries pentries = (PathEntries)_entries.get(dir);
JnlpResource xmlVersionResPath = new JnlpResource(_servletContext, dir + VERSION_XML_FILENAME);
if (pentries == null || (xmlVersionResPath.exists() && xmlVersionResPath.getLastModified() > pentries.getLastModified())) {
log.info("servlet.log.scandir", dir);
List dirList = scanDirectory(dir, dreq);
// Scan XML file
List versionList = new ArrayList();
List platformList = new ArrayList();
parseVersionXML(versionList, platformList, dir, xmlVersionResPath);
pentries = new PathEntries(versionList, dirList, platformList, xmlVersionResPath.getLastModified());
_entries.put(dir, pentries);
}
// Search for a match
JnlpResource[] result = new JnlpResource[1];
if (dreq.isPlatformRequest()) {
int sts = findMatch(pentries.getPlatformList(), name, dreq, result);
if (sts != DownloadResponse.STS_00_OK) {
throw new ErrorResponseException(DownloadResponse.getJnlpErrorResponse(sts));
}
} else {
// First lookup in versions.xml file
int sts1 = findMatch(pentries.getVersionXmlList(), name, dreq, result);
if (sts1 != DownloadResponse.STS_00_OK) {
// Then lookup in directory
int sts2 = findMatch(pentries.getDirectoryList(), name, dreq, result);
if (sts2 != DownloadResponse.STS_00_OK) {
// fix for 4450104
// try rescan and see if it helps
pentries.setDirectoryList(scanDirectory(dir, dreq));
sts2 = findMatch(pentries.getDirectoryList(), name, dreq, result);
// try again after rescanning directory
if (sts2 != DownloadResponse.STS_00_OK) {
// Throw the most specific error code
throw new ErrorResponseException(DownloadResponse.getJnlpErrorResponse(Math.max(sts1, sts2)));
}
}
}
}
return result[0];
}
/** This method finds the best match, or return the best error code. The
* result parameter must be an array with room for one element.
*
* If a match is found, the method returns DownloadResponse.STS_00_OK
* If one or more entries matches on: name, version-id, os, arch, and locale,
* then the one with the highest version-id is set in the result[0] field.
*
* If a match is not found, it returns an error code, either: ERR_10_NO_RESOURCE,
* ERR_11_NO_VERSION, ERR_20_UNSUP_OS, ERR_21_UNSUP_ARCH, ERR_22_UNSUP_LOCALE,
* ERR_23_UNSUP_JRE.
*
*/
public int findMatch(List list, String name, DownloadRequest dreq, JnlpResource[] result) {
if (list == null) return DownloadResponse.ERR_10_NO_RESOURCE;
// Setup return values
VersionID bestVersionId = null;
int error = DownloadResponse.ERR_10_NO_RESOURCE;
VersionString vs = new VersionString(dreq.getVersion());
// Iterate through entries
for(int i = 0; i < list.size(); i++) {
JnlpResource respath = (JnlpResource)list.get(i);
VersionID vid = new VersionID(respath.getVersionId());
int sts = matchEntry(name, vs, dreq, respath, vid);
if (sts == DownloadResponse.STS_00_OK) {
if (result[0] == null || vid.isGreaterThan(bestVersionId)) {
result[0] = respath;
bestVersionId = vid;
}
} else {
error = Math.max(error, sts);
}
}
return (result[0] != null) ? DownloadResponse.STS_00_OK : error;
}
public int matchEntry(String name, VersionString vs, DownloadRequest dreq, JnlpResource jnlpres, VersionID vid) {
if (!name.equals(jnlpres.getName())) {
return DownloadResponse.ERR_10_NO_RESOURCE;
}
if (!vs.contains(vid)) {
return DownloadResponse.ERR_11_NO_VERSION;
}
if (!prefixMatchLists(jnlpres.getOSList(), dreq.getOS())) {
return DownloadResponse.ERR_20_UNSUP_OS;
}
if (!prefixMatchLists(jnlpres.getArchList(), dreq.getArch())) {
return DownloadResponse.ERR_21_UNSUP_ARCH;
}
if (!prefixMatchLists(jnlpres.getLocaleList(), dreq.getLocale())) {
return DownloadResponse.ERR_22_UNSUP_LOCALE;
}
return DownloadResponse.STS_00_OK;
}
private static boolean prefixMatchStringList(String[] prefixList, String target) {
// No prefixes matches everything
if (prefixList == null) return true;
// No target, but a prefix list does not match anything
if (target == null) return false;
for(int i = 0; i < prefixList.length; i++) {
if (target.startsWith(prefixList[i])) return true;
}
return false;
}
/* Return true if at least one of the strings in 'prefixes' are a prefix
* to at least one of the 'keys'.
*/
public boolean prefixMatchLists(String[] prefixes, String[] keys) {
// The prefixes are part of the server resources. If none is given,
// everything matches
if (prefixes == null) return true;
// If no os keyes was given, and the server resource is keyed of this,
// then return false.
if (keys == null) return false;
// Check for a match on a key
for(int i = 0; i < keys.length; i++) {
if (prefixMatchStringList(prefixes, keys[i])) return true;
}
return false;
}
/** This method scans the directory pointed to by the
* given path and creates a list of ResourcePath elements
* that contains information about all the entries
*
* The version-based information is encoded in the file name
* given the following format:
*
* entry ::= <name> __ ( <options> ). <ext>
* options ::= <option> ( __ <options> )?
* option ::= V<version-id>
* | O<os>
* | A<arch>
* | L<locale>
*
*/
private String jnlpGetPath(DownloadRequest dreq) {
// fix for 4474021
// try to manuually generate the filename
// extract file name
String path = dreq.getPath();
String filename = path.substring(path.lastIndexOf("/") + 1);
path = path.substring(0, path.lastIndexOf("/") + 1);
String name = filename;
String ext = null;
if (filename.lastIndexOf(".") != -1) {
ext = filename.substring(filename.lastIndexOf(".") + 1);
filename = filename.substring(0, filename.lastIndexOf("."));
}
if (dreq.getVersion() != null) {
filename += "__V" + dreq.getVersion();
}
String[] temp = dreq.getOS();
if (temp != null) {
for (int i=0; i<temp.length; i++) {
filename += "__O" + temp[i];
}
}
temp = dreq.getArch();
if (temp != null) {
for (int i=0; i<temp.length; i++) {
filename += "__A" + temp[i];
}
}
temp = dreq.getLocale();
if (temp != null) {
for (int i=0; i<temp.length; i++) {
filename += "__L" + temp[i];
}
}
if (ext != null) {
filename += "." + ext;
}
path += filename;
return path;
}
public List scanDirectory(String dirPath, DownloadRequest dreq) {
ArrayList list = new ArrayList();
// fix for 4474021
if (_servletContext.getRealPath(dirPath) == null) {
String path = jnlpGetPath(dreq);
String name = dreq.getPath().substring(path.lastIndexOf("/") + 1);
JnlpResource jnlpres = new JnlpResource(_servletContext, name, dreq.getVersion(), dreq.getOS(), dreq.getArch(), dreq.getLocale(), path, dreq.getVersion());
// the file does not exist
if (jnlpres.getResource() == null) return null;
list.add(jnlpres);
return list;
}
File dir = new File(_servletContext.getRealPath(dirPath));
log.debug("File directory: " + dir);
if (dir.exists() && dir.isDirectory()) {
File[] entries = dir.listFiles();
for(int i = 0; i < entries.length; i++) {
JnlpResource jnlpres = parseFileEntry(dirPath, entries[i].getName());
if (jnlpres != null) {
if (log.isDebugEnabled()) {
log.debug("Read file resource: " + jnlpres);
}
list.add(jnlpres);
}
}
}
return list;
}
private JnlpResource parseFileEntry(String dir, String filename) {
int idx = filename .indexOf("__");
if (idx == -1) return null;
// Cut out name
String name = filename.substring(0, idx);
String rest = filename.substring(idx);
// Cut out extension
idx = rest.lastIndexOf('.');
String extension = "";
if (idx != -1 ) {
extension = rest.substring(idx);
rest = rest .substring(0, idx);
}
// Parse options
String versionId = null;
ArrayList osList = new ArrayList();
ArrayList archList = new ArrayList();
ArrayList localeList = new ArrayList();
while(rest.length() > 0) {
/* Must start with __ at this point */
if (!rest.startsWith("__")) return null;
rest = rest.substring(2);
// Get option and argument
char option = rest.charAt(0);
idx = rest.indexOf("__");
String arg = null;
if (idx == -1) {
arg = rest.substring(1);
rest = "";
} else {
arg = rest.substring(1, idx);
rest = rest.substring(idx);
}
switch(option) {
case 'V': versionId = arg; break;
case 'O': osList.add(arg); break;
case 'A': archList.add(arg); break;
case 'L': localeList.add(arg); break;
default: return null; // error
}
}
return new JnlpResource(_servletContext,
name + extension, /* Resource name in URL request */
versionId,
listToStrings(osList),
listToStrings(archList),
listToStrings(localeList),
dir + filename, /* Resource name in WAR file */
versionId);
}
private String[] listToStrings(List list) {
if (list.size() == 0) return null;
return (String[])list.toArray(new String[list.size()]);
}
// Returns false if parsing failed
private void parseVersionXML(final List versionList, final List platformList,
final String dir, final JnlpResource versionRes) {
if (!versionRes.exists()) return;
// Parse XML into a more understandable format
XMLNode root = null;
try {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
Document doc = docBuilder.parse(new BufferedInputStream(versionRes.getResource().openStream()));
doc.getDocumentElement().normalize();
// Convert document into an XMLNode structure, since we already got utility methods
// to handle these. We should really use the data-binding stuff here - but that will come
// later
//
root = XMLParsing.convert(doc.getDocumentElement());
} catch (SAXParseException err) {
log.warn("servlet.log.warning.xml.parsing",
versionRes.getPath(),
Integer.toString(err.getLineNumber()),
err.getMessage());
return;
} catch (Throwable t) {
log.warn("servlet.log.warning.xml.reading", versionRes.getPath(), t);
return;
}
// Check that root element is a <jnlp> tag
if (!root.getName().equals("jnlp-versions")) {
log.warn("servlet.log.warning.xml.missing-jnlp", versionRes.getPath());
return;
}
// Visit all <resource> elements
XMLParsing.visitElements(root, "<resource>", new XMLParsing.ElementVisitor() {
public void visitElement(XMLNode node) {
XMLNode pattern = XMLParsing.findElementPath(node, "<pattern>");
if (pattern == null) {
log.warn("servlet.log.warning.xml.missing-pattern", versionRes.getPath());
} else {
// Parse pattern
String name = XMLParsing.getElementContent(pattern , "<name>", "");
String versionId = XMLParsing.getElementContent(pattern , "<version-id>");
String[] os = XMLParsing.getMultiElementContent(pattern, "<os>");
String[] arch = XMLParsing.getMultiElementContent(pattern, "<arch>");
String[] locale = XMLParsing.getMultiElementContent(pattern, "<locale>");
// Get return request
String file = XMLParsing.getElementContent(node, "<file>");
if (versionId == null || file == null) {
log.warn("servlet.log.warning.xml.missing-elems", versionRes.getPath());
} else {
JnlpResource res = new JnlpResource(_servletContext,
name,
versionId,
os,
arch,
locale,
dir + file,
versionId);
if (res.exists()) {
versionList.add(res);
if (log.isDebugEnabled()) {
log.debug("Read resource: " + res);
}
} else {
log.warn("servlet.log.warning.missing-file", file, versionRes.getPath());
}
}
}
}
});
// Visit all <resource> elements
XMLParsing.visitElements(root, "<platform>", new XMLParsing.ElementVisitor() {
public void visitElement(XMLNode node) {
XMLNode pattern = XMLParsing.findElementPath(node, "<pattern>");
if (pattern == null) {
log.warn("servlet.log.warning.xml.missing-pattern", versionRes.getPath());
} else {
// Parse pattern
String name = XMLParsing.getElementContent(pattern , "<name>", "");
String versionId = XMLParsing.getElementContent(pattern , "<version-id>");
String[] os = XMLParsing.getMultiElementContent(pattern, "<os>");
String[] arch = XMLParsing.getMultiElementContent(pattern, "<arch>");
String[] locale = XMLParsing.getMultiElementContent(pattern, "<locale>");
// Get return request
String file = XMLParsing.getElementContent(node, "<file>");
String productId = XMLParsing.getElementContent(node, "<product-version-id>");
if (versionId == null || file == null || productId == null) {
log.warn("servlet.log.warning.xml.missing-elems2", versionRes.getPath());
} else {
JnlpResource res = new JnlpResource(_servletContext,
name,
versionId,
os,
arch,
locale,
dir + file,
productId);
if (res.exists()) {
platformList.add(res);
if (log.isDebugEnabled()) {
log.debug("Read platform resource: " + res);
}
} else {
log.warn("servlet.log.warning.missing-file", file, versionRes.getPath());
}
}
}
}
});
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
/** Class that contains information about a specific attribute
*/
public class XMLAttribute {
private String _name;
private String _value;
private XMLAttribute _next;
public XMLAttribute(String name, String value) {
_name = name;
_value = value;
_next = null;
}
public XMLAttribute(String name, String value, XMLAttribute next) {
_name = name;
_value = value;
_next = next;
}
public String getName() { return _name; }
public String getValue() { return _value; }
public XMLAttribute getNext() { return _next; }
public void setNext(XMLAttribute next) { _next = next; }
public boolean equals(Object o) {
if (o == null || !(o instanceof XMLAttribute)) return false;
XMLAttribute other = (XMLAttribute)o;
return
match(_name, other._name) &&
match(_value, other._value) &&
match(_next, other._next);
}
private static boolean match(Object o1, Object o2) {
if (o1 == null) return (o2 == null);
return o1.equals(o2);
}
public String toString() {
if (_next != null) {
return _name + "=\"" + _value + "\" " + _next.toString();
} else {
return _name + "=\"" + _value + "\"";
}
}
}

View File

@ -0,0 +1,150 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
import java.io.PrintWriter;
import java.io.StringWriter;
/** Class that contains information about an XML Node
*/
public class XMLNode {
private boolean _isElement; // Element/PCTEXT
private String _name;
private XMLAttribute _attr;
private XMLNode _parent; // Parent Node
private XMLNode _nested; // Nested XML tags
private XMLNode _next; // Following XML tag on the same level
/** Creates a PCTEXT node */
public XMLNode(String name) {
this(name, null, null, null);
_isElement = false;
}
/** Creates a ELEMENT node */
public XMLNode(String name, XMLAttribute attr) {
this(name, attr, null, null);
}
/** Creates a ELEMENT node */
public XMLNode(String name, XMLAttribute attr, XMLNode nested, XMLNode next) {
_isElement = true;
_name = name;
_attr = attr;
_nested = nested;
_next = next;
_parent = null;
}
public String getName() { return _name; }
public XMLAttribute getAttributes() { return _attr; }
public XMLNode getNested() { return _nested; }
public XMLNode getNext() { return _next; }
public boolean isElement() { return _isElement; }
public void setParent(XMLNode parent) { _parent = parent; }
public XMLNode getParent() { return _parent; }
public void setNext(XMLNode next) { _next = next; }
public void setNested(XMLNode nested) { _nested = nested; }
public boolean equals(Object o) {
if (o == null || !(o instanceof XMLNode)) return false;
XMLNode other = (XMLNode)o;
boolean result =
match(_name, other._name) &&
match(_attr, other._attr) &&
match(_nested, other._nested) &&
match(_next, other._next);
return result;
}
public String getAttribute(String name) {
XMLAttribute cur = _attr;
while(cur != null) {
if (name.equals(cur.getName())) return cur.getValue();
cur = cur.getNext();
}
return "";
}
private static boolean match(Object o1, Object o2) {
if (o1 == null) return (o2 == null);
return o1.equals(o2);
}
public void printToStream(PrintWriter out) {
printToStream(out, 0);
}
public void printToStream(PrintWriter out, int n) {
if (!isElement()) {
out.print(_name);
} else {
if (_nested == null) {
String attrString = (_attr == null) ? "" : (" " + _attr.toString());
lineln(out, n, "<" + _name + attrString + "/>");
} else {
String attrString = (_attr == null) ? "" : (" " + _attr.toString());
lineln(out, n, "<" + _name + attrString + ">");
_nested.printToStream(out, n + 1);
if (_nested.isElement()) {
lineln(out, n, "</" + _name + ">");
} else {
out.print("</" + _name + ">");
}
}
}
if (_next != null) {
_next.printToStream(out, n);
}
}
private static void lineln(PrintWriter out, int indent, String s) {
out.println("");
for(int i = 0; i < indent; i++) {
out.print(" ");
}
out.print(s);
}
public String toString() {
StringWriter sw = new StringWriter(1000);
PrintWriter pw = new PrintWriter(sw);
printToStream(pw);
pw.close();
return sw.toString();
}
}

View File

@ -0,0 +1,189 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.servlet;
import javax.xml.parsers.*;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.*;
import org.w3c.dom.*;
/** Contains handy methods for looking up information
* stored in XMLNodes.
*/
public class XMLParsing {
public static XMLNode convert(Node n) {
if (n == null) {
return null;
} else if (n instanceof Text) {
Text tn = (Text)n;
return new XMLNode(tn.getNodeValue());
} else if (n instanceof Element) {
Element en = (Element)n;
XMLAttribute xmlatts = null;
NamedNodeMap attributes = en.getAttributes();
for(int i = attributes.getLength() - 1; i >= 0; i--) {
Attr ar = (Attr)attributes.item(i);
xmlatts = new XMLAttribute(ar.getName(), ar.getValue(), xmlatts);
}
// Convert childern
XMLNode thisNode = new XMLNode(en.getNodeName(), xmlatts, null, null);;
XMLNode last = null;
Node nn = en.getFirstChild();
while(nn != null) {
if (thisNode.getNested() == null) {
last = convert(nn);
thisNode.setNested(last);
} else {
XMLNode nnode = convert(nn);
last.setNext(nnode);
last = nnode;
}
last.setParent(thisNode);
nn = nn.getNextSibling();
}
return thisNode;
}
return null;
}
/** Returns true if the path exists in the document, otherwise false */
static public boolean isElementPath(XMLNode root, String path) {
return findElementPath(root, path) != null;
}
/** Returns a string describing the current location in the DOM */
static public String getPathString(XMLNode e) {
return (e == null || !(e.isElement())) ? "" : getPathString(e.getParent()) + "<" + e.getName() + ">";
}
/** Like getElementContents(...) but with a defaultValue of null */
static public String getElementContent(XMLNode root, String path) {
return getElementContent(root, path, null);
}
/** Like getElementContents(...) but with a defaultValue of null */
static public String[] getMultiElementContent(XMLNode root, String path) {
final List list = new ArrayList();
visitElements(root, path, new ElementVisitor() {
public void visitElement(XMLNode n) {
String value = getElementContent(n, "");
if (value != null) list.add(value);
}
});
if (list.size() == 0) return null;
return (String[])list.toArray(new String[list.size()]);
}
/** Returns the value of the last element tag in the path, e.g., <..><tag>value</tag>. The DOM is assumes
* to be normalized. If no value is found, the defaultvalue is returned
*/
static public String getElementContent(XMLNode root, String path, String defaultvalue) {
XMLNode e = findElementPath(root, path);
if (e == null) return defaultvalue;
XMLNode n = e.getNested();
if (n != null && !n.isElement()) return n.getName();
return defaultvalue;
}
/** Parses a path string of the form <tag1><tag2><tag3> and returns the specific Element
* node for that tag, or null if it does not exist. If multiple elements exists with same
* path the first is returned
*/
static public XMLNode findElementPath(XMLNode elem, String path) {
// End condition. Root null -> path does not exist
if (elem == null) return null;
// End condition. String empty, return current root
if (path == null || path.length() == 0) return elem;
// Strip of first tag
int idx = path.indexOf('>');
String head = path.substring(1, idx);
String tail = path.substring(idx + 1);
return findElementPath(findChildElement(elem, head), tail);
}
/** Returns an child element with the current tag name or null. */
static public XMLNode findChildElement(XMLNode elem, String tag) {
XMLNode n = elem.getNested();
while(n != null) {
if (n.isElement() && n.getName().equals(tag)) return n;
n = n.getNext();
}
return null;
}
/** Iterator class */
public abstract static class ElementVisitor {
abstract public void visitElement(XMLNode e);
}
/** Visits all elements which matches the <path>. The iteration is only
* done on the last elment in the path.
*/
static public void visitElements(XMLNode root, String path, ElementVisitor ev) {
// Get last element in path
int idx = path.lastIndexOf('<');
String head = path.substring(0, idx);
String tag = path.substring(idx + 1, path.length() - 1);
XMLNode elem = findElementPath(root, head);
if (elem == null) return;
// Iterate through all child nodes
XMLNode n = elem.getNested();
while(n != null) {
if (n.isElement() && n.getName().equals(tag)) {
ev.visitElement(n);
}
n = n.getNext();
}
}
static public void visitChildrenElements(XMLNode elem, ElementVisitor ev) {
// Iterate through all child nodes
XMLNode n = elem.getNested();
while(n != null) {
if (n.isElement()) ev.visitElement(n);
n = n.getNext();
}
}
}

View File

@ -0,0 +1,67 @@
#
# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# -Redistribution of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# -Redistribution in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# Neither the name of Oracle nor the names of contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# This software is provided "AS IS," without a warranty of any kind. ALL
# EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
# ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
# OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
# AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
# AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
# DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
# REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
# INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
# OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
# EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
#
# You acknowledge that this software is not designed, licensed or intended
# for use in the design, construction, operation or maintenance of any
# nuclear facility.
#
# Fatals
servlet.log.fatal.internalerror=Internal error:
# Warnings
servlet.log.warning.nolastmodified=Last-modified read as 0 for {0}
servlet.log.warning.notimestamp=Timestamp read as 0 for {0}. Using Last-modified instead
servlet.log.warning.missing-file=Reference to non-existing file ({0}) in {1}
servlet.log.warning.xml.parsing=Error parsing {0} at line {1}: {2}
servlet.log.warning.xml.reading=Unexpected error reading {0}:
servlet.log.warning.xml.missing-jnlp=Missing <jnlp-versions> element in {0}
servlet.log.warning.xml.missing-pattern=Missing <pattern> element in {0}
servlet.log.warning.xml.missing-elems=Missing <version-id> or <file> attribute in {0}
servlet.log.warning.xml.missing-elems2=Missing <version-id>, <file>, or <product-version-id> attribute in {0}
servlet.log.warning.jardiff.failed=Failed to generate JarDiff for {0} {1}->{2}
# Informational
servlet.log.info.request=Request: {0}
servlet.log.info.useragent=User-Agent: {0}
servlet.log.info.goodrequest=Resource returned: {0}
servlet.log.info.badrequest=Error code returned for request: {0}
servlet.log.scandir=Rescanning directory: {0}
servlet.log.info.jardiff.response=JarDiff returned for request
servlet.log.info.jardiff.gen=Generating JarDiff for {0} {1}->{2}
# JNLP Error strings
servlet.jnlp.err.10 = Could not locate resource
servlet.jnlp.err.11 = Could not locate requested version
servlet.jnlp.err.20 = Unsupported operating system
servlet.jnlp.err.21 = Unsupported architecture
servlet.jnlp.err.22 = Unsupported locale
servlet.jnlp.err.23 = Unsupported JRE version
servlet.jnlp.err.99 = Unknown error

View File

@ -0,0 +1,260 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.util;
import java.util.ArrayList;
import java.util.Arrays;
/**
* VersionID contains a JNLP version ID.
*
* The VersionID also contains a prefix indicator that can
* be used when stored with a VersionString
*
*/
public class VersionID implements Comparable {
private String[] _tuple; // Array of Integer or String objects
private boolean _usePrefixMatch; // star (*) prefix
private boolean _useGreaterThan; // plus (+) greather-than
private boolean _isCompound; // and (&) operator
private VersionID _rest; // remaining part after the &
/** Creates a VersionID object */
public VersionID(String str) {
_usePrefixMatch = false;
_useGreaterThan = false;
_isCompound = false;
if (str == null && str.length() == 0) {
_tuple = new String[0];
return;
}
// Check for compound
int amp = str.indexOf("&");
if (amp >= 0) {
_isCompound = true;
VersionID firstPart = new VersionID(str.substring(0, amp));
_rest = new VersionID(str.substring(amp+1));
_tuple = firstPart._tuple;
_usePrefixMatch = firstPart._usePrefixMatch;
_useGreaterThan = firstPart._useGreaterThan;
} else {
// Check for postfix
if (str.endsWith("+")) {
_useGreaterThan = true;
str = str.substring(0, str.length() - 1);
} else if (str.endsWith("*")) {
_usePrefixMatch = true;
str = str.substring(0, str.length() - 1);
}
ArrayList list = new ArrayList();
int start = 0;
for(int i = 0; i < str.length(); i++) {
// Split at each separator character
if (".-_".indexOf(str.charAt(i)) != -1) {
if (start < i) {
String value = str.substring(start, i);
list.add(value);
}
start = i + 1;
}
}
if (start < str.length()) {
list.add(str.substring(start, str.length()));
}
_tuple = new String[list.size()];
_tuple = (String[])list.toArray(_tuple);
}
}
/** Returns true if no flags are set */
public boolean isSimpleVersion() {
return !_useGreaterThan && !_usePrefixMatch && !_isCompound;
}
/** Match 'this' versionID against vid.
* The _usePrefixMatch/_useGreaterThan flag is used to determine if a
* prefix match of an exact match should be performed
* if _isCompound, must match _rest also.
*/
public boolean match(VersionID vid) {
if (_isCompound) {
if (!_rest.match(vid)) {
return false;
}
}
return (_usePrefixMatch) ? this.isPrefixMatch(vid) :
(_useGreaterThan) ? vid.isGreaterThanOrEqual(this) :
matchTuple(vid);
}
/** Compares if two version IDs are equal */
public boolean equals(Object o) {
if (matchTuple(o)) {
VersionID ov = (VersionID) o;
if (_rest == null || _rest.equals(ov._rest)) {
if ((_useGreaterThan == ov._useGreaterThan) &&
(_usePrefixMatch == ov._usePrefixMatch)) {
return true;
}
}
}
return false;
}
/** Compares if two version IDs are equal */
private boolean matchTuple(Object o) {
// Check for null and type
if (o == null || !(o instanceof VersionID)) return false;
VersionID vid = (VersionID)o;
// Normalize arrays
String[] t1 = normalize(_tuple, vid._tuple.length);
String[] t2 = normalize(vid._tuple, _tuple.length);
// Check contents
for(int i = 0; i < t1.length; i++) {
Object o1 = getValueAsObject(t1[i]);
Object o2 = getValueAsObject(t2[i]);
if (!o1.equals(o2)) return false;
}
return true;
}
private Object getValueAsObject(String value) {
if (value.length() > 0 && value.charAt(0) != '-') {
try { return Integer.valueOf(value);
} catch(NumberFormatException nfe) { /* fall through */ }
}
return value;
}
public boolean isGreaterThan(VersionID vid) {
return isGreaterThanOrEqualHelper(vid, false);
}
public boolean isGreaterThanOrEqual(VersionID vid) {
return isGreaterThanOrEqualHelper(vid, true);
}
/** Compares if 'this' is greater than vid */
private boolean isGreaterThanOrEqualHelper(VersionID vid,
boolean allowEqual) {
if (_isCompound) {
if (!_rest.isGreaterThanOrEqualHelper(vid, allowEqual)) {
return false;
}
}
// Normalize the two strings
String[] t1 = normalize(_tuple, vid._tuple.length);
String[] t2 = normalize(vid._tuple, _tuple.length);
for(int i = 0; i < t1.length; i++) {
// Compare current element
Object e1 = getValueAsObject(t1[i]);
Object e2 = getValueAsObject(t2[i]);
if (e1.equals(e2)) {
// So far so good
} else {
if (e1 instanceof Integer && e2 instanceof Integer) {
return ((Integer)e1).intValue() > ((Integer)e2).intValue();
} else {
String s1 = t1[i].toString();
String s2 = t2[i].toString();
return s1.compareTo(s2) > 0;
}
}
}
// If we get here, they are equal
return allowEqual;
}
/** Checks if 'this' is a prefix of vid */
public boolean isPrefixMatch(VersionID vid) {
if (_isCompound) {
if (!_rest.isPrefixMatch(vid)) {
return false;
}
}
// Make sure that vid is at least as long as the prefix
String[] t2 = normalize(vid._tuple, _tuple.length);
for(int i = 0; i < _tuple.length; i++) {
Object e1 = _tuple[i];
Object e2 = t2[i];
if (e1.equals(e2)) {
// So far so good
} else {
// Not a prefix
return false;
}
}
return true;
}
/** Normalize an array to a certain lengh */
private String[] normalize(String[] list, int minlength) {
if (list.length < minlength) {
// Need to do padding
String[] newlist = new String[minlength];
System.arraycopy(list, 0, newlist, 0, list.length);
Arrays.fill(newlist, list.length, newlist.length, "0");
return newlist;
} else {
return list;
}
}
public int compareTo(Object o) {
if (o == null || !(o instanceof VersionID)) return -1;
VersionID vid = (VersionID)o;
return equals(vid) ? 0 : (isGreaterThanOrEqual(vid) ? 1 : -1);
}
/** Show it as a string */
public String toString() {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < _tuple.length -1; i++) {
sb.append(_tuple[i]);
sb.append('.');
}
if (_tuple.length > 0 ) sb.append(_tuple[_tuple.length - 1]);
if (_usePrefixMatch) sb.append('+');
return sb.toString();
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Oracle nor the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
package jnlp.sample.util;
import java.util.ArrayList;
import java.util.StringTokenizer;
/*
* Utility class that knows to handle version strings
* A version string is of the form:
*
* (version-id ('+'?) ' ') *
*
*/
public class VersionString {
private ArrayList _versionIds;
/** Constructs a VersionString object from string */
public VersionString(String vs) {
_versionIds = new ArrayList();
if (vs != null) {
StringTokenizer st = new StringTokenizer(vs, " ", false);
while(st.hasMoreElements()) {
// Note: The VersionID class takes care of a postfixed '+'
_versionIds.add(new VersionID(st.nextToken()));
}
}
}
/** Check if this VersionString object contains the VersionID m */
public boolean contains(VersionID m) {
for(int i = 0; i < _versionIds.size(); i++) {
VersionID vi = (VersionID)_versionIds.get(i);
boolean check = vi.match(m);
if (check) return true;
}
return false;
}
/** Check if this VersionString object contains the VersionID m, given as a string */
public boolean contains(String versionid) {
return contains(new VersionID(versionid));
}
/** Check if this VersionString object contains anything greater than m */
public boolean containsGreaterThan(VersionID m) {
for(int i = 0; i < _versionIds.size(); i++) {
VersionID vi = (VersionID)_versionIds.get(i);
boolean check = vi.isGreaterThan(m);
if (check) return true;
}
return false;
}
/** Check if this VersionString object contains anything greater than the VersionID m, given as a string */
public boolean containsGreaterThan(String versionid) {
return containsGreaterThan(new VersionID(versionid));
}
/** Check if the versionString 'vs' contains the VersionID 'vi' */
static public boolean contains(String vs, String vi) {
return (new VersionString(vs)).contains(vi);
}
/** Pretty-print object */
public String toString() {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < _versionIds.size(); i++) {
sb.append(_versionIds.get(i).toString());
sb.append(' ');
}
return sb.toString();
}
}

View File

@ -0,0 +1,10 @@
package org.bigbluebutton.app.screenshare;
public class Error {
public final String reason;
public Error(String reason) {
this.reason = reason;
}
}

View File

@ -0,0 +1,43 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare;
import java.util.Map;
import redis.clients.jedis.Jedis;
public class EventRecordingService {
private static final String COLON = ":";
private final String host;
private final int port;
public EventRecordingService(String host, int port) {
this.host = host;
this.port = port;
}
public void record(String meetingId, Map<String, String> event) {
Jedis jedis = new Jedis(host, port);
Long msgid = jedis.incr("global:nextRecordedMsgId");
jedis.hmset("recording:" + meetingId + COLON + msgid, event);
jedis.rpush("meeting:" + meetingId + COLON + "recordings", msgid.toString());
}
}

View File

@ -0,0 +1,17 @@
package org.bigbluebutton.app.screenshare;
public interface IScreenShareApplication {
IsScreenSharingResponse isScreenSharing(String meetingId);
ScreenShareInfoResponse getScreenShareInfo(String meetingId, String token);
StartShareRequestResponse startShareRequest(String meetingId, String userId, Boolean record);
void stopShareRequest(String meetingId, String streamId);
void streamStarted(String meetingId, String streamId, String url);
void streamStopped(String meetingId, String streamId);
void sharingStarted(String meetingId, String streamId, Integer width, Integer height);
void sharingStopped(String meetingId, String streamId);
void updateShareStatus(String meetingId, String streamId, Integer seqNum);
Boolean isSharingStopped(String meetingId, String streamId);
Boolean recordStream(String meetingId, String streamId);
void userDisconnected(String meetingId, String userId);
}

View File

@ -0,0 +1,12 @@
package org.bigbluebutton.app.screenshare;
public class IsScreenSharingResponse {
public final StreamInfo info;
public final Error error;
public IsScreenSharingResponse(StreamInfo info, Error error) {
this.info = info;
this.error = error;
}
}

View File

@ -0,0 +1,12 @@
package org.bigbluebutton.app.screenshare;
public class ScreenShareInfo {
public final String streamId;
public final String publishUrl;
public ScreenShareInfo(String publishUrl, String streamId) {
this.streamId = streamId;
this.publishUrl = publishUrl;
}
}

View File

@ -0,0 +1,12 @@
package org.bigbluebutton.app.screenshare;
public class ScreenShareInfoResponse {
public final ScreenShareInfo info;
public final Error error;
public ScreenShareInfoResponse(ScreenShareInfo info, Error error) {
this.info = info;
this.error = error;
}
}

View File

@ -0,0 +1,99 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IStreamListener;
import org.red5.server.api.stream.IStreamPacket;
import org.red5.server.net.rtmp.event.VideoData;
/**
* Class to listen for the first video packet of the webcam.
* We need to listen for the first packet and send a startWebcamEvent.
* The reason is that when starting the webcam, sometimes Flash Player
* needs to prompt the user for permission to access the webcam. However,
* while waiting for the user to click OK to the prompt, Red5 has already
* called the startBroadcast method which we take as the start of the recording.
* When the user finally clicks OK, the packets then start to flow through.
* This introduces a delay of when we assume the start of the recording and
* the webcam actually publishes video packets. When we do the ingest and
* processing of the video and multiplex the audio, the video and audio will
* be un-synched by at least this amount of delay.
* @author Richard Alam
*
*/
public class ScreenshareStreamListener implements IStreamListener {
private EventRecordingService recordingService;
private volatile boolean firstPacketReceived = false;
private String recordingDir;
public ScreenshareStreamListener(EventRecordingService s, String recordingDir) {
this.recordingService = s;
this.recordingDir = recordingDir;
}
private Long genTimestamp() {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
}
@Override
public void packetReceived(IBroadcastStream stream, IStreamPacket packet) {
IoBuffer buf = packet.getData();
if (buf != null)
buf.rewind();
if (buf == null || buf.remaining() == 0){
return;
}
if (packet instanceof VideoData) {
if (! firstPacketReceived) {
firstPacketReceived = true;
IConnection conn = Red5.getConnectionLocal();
String meetingId = conn.getScope().getName();
String filename = recordingDir;
if (!filename.endsWith("/")) {
filename.concat("/");
}
filename = filename.concat(meetingId).concat("/").concat(stream.getPublishedName()).concat(".flv");
Map<String, String> event = new HashMap<String, String>();
event.put("module", "Deskshare");
event.put("timestamp", new Long(System.currentTimeMillis()).toString());
event.put("meetingId", meetingId);
event.put("file", filename);
event.put("stream", stream.getPublishedName());
event.put("eventName", "DeskshareStartedEvent");
recordingService.record(conn.getScope().getName(), event);
}
}
}
}

View File

@ -0,0 +1,15 @@
package org.bigbluebutton.app.screenshare;
public class StartShareRequestResponse {
public final String token;
public final String jnlp;
public final Error error;
public StartShareRequestResponse(String token, String jnlp, Error error) {
this.token = token;
this.jnlp = jnlp;
this.error = error;
}
}

View File

@ -0,0 +1,19 @@
package org.bigbluebutton.app.screenshare;
public class StreamInfo {
public final String streamId;
public final Boolean sharing;
public final int width;
public final int height;
public final String url;
public StreamInfo(Boolean sharing, String streamId,
int width, int height, String url) {
this.sharing = sharing;
this.streamId = streamId;
this.width = width;
this.height = height;
this.url = url;
}
}

View File

@ -0,0 +1,67 @@
package org.bigbluebutton.app.screenshare.events;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.Set;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
public class EventMessageBusImp implements IEventsMessageBus {
private static Logger log = Red5LoggerFactory.getLogger(EventMessageBusImp.class, "screenshare");
private BlockingQueue<IEvent> receivedMessages = new LinkedBlockingQueue<IEvent>();
private volatile boolean processMessage = false;
private final Executor msgProcessorExec = Executors.newSingleThreadExecutor();
private int maxThreshold = 1024;
private Set<IEventListener> listeners;
public void send(IEvent msg) {
if (receivedMessages.size() > maxThreshold) {
log.warn("Queued number of events [{}] is greater than threshold [{}]", receivedMessages.size(), maxThreshold);
}
receivedMessages.add(msg);
}
public void stop() {
processMessage = false;
}
public void start() {
try {
processMessage = true;
Runnable messageProcessor = new Runnable() {
public void run() {
while (processMessage) {
try {
IEvent msg = receivedMessages.take();
processMessage(msg);
} catch (InterruptedException e) {
log.warn("Error while taking received message from queue.");
}
}
}
};
msgProcessorExec.execute(messageProcessor);
} catch (Exception e) {
log.error("Error processing event: " + e.getMessage());
}
}
private void processMessage(final IEvent msg) {
for (IEventListener listener : listeners) {
listener.handleMessage(msg);
}
}
public void setListeners(Set<IEventListener> listeners) {
this.listeners = listeners;
}
public void setMaxThreshold(int threshold) {
maxThreshold = threshold;
}
}

View File

@ -0,0 +1,5 @@
package org.bigbluebutton.app.screenshare.events;
public interface IEvent {
}

View File

@ -0,0 +1,5 @@
package org.bigbluebutton.app.screenshare.events;
public interface IEventListener {
void handleMessage(IEvent msg);
}

View File

@ -0,0 +1,5 @@
package org.bigbluebutton.app.screenshare.events;
public interface IEventsMessageBus {
void send(IEvent msg);
}

View File

@ -0,0 +1,12 @@
package org.bigbluebutton.app.screenshare.events;
public class ShareStartedEvent implements IEvent {
public final String meetingId;
public final String streamId;
public ShareStartedEvent(String meetingId, String streamId) {
this.meetingId = meetingId;
this.streamId = streamId;
}
}

View File

@ -0,0 +1,12 @@
package org.bigbluebutton.app.screenshare.events;
public class ShareStoppedEvent implements IEvent {
public final String meetingId;
public final String streamId;
public ShareStoppedEvent(String meetingId, String streamId) {
this.meetingId = meetingId;
this.streamId = streamId;
}
}

View File

@ -0,0 +1,19 @@
package org.bigbluebutton.app.screenshare.events;
public class StreamStartedEvent implements IEvent {
public final String meetingId;
public final String streamId;
public final int width;
public final int height;
public final String url;
public StreamStartedEvent(String meetingId, String streamId,
int width, int height, String url) {
this.meetingId = meetingId;
this.streamId = streamId;
this.width = width;
this.height = height;
this.url = url;
}
}

View File

@ -0,0 +1,12 @@
package org.bigbluebutton.app.screenshare.events;
public class StreamStoppedEvent implements IEvent {
public final String meetingId;
public final String streamId;
public StreamStoppedEvent(String meetingId, String streamId) {
this.meetingId = meetingId;
this.streamId = streamId;
}
}

View File

@ -0,0 +1,14 @@
package org.bigbluebutton.app.screenshare.events;
public class StreamUpdateEvent implements IEvent {
public final String meetingId;
public final String streamId;
public final Long date;
public StreamUpdateEvent(String meetingId, String streamId, Long date) {
this.meetingId = meetingId;
this.streamId = streamId;
this.date = date;
}
}

View File

@ -0,0 +1,35 @@
package org.bigbluebutton.app.screenshare.messaging.redis;
import java.util.HashMap;
import java.util.Map;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import com.google.gson.Gson;
public class MeetingMessageHandler implements MessageHandler {
private static Logger log = Red5LoggerFactory.getLogger(MeetingMessageHandler.class, "screenshare");
@Override
public void handleMessage(String pattern, String channel, String message) {
if (channel.equalsIgnoreCase(MessagingConstants.TO_MEETING_CHANNEL)) {
// IMessage msg = MessageFromJsonConverter.convert(message);
// if (msg != null) {
// }
} else if (channel.equalsIgnoreCase(MessagingConstants.TO_SYSTEM_CHANNEL)) {
// IMessage msg = MessageFromJsonConverter.convert(message);
// if (msg != null) {
//
// }
}
}
}

View File

@ -0,0 +1,25 @@
package org.bigbluebutton.app.screenshare.messaging.redis;
import java.util.Set;
public class MessageDistributor {
private ReceivedMessageHandler handler;
private Set<MessageHandler> listeners;
public void setMessageListeners(Set<MessageHandler> listeners) {
this.listeners = listeners;
}
public void setMessageHandler(ReceivedMessageHandler handler) {
this.handler = handler;
if (handler != null) {
handler.setMessageDistributor(this);
}
}
public void notifyListeners(String pattern, String channel, String message) {
for (MessageHandler listener : listeners) {
listener.handleMessage(pattern, channel, message);
}
}
}

View File

@ -0,0 +1,23 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.messaging.redis;
public interface MessageHandler {
void handleMessage(String pattern, String channel, String message);
}

View File

@ -0,0 +1,88 @@
package org.bigbluebutton.app.screenshare.messaging.redis;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;
public class MessageReceiver {
private static Logger log = Red5LoggerFactory.getLogger(MessageReceiver.class, "bigbluebutton");
private ReceivedMessageHandler handler;
private JedisPool redisPool;
private volatile boolean receiveMessage = false;
private final Executor msgReceiverExec = Executors.newSingleThreadExecutor();
public void stop() {
receiveMessage = false;
}
public void start() {
log.info("Ready to receive messages from Redis pubsub.");
try {
receiveMessage = true;
final Jedis jedis = redisPool.getResource();
Runnable messageReceiver = new Runnable() {
public void run() {
if (receiveMessage) {
jedis.psubscribe(new PubSubListener(), MessagingConstants.TO_BBB_APPS_PATTERN);
}
}
};
msgReceiverExec.execute(messageReceiver);
} catch (Exception e) {
log.error("Error subscribing to channels: " + e.getMessage());
}
}
public void setRedisPool(JedisPool redisPool){
this.redisPool = redisPool;
}
public void setMessageHandler(ReceivedMessageHandler handler) {
this.handler = handler;
}
private class PubSubListener extends JedisPubSub {
public PubSubListener() {
super();
}
@Override
public void onMessage(String channel, String message) {
// Not used.
}
@Override
public void onPMessage(String pattern, String channel, String message) {
handler.handleMessage(pattern, channel, message);
}
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
log.debug("Subscribed to the pattern: " + pattern);
}
@Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
// Not used.
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
// Not used.
}
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
// Not used.
}
}
}

View File

@ -0,0 +1,74 @@
package org.bigbluebutton.app.screenshare.messaging.redis;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class MessageSender {
private static Logger log = Red5LoggerFactory.getLogger(MessageSender.class, "bigbluebutton");
private JedisPool redisPool;
private volatile boolean sendMessage = false;
private final Executor msgSenderExec = Executors.newSingleThreadExecutor();
private final Executor runExec = Executors.newSingleThreadExecutor();
private BlockingQueue<MessageToSend> messages = new LinkedBlockingQueue<MessageToSend>();
public void stop() {
sendMessage = false;
}
public void start() {
log.info("Redis message publisher starting!");
try {
sendMessage = true;
Runnable messageSender = new Runnable() {
public void run() {
while (sendMessage) {
try {
MessageToSend msg = messages.take();
publish(msg.getChannel(), msg.getMessage());
} catch (InterruptedException e) {
log.warn("Failed to get message from queue.");
}
}
}
};
msgSenderExec.execute(messageSender);
} catch (Exception e) {
log.error("Error subscribing to channels: " + e.getMessage());
}
}
public void send(String channel, String message) {
MessageToSend msg = new MessageToSend(channel, message);
messages.add(msg);
}
private void publish(final String channel, final String message) {
Runnable task = new Runnable() {
public void run() {
Jedis jedis = redisPool.getResource();
try {
jedis.publish(channel, message);
} catch(Exception e){
log.warn("Cannot publish the message to redis", e);
} finally {
redisPool.returnResource(jedis);
}
}
};
runExec.execute(task);
}
public void setRedisPool(JedisPool redisPool){
this.redisPool = redisPool;
}
}

View File

@ -0,0 +1,19 @@
package org.bigbluebutton.app.screenshare.messaging.redis;
public class MessageToSend {
private final String channel;
private final String message;
public MessageToSend(String channel, String message) {
this.channel = channel;
this.message = message;
}
public String getChannel() {
return channel;
}
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,40 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.messaging.redis;
public class MessagingConstants {
public static final String FROM_BBB_APPS_CHANNEL = "bigbluebutton:from-bbb-apps";
public static final String FROM_BBB_APPS_PATTERN = FROM_BBB_APPS_CHANNEL + ":*";
public static final String FROM_SYSTEM_CHANNEL = FROM_BBB_APPS_CHANNEL + ":system";
public static final String FROM_MEETING_CHANNEL = FROM_BBB_APPS_CHANNEL + ":meeting";
public static final String TO_BBB_APPS_CHANNEL = "bigbluebutton:to-bbb-apps";
public static final String TO_BBB_APPS_PATTERN = TO_BBB_APPS_CHANNEL + ":*";
public static final String TO_MEETING_CHANNEL = TO_BBB_APPS_CHANNEL + ":meeting";
public static final String TO_SYSTEM_CHANNEL = TO_BBB_APPS_CHANNEL + ":system";
public static final String DESTROY_MEETING_REQUEST_EVENT = "DestroyMeetingRequestEvent";
public static final String CREATE_MEETING_REQUEST_EVENT = "CreateMeetingRequestEvent";
public static final String END_MEETING_REQUEST_EVENT = "EndMeetingRequestEvent";
public static final String MEETING_STARTED_EVENT = "meeting_created_message";
public static final String MEETING_ENDED_EVENT = "meeting_ended_event";
public static final String MEETING_DESTROYED_EVENT = "meeting_destroyed_event";
}

View File

@ -0,0 +1,28 @@
package org.bigbluebutton.app.screenshare.messaging.redis;
public class ReceivedMessage {
private final String pattern;
private final String channel;
private final String message;
public ReceivedMessage(String pattern, String channel, String message) {
this.pattern = pattern;
this.channel = channel;
this.message = message;
}
public String getPattern() {
return pattern;
}
public String getChannel() {
return channel;
}
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,72 @@
package org.bigbluebutton.app.screenshare.messaging.redis;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
public class ReceivedMessageHandler {
private static Logger log = Red5LoggerFactory.getLogger(ReceivedMessageHandler.class, "bigbluebutton");
private BlockingQueue<ReceivedMessage> receivedMessages = new LinkedBlockingQueue<ReceivedMessage>();
private volatile boolean processMessage = false;
private final Executor msgProcessorExec = Executors.newSingleThreadExecutor();
private final Executor runExec = Executors.newSingleThreadExecutor();
private MessageDistributor handler;
public void stop() {
processMessage = false;
}
public void start() {
log.info("Ready to handle messages from Redis pubsub!");
try {
processMessage = true;
Runnable messageProcessor = new Runnable() {
public void run() {
while (processMessage) {
try {
ReceivedMessage msg = receivedMessages.take();
processMessage(msg);
} catch (InterruptedException e) {
log.warn("Error while taking received message from queue.");
}
}
}
};
msgProcessorExec.execute(messageProcessor);
} catch (Exception e) {
log.error("Error subscribing to channels: " + e.getMessage());
}
}
private void processMessage(final ReceivedMessage msg) {
Runnable task = new Runnable() {
public void run() {
if (handler != null) {
handler.notifyListeners(msg.getPattern(), msg.getChannel(), msg.getMessage());
} else {
log.info("No listeners interested in messages from Redis!");
}
}
};
runExec.execute(task);
}
public void handleMessage(String pattern, String channel, String message) {
ReceivedMessage rm = new ReceivedMessage(pattern, channel, message);
receivedMessages.add(rm);
}
public void setMessageDistributor(MessageDistributor h) {
this.handler = h;
}
}

View File

@ -0,0 +1,46 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.red5;
import java.util.Map;
public class BroadcastClientMessage implements ClientMessage {
private String meetingID;
private Map<String, Object> message;
private String messageName;
public BroadcastClientMessage(String meetingID, String messageName, Map<String, Object> message) {
this.meetingID = meetingID;
this.message = message;
this.messageName = messageName;
}
public String getMeetingID() {
return meetingID;
}
public String getMessageName() {
return messageName;
}
public Map<String, Object> getMessage() {
return message;
}
}

View File

@ -0,0 +1,24 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.red5;
public interface ClientMessage {
}

View File

@ -0,0 +1,220 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.red5;
import java.util.Set;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.IConnection;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.scope.ScopeType;
import org.red5.server.api.service.ServiceUtils;
import org.red5.server.api.so.ISharedObject;
import org.red5.server.api.so.ISharedObjectService;
import org.red5.server.so.SharedObjectService;
import org.red5.server.util.ScopeUtils;
import org.slf4j.Logger;
public class ConnectionInvokerService {
private static Logger log = Red5LoggerFactory.getLogger(ConnectionInvokerService.class, "screenshare");
private static final int NTHREADS = 1;
private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
private static final Executor runExec = Executors.newFixedThreadPool(NTHREADS);
private BlockingQueue<ClientMessage> messages;
private volatile boolean sendMessages = false;
private IScope bbbAppScope;
public ConnectionInvokerService() {
messages = new LinkedBlockingQueue<ClientMessage>();
}
public void setAppScope(IScope scope) {
bbbAppScope = scope;
}
public void start() {
sendMessages = true;
Runnable sender = new Runnable() {
public void run() {
while (sendMessages) {
ClientMessage message;
try {
message = messages.take();
sendMessageToClient(message);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
exec.execute(sender);
}
public void stop() {
sendMessages = false;
}
public void sendMessage(final ClientMessage message) {
messages.offer(message);
}
private void sendMessageToClient(ClientMessage message) {
if (message instanceof BroadcastClientMessage) {
sendBroadcastMessage((BroadcastClientMessage) message);
} else if (message instanceof DirectClientMessage) {
sendDirectMessage((DirectClientMessage) message);
} else if (message instanceof SharedObjectClientMessage) {
sendSharedObjectMessage((SharedObjectClientMessage) message);
} else if (message instanceof DisconnectClientMessage) {
handlDisconnectClientMessage((DisconnectClientMessage) message);
} else if (message instanceof DisconnectAllClientsMessage) {
handleDisconnectAllClientsMessage((DisconnectAllClientsMessage) message);
}
}
private void handleDisconnectAllClientsMessage(DisconnectAllClientsMessage msg) {
IScope meetingScope = getScope(msg.getMeetingId());
if (meetingScope != null) {
Set<IConnection> conns = meetingScope.getClientConnections();
for (IConnection conn : conns) {
if (conn.isConnected()) {
String connId = (String) conn.getAttribute("INTERNAL_USER_ID");
log.info("Disconnecting client=[{}] from meeting=[{}]", connId, msg.getMeetingId());
conn.close();
}
}
}
}
private void handlDisconnectClientMessage(DisconnectClientMessage msg) {
IScope meetingScope = getScope(msg.getMeetingId());
if (meetingScope != null) {
IConnection conn = getConnection(meetingScope, msg.getUserId());
if (conn != null) {
if (conn.isConnected()) {
log.info("Disconnecting user=[{}] from meeting=[{}]", msg.getUserId(), msg.getMeetingId());
conn.close();
}
}
}
}
private void sendSharedObjectMessage(SharedObjectClientMessage msg) {
System.out.println("*********** Request to send [" + msg.getMessageName() + "] using shared object.");
IScope meetingScope = getScope(msg.getMeetingID());
if (meetingScope != null) {
if (meetingScope.hasChildScope(ScopeType.SHARED_OBJECT, msg.getSharedObjectName())) {
ISharedObject so = getSharedObject(meetingScope, msg.getSharedObjectName());
if (so != null) {
System.out.println("*********** Sending [" + msg.getMessageName() + "] using shared object.");
so.sendMessage(msg.getMessageName(), msg.getMessage());
} else {
System.out.println("**** Cannot get SO for [" + msg.getSharedObjectName() + "]");
}
} else {
System.out.println("**** No SO scope for [" + msg.getSharedObjectName() + "]");
}
} else {
System.out.println("**** No Meeting scope for [" + msg.getMeetingID() + "]");
}
}
private void sendDirectMessage(final DirectClientMessage msg) {
Runnable sender = new Runnable() {
public void run() {
IScope meetingScope = getScope(msg.getMeetingID());
if (meetingScope != null) {
log.debug("Found scope =[{}] for meeting=[{}]", meetingScope.getName(), msg.getMeetingID());
IConnection conn = getConnection(meetingScope, msg.getUserID());
if (conn != null) {
if (conn.isConnected()) {
List<Object> params = new ArrayList<Object>();
params.add(msg.getMessageName());
params.add(msg.getMessage());
log.debug("Sending message=[{}] to meeting=[{}]", msg.getMessageName(), msg.getMeetingID());
ServiceUtils.invokeOnConnection(conn, "onMessageFromServer", params.toArray());
} else {
log.warn("Connection not connected for userid=[{}] in meeting=[{}]", msg.getUserID(), msg.getMeetingID());
}
} else {
log.warn("No connection for userid=[{}] in meeting=[{}]", msg.getUserID(), msg.getMeetingID());
}
} else {
log.error("Failed to find scope for meeting=[{}]", msg.getMeetingID());
}
}
};
runExec.execute(sender);
}
private void sendBroadcastMessage(final BroadcastClientMessage msg) {
Runnable sender = new Runnable() {
public void run() {
IScope meetingScope = getScope(msg.getMeetingID());
if (meetingScope != null) {
List<Object> params = new ArrayList<Object>();
params.add(msg.getMessageName());
params.add(msg.getMessage());
ServiceUtils.invokeOnAllScopeConnections(meetingScope, "onMessageFromServer", params.toArray(), null);
}
}
};
runExec.execute(sender);
}
private IConnection getConnection(IScope scope, String userID) {
Set<IConnection> conns = scope.getClientConnections();
for (IConnection conn : conns) {
String connID = (String) conn.getAttribute("USERID");
if (connID != null && connID.equals(userID)) {
return conn;
}
}
return null;
}
public IScope getScope(String meetingID) {
if (bbbAppScope != null) {
return bbbAppScope.getContext().resolveScope("screenshare/" + meetingID);
} else {
log.error("BigBlueButton Scope not initialized. No messages are going to the Flash client!");
}
return null;
}
private ISharedObject getSharedObject(IScope scope, String name) {
ISharedObjectService service = (ISharedObjectService) ScopeUtils.getScopeService(scope, ISharedObjectService.class, SharedObjectService.class, false);
return service.getSharedObject(scope, name);
}
}

View File

@ -0,0 +1,61 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.red5;
import java.util.Map;
public class DirectClientMessage implements ClientMessage {
private String meetingID;
private String userID;
private Map<String, Object> message;
private String messageName;
private String sharedObjectName;
public DirectClientMessage(String meetingID, String userID, String messageName, Map<String, Object> message) {
this.meetingID = meetingID;
this.userID = userID;
this.message = message;
this.messageName = messageName;
}
public void setSharedObjectName(String name) {
sharedObjectName = name;
}
public String getSharedObjectName() {
return sharedObjectName;
}
public String getMeetingID() {
return meetingID;
}
public String getUserID() {
return userID;
}
public String getMessageName() {
return messageName;
}
public Map<String, Object> getMessage() {
return message;
}
}

View File

@ -0,0 +1,14 @@
package org.bigbluebutton.app.screenshare.red5;
public class DisconnectAllClientsMessage implements ClientMessage {
private final String meetingId;
public DisconnectAllClientsMessage(String meetingId) {
this.meetingId = meetingId;
}
public String getMeetingId() {
return meetingId;
}
}

View File

@ -0,0 +1,20 @@
package org.bigbluebutton.app.screenshare.red5;
public class DisconnectClientMessage implements ClientMessage {
private final String meetingId;
private final String userId;
public DisconnectClientMessage(String meetingId, String userId) {
this.meetingId = meetingId;
this.userId = userId;
}
public String getMeetingId() {
return meetingId;
}
public String getUserId() {
return userId;
}
}

View File

@ -0,0 +1,91 @@
package org.bigbluebutton.app.screenshare.red5;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.app.screenshare.events.IEvent;
import org.bigbluebutton.app.screenshare.events.IEventListener;
import org.bigbluebutton.app.screenshare.events.ShareStartedEvent;
import org.bigbluebutton.app.screenshare.events.ShareStoppedEvent;
import org.bigbluebutton.app.screenshare.events.StreamStartedEvent;
import org.bigbluebutton.app.screenshare.events.StreamStoppedEvent;
import com.google.gson.Gson;
public class EventListenerImp implements IEventListener {
private ConnectionInvokerService sender;
@Override
public void handleMessage(IEvent event) {
if (event instanceof ShareStartedEvent) {
sendShareStartedEvent((ShareStartedEvent) event);
} else if (event instanceof ShareStoppedEvent) {
sendShareStoppedEvent((ShareStoppedEvent) event);
} else if (event instanceof StreamStartedEvent) {
sendStreamStartedEvent((StreamStartedEvent) event);
} else if (event instanceof StreamStoppedEvent) {
sendStreamStoppedEvent((StreamStoppedEvent) event);
}
}
private void sendShareStartedEvent(ShareStartedEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
data.put("meetingId", event.meetingId);
data.put("streamId", event.streamId);
Map<String, Object> message = new HashMap<String, Object>();
Gson gson = new Gson();
message.put("msg", gson.toJson(data));
BroadcastClientMessage msg = new BroadcastClientMessage(event.meetingId, "screenShareStartedMessage", message);
sender.sendMessage(msg);
}
private void sendShareStoppedEvent(ShareStoppedEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
data.put("meetingId", event.meetingId);
data.put("streamId", event.streamId);
Map<String, Object> message = new HashMap<String, Object>();
Gson gson = new Gson();
message.put("msg", gson.toJson(data));
BroadcastClientMessage msg = new BroadcastClientMessage(event.meetingId, "screenShareStoppedMessage", message);
sender.sendMessage(msg);
}
private void sendStreamStartedEvent(StreamStartedEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
data.put("meetingId", event.meetingId);
data.put("streamId", event.streamId);
data.put("width", event.width);
data.put("height", event.height);
data.put("url", event.url);
Map<String, Object> message = new HashMap<String, Object>();
Gson gson = new Gson();
message.put("msg", gson.toJson(data));
BroadcastClientMessage msg = new BroadcastClientMessage(event.meetingId, "screenStreamStartedMessage", message);
sender.sendMessage(msg);
}
private void sendStreamStoppedEvent(StreamStoppedEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
data.put("meetingId", event.meetingId);
data.put("streamId", event.streamId);
Map<String, Object> message = new HashMap<String, Object>();
Gson gson = new Gson();
message.put("msg", gson.toJson(data));
BroadcastClientMessage msg = new BroadcastClientMessage(event.meetingId, "screenStreamStoppedMessage", message);
sender.sendMessage(msg);
}
public void setMessageSender(ConnectionInvokerService sender) {
this.sender = sender;
}
}

View File

@ -0,0 +1,274 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.red5;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IServerStream;
import org.red5.server.api.stream.IStreamListener;
import org.red5.server.stream.ClientBroadcastStream;
import org.slf4j.Logger;
import com.google.gson.Gson;
import org.bigbluebutton.app.screenshare.EventRecordingService;
import org.bigbluebutton.app.screenshare.IScreenShareApplication;
import org.bigbluebutton.app.screenshare.ScreenshareStreamListener;
public class Red5AppAdapter extends MultiThreadedApplicationAdapter {
private static Logger log = Red5LoggerFactory.getLogger(Red5AppAdapter.class, "screenshare");
private EventRecordingService recordingService;
private final Map<String, IStreamListener> streamListeners = new HashMap<String, IStreamListener>();
private IScreenShareApplication app;
private String streamBaseUrl;
private ConnectionInvokerService sender;
private String recordingDirectory;
@Override
public boolean appStart(IScope app) {
super.appStart(app);
log.info("BBB Screenshare appStart");
sender.setAppScope(app);
return true;
}
@Override
public boolean appConnect(IConnection conn, Object[] params) {
log.info("BBB Screenshare appConnect");
return super.appConnect(conn, params);
}
@Override
public boolean roomConnect(IConnection conn, Object[] params) {
log.info("BBB Screenshare roomConnect");
return super.roomConnect(conn, params);
}
private String getConnectionType(String connType) {
if ("persistent".equals(connType.toLowerCase())) {
return "RTMP";
} else if("polling".equals(connType.toLowerCase())) {
return "RTMPT";
} else {
return connType.toUpperCase();
}
}
private String getUserId() {
String userid = (String) Red5.getConnectionLocal().getAttribute("USERID");
if ((userid == null) || ("".equals(userid))) userid = "unknown-userid";
return userid;
}
private String getMeetingId() {
String meetingId = (String) Red5.getConnectionLocal().getAttribute("MEETING_ID");
if ((meetingId == null) || ("".equals(meetingId))) meetingId = "unknown-meetingid";
return meetingId;
}
@Override
public void appDisconnect(IConnection conn) {
log.info("BBB Screenshare appDisconnect");
String connType = getConnectionType(Red5.getConnectionLocal().getType());
String connId = Red5.getConnectionLocal().getSessionId();
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", getMeetingId());
logData.put("userId", getUserId());
logData.put("connType", connType);
logData.put("connId", connId);
logData.put("event", "user_leaving_bbb_screenshare");
logData.put("description", "User leaving BBB Screenshare.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("User leaving bbb-screenshare: data={}", logStr);
super.appDisconnect(conn);
}
@Override
public void roomDisconnect(IConnection conn) {
log.info("BBB Screenshare roomDisconnect");
String connType = getConnectionType(Red5.getConnectionLocal().getType());
String connId = Red5.getConnectionLocal().getSessionId();
String meetingId = conn.getScope().getName();
String userId = getUserId();
app.userDisconnected(meetingId, userId);
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", getMeetingId());
logData.put("userId", userId);
logData.put("connType", connType);
logData.put("connId", connId);
logData.put("event", "user_leaving_bbb_screenshare");
logData.put("description", "User leaving BBB Screenshare.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("User leaving bbb-screenshare: data={}", logStr);
super.roomDisconnect(conn);
}
@Override
public void streamPublishStart(IBroadcastStream stream) {
super.streamPublishStart(stream);
}
@Override
public void streamBroadcastStart(IBroadcastStream stream) {
IConnection conn = Red5.getConnectionLocal();
super.streamBroadcastStart(stream);
log.info("streamBroadcastStart " + stream.getPublishedName() + "]");
String streamId = stream.getPublishedName();
Matcher matcher = STREAM_ID_PATTERN.matcher(stream.getPublishedName());
if (matcher.matches()) {
String meetingId = matcher.group(1).trim();
String url = streamBaseUrl + "/" + meetingId + "/" + streamId;
app.streamStarted(meetingId, streamId, url);
boolean recordVideoStream = app.recordStream(meetingId, streamId);
if (recordVideoStream) {
recordStream(stream);
ScreenshareStreamListener listener = new ScreenshareStreamListener(recordingService, recordingDirectory);
stream.addStreamListener(listener);
streamListeners.put(conn.getScope().getName() + "-" + stream.getPublishedName(), listener);
}
} else {
log.error("Invalid streamid format [{}]", streamId);
}
}
private Long genTimestamp() {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
}
private final Pattern STREAM_ID_PATTERN = Pattern.compile("(.*)-(.*)$");
@Override
public void streamBroadcastClose(IBroadcastStream stream) {
super.streamBroadcastClose(stream);
log.info("streamBroadcastStop " + stream.getPublishedName() + "]");
String streamId = stream.getPublishedName();
Matcher matcher = STREAM_ID_PATTERN.matcher(stream.getPublishedName());
if (matcher.matches()) {
String meetingId = matcher.group(1).trim();
app.streamStopped(meetingId, streamId);
boolean recordVideoStream = app.recordStream(meetingId, streamId);
if (recordVideoStream) {
IConnection conn = Red5.getConnectionLocal();
String scopeName;
if (conn != null) {
scopeName = conn.getScope().getName();
} else {
log.info("Connection local was null, using scope name from the stream: {}", stream);
scopeName = stream.getScope().getName();
}
IStreamListener listener = streamListeners.remove(scopeName + "-" + stream.getPublishedName());
if (listener != null) {
stream.removeStreamListener(listener);
}
String filename = recordingDirectory;
if (!filename.endsWith("/")) {
filename.concat("/");
}
filename = filename.concat(meetingId).concat("/").concat(stream.getPublishedName()).concat(".flv");
long publishDuration = (System.currentTimeMillis() - stream.getCreationTime()) / 1000;
log.info("streamBroadcastClose " + stream.getPublishedName() + " " + System.currentTimeMillis() + " " + scopeName);
Map<String, String> event = new HashMap<String, String>();
event.put("module", "Deskshare");
event.put("timestamp", new Long(System.currentTimeMillis()).toString());
event.put("meetingId", scopeName);
event.put("stream", stream.getPublishedName());
event.put("file", filename);
event.put("duration", new Long(publishDuration).toString());
event.put("eventName", "DeskshareStoppedEvent");
recordingService.record(scopeName, event);
}
} else {
log.error("Invalid streamid format [{}]", streamId);
}
}
/**
* A hook to record a stream. A file is written in webapps/video/streams/
* @param stream
*/
private void recordStream(IBroadcastStream stream) {
IConnection conn = Red5.getConnectionLocal();
long now = System.currentTimeMillis();
String recordingStreamName = stream.getPublishedName(); // + "-" + now; /** Comment out for now...forgot why I added this - ralam */
try {
log.info("Recording stream " + recordingStreamName );
ClientBroadcastStream cstream = (ClientBroadcastStream) this.getBroadcastStream(conn.getScope(), stream.getPublishedName());
cstream.saveAs(recordingStreamName, false);
} catch(Exception e) {
log.error("ERROR while recording stream " + e.getMessage());
e.printStackTrace();
}
}
public void setEventRecordingService(EventRecordingService s) {
recordingService = s;
}
public void setStreamBaseUrl(String baseUrl) {
streamBaseUrl = baseUrl;
}
public void setRecordingDirectory(String dir) {
recordingDirectory = dir;
}
public void setApplication(IScreenShareApplication app) {
this.app = app;
}
public void setMessageSender(ConnectionInvokerService sender) {
this.sender = sender;
}
}

View File

@ -0,0 +1,93 @@
package org.bigbluebutton.app.screenshare.red5;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bigbluebutton.app.screenshare.IScreenShareApplication;
import org.bigbluebutton.app.screenshare.IsScreenSharingResponse;
import org.bigbluebutton.app.screenshare.StartShareRequestResponse;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import com.google.gson.Gson;
public class Red5AppHandler {
private static Logger log = Red5LoggerFactory.getLogger(Red5AppHandler.class, "screenshare");
private IScreenShareApplication app;
private ConnectionInvokerService sender;
private final Pattern STREAM_ID_PATTERN = Pattern.compile("(.*)-(.*)$");
public void isScreenSharing(String meetingId, String userId) {
IsScreenSharingResponse resp = app.isScreenSharing(meetingId);
Map<String, Object> data = new HashMap<String, Object>();
data.put("sharing", resp.info.sharing);
if (resp.info.sharing) {
data.put("streamId", resp.info.streamId);
data.put("width", resp.info.width);
data.put("height", resp.info.height);
data.put("url", resp.info.url);
}
Map<String, Object> message = new HashMap<String, Object>();
Gson gson = new Gson();
message.put("msg", gson.toJson(data));
log.info("Sending isSharingScreenRequestResponse to client, meetingId=" + meetingId + " userid=" + userId);
DirectClientMessage msg = new DirectClientMessage(meetingId, userId, "isSharingScreenRequestResponse", message);
sender.sendMessage(msg);
}
public void startShareRequest(String meetingId, String userId, Boolean record) {
StartShareRequestResponse resp = app.startShareRequest(meetingId, userId, record);
Map<String, Object> data = new HashMap<String, Object>();
if (resp.error != null) {
data.put("error", resp.error.reason);
} else {
data.put("authToken", resp.token);
data.put("jnlp", resp.jnlp);
}
Map<String, Object> message = new HashMap<String, Object>();
Gson gson = new Gson();
message.put("msg", gson.toJson(data));
log.info("Sending startShareRequestResponse to client, meetingId=" + meetingId + " userid=" + userId);
DirectClientMessage msg = new DirectClientMessage(meetingId, userId, "startShareRequestResponse", message);
sender.sendMessage(msg);
}
public void stopShareRequest(String meetingId, String streamId) {
Matcher matcher = STREAM_ID_PATTERN.matcher(streamId);
if (matcher.matches()) {
app.stopShareRequest(meetingId, streamId);
}
Map<String, Object> data = new HashMap<String, Object>();
data.put("meetingId", meetingId);
data.put("streamId", streamId);
Map<String, Object> message = new HashMap<String, Object>();
Gson gson = new Gson();
message.put("msg", gson.toJson(data));
log.info("Sending stopShareRequest to client, meetingId=" + meetingId + " streamId=" + streamId);
BroadcastClientMessage msg = new BroadcastClientMessage(meetingId, "stopViewingStream", message);
sender.sendMessage(msg);
}
public void setApplication(IScreenShareApplication app) {
this.app = app;
}
public void setMessageSender(ConnectionInvokerService sender) {
this.sender = sender;
}
}

View File

@ -0,0 +1,87 @@
package org.bigbluebutton.app.screenshare.red5;
import java.util.HashMap;
import java.util.Map;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.Red5;
import org.slf4j.Logger;
import com.google.gson.Gson;
public class Red5AppService {
private static Logger log = Red5LoggerFactory.getLogger(Red5AppService.class, "screenshare");
private Red5AppHandler handler;
/**
* Called from the client to pass us the userId.
*
* We need to to this as we can't have params on the connect call
* as FFMeeg won't be able to connect.
* @param userId
*/
public void setUserId(Map<String, Object> msg) {
String meetingId = Red5.getConnectionLocal().getScope().getName();
String userId = (String) msg.get("userId");
Red5.getConnectionLocal().setAttribute("MEETING_ID", meetingId);
Red5.getConnectionLocal().setAttribute("USERID", userId);
String connType = getConnectionType(Red5.getConnectionLocal().getType());
String connId = Red5.getConnectionLocal().getSessionId();
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", meetingId);
logData.put("userId", userId);
logData.put("connType", connType);
logData.put("connId", connId);
logData.put("event", "user_joining_bbb_screenshare");
logData.put("description", "User joining BBB Screenshare.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("User joining bbb-screenshare: data={}", logStr);
}
private String getConnectionType(String connType) {
if ("persistent".equals(connType.toLowerCase())) {
return "RTMP";
} else if("polling".equals(connType.toLowerCase())) {
return "RTMPT";
} else {
return connType.toUpperCase();
}
}
public void isScreenSharing(Map<String, Object> msg) {
String meetingId = Red5.getConnectionLocal().getScope().getName();
log.debug("Received check if publishing for meeting=[{}]", meetingId);
String userId = (String) Red5.getConnectionLocal().getAttribute("USERID");
handler.isScreenSharing(meetingId, userId);
}
public void startShareRequest(Map<String, Object> msg) {
Boolean record = (Boolean) msg.get("record");
String meetingId = Red5.getConnectionLocal().getScope().getName();
log.debug("Received startShareRequest for meeting=[{}]", meetingId);
String userId = (String) Red5.getConnectionLocal().getAttribute("USERID");
handler.startShareRequest(meetingId, userId, record);
}
public void stopShareRequest(Map<String, Object> msg) {
String meetingId = Red5.getConnectionLocal().getScope().getName();
String streamId = (String) msg.get("streamId");
log.debug("Received stopShareRequest for meeting=[{}]", meetingId);
handler.stopShareRequest(meetingId, streamId);
}
public void setAppHandler(Red5AppHandler handler) {
this.handler = handler;
}
}

View File

@ -0,0 +1,59 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.red5;
import java.util.ArrayList;
public class SharedObjectClientMessage implements ClientMessage {
public static final String BROADCAST = "broadcast";
public static final String DIRECT = "direct";
public static final String SHAREDOBJECT = "sharedobject";
private String meetingID;
private String sharedObjectName;
private ArrayList<Object> message;
private String messageName;
public SharedObjectClientMessage(String meetingID, String sharedObjectName, String messageName, ArrayList<Object> message) {
this.meetingID = meetingID;
this.message = message;
this.sharedObjectName = sharedObjectName;
this.messageName = messageName;
}
public void setSharedObjectName(String name) {
sharedObjectName = name;
}
public String getSharedObjectName() {
return sharedObjectName;
}
public String getMeetingID() {
return meetingID;
}
public String getMessageName() {
return messageName;
}
public ArrayList<Object> getMessage() {
return message;
}
}

View File

@ -0,0 +1,38 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.messages;
public class CaptureEndMessage {
private final String room;
private final int sequenceNum;
public CaptureEndMessage(String room, int sequenceNum) {
this.room = room;
this.sequenceNum = sequenceNum;
}
public String getRoom() {
return room;
}
public int getSequenceNum() {
return sequenceNum;
}
}

View File

@ -0,0 +1,58 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.messages;
import org.bigbluebutton.app.screenshare.server.session.Dimension;
public class CaptureStartMessage {
private final String room;
private final Dimension screenDim;
private final Dimension blockDim;
private final int sequenceNum;
private final boolean useSVC2;
public CaptureStartMessage(String room, Dimension screen, Dimension block, int sequenceNum, boolean useSVC2) {
this.room = room;
screenDim = screen;
blockDim = block;
this.sequenceNum = sequenceNum;
this.useSVC2 = useSVC2;
}
public Dimension getScreenDimension() {
return screenDim;
}
public Dimension getBlockDimension() {
return blockDim;
}
public String getRoom() {
return room;
}
public int getSequenceNum() {
return sequenceNum;
}
public boolean isUseSVC2() {
return useSVC2;
}
}

View File

@ -0,0 +1,55 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.messages;
public class CaptureUpdateMessage {
private final String room;
private final int position;
private final byte[] videoData;
private final boolean isKeyFrame;
private final int sequenceNum;
public CaptureUpdateMessage(String room, int position, byte[] videoData, boolean isKeyFrame, int sequenceNum) {
this.room = room;
this.position = position;
this.videoData = videoData;
this.isKeyFrame = isKeyFrame;
this.sequenceNum = sequenceNum;
}
public String getRoom() {
return room;
}
public int getPosition() {
return position;
}
public byte[] getVideoData() {
return videoData;
}
public boolean isKeyFrame() {
return isKeyFrame;
}
public int getSequenceNum() {
return sequenceNum;
}
}

View File

@ -0,0 +1,46 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.messages;
import java.awt.Point;
public class MouseLocationMessage {
private String room;
private Point loc;
private final int sequenceNum;
public MouseLocationMessage(String room, Point loc, int sequenceNum) {
this.room = room;
this.loc = loc;
this.sequenceNum = sequenceNum;
}
public String getRoom() {
return room;
}
public Point getLoc() {
return loc;
}
public int getSequenceNum() {
return sequenceNum;
}
}

View File

@ -0,0 +1,59 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder;
import java.util.concurrent.TimeUnit;
import org.bigbluebutton.app.screenshare.server.recorder.event.AbstractDeskshareRecordEvent;
import org.bigbluebutton.app.screenshare.server.recorder.event.RecordEvent;
import org.bigbluebutton.app.screenshare.server.recorder.event.RecordStartedEvent;
import org.bigbluebutton.app.screenshare.server.recorder.event.RecordStoppedEvent;
import redis.clients.jedis.Jedis;
public class EventRecorder implements RecordStatusListener {
private static final String COLON=":";
private String host;
private int port;
public EventRecorder(String host, int port){
this.host = host;
this.port = port;
}
private Long genTimestamp() {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
}
private void record(String session, RecordEvent message) {
Jedis jedis = new Jedis(host, port);
Long msgid = jedis.incr("global:nextRecordedMsgId");
jedis.hmset("recording" + COLON + session + COLON + msgid, message.toMap());
jedis.rpush("meeting" + COLON + session + COLON + "recordings", msgid.toString());
}
@Override
public void notify(RecordEvent event) {
if ((event instanceof RecordStoppedEvent) || (event instanceof RecordStartedEvent)) {
event.setTimestamp(genTimestamp());
event.setMeetingId(((AbstractDeskshareRecordEvent)event).getSession());
record(((AbstractDeskshareRecordEvent)event).getSession(), event);
}
}
}

View File

@ -0,0 +1,25 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder;
import org.bigbluebutton.app.screenshare.server.recorder.event.RecordEvent;
public interface RecordStatusListener {
void notify(RecordEvent event);
}

View File

@ -0,0 +1,42 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder;
import java.util.HashSet;
import java.util.Set;
import org.bigbluebutton.app.screenshare.server.recorder.event.RecordEvent;
public class RecordStatusListeners {
private final Set<RecordStatusListener> listeners = new HashSet<RecordStatusListener>();
public void addListener(RecordStatusListener l) {
listeners.add(l);
}
public void removeListener(RecordStatusListener l) {
listeners.remove(l);
}
public void notifyListeners(RecordEvent event) {
for (RecordStatusListener listener: listeners) {
listener.notify(event);
}
}
}

View File

@ -0,0 +1,29 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder;
import org.apache.mina.core.buffer.IoBuffer;
public interface Recorder {
public void record(IoBuffer frame);
public void start();
public void stop();
public void addListener(RecordStatusListener l);
public void removeListener(RecordStatusListener l);
}

View File

@ -0,0 +1,28 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder;
public interface RecordingService {
/**
* Get a recorder for a particular stream
* @param name the name of the stream
* @return the recorder for the stream
*/
Recorder getRecorderFor(String name);
}

View File

@ -0,0 +1,33 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder.event;
public class AbstractDeskshareRecordEvent extends RecordEvent {
private String session;
public AbstractDeskshareRecordEvent(String session) {
setModule("Deskshare");
this.session = session;
}
public String getSession() {
return session;
}
}

View File

@ -0,0 +1,37 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder.event;
public class RecordErrorEvent extends AbstractDeskshareRecordEvent {
private String reason;
public RecordErrorEvent(String session) {
super(session);
}
public void setReason(String reason) {
this.reason = reason;
}
public String getReason() {
return reason;
}
}

View File

@ -0,0 +1,81 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder.event;
import java.util.HashMap;
/**
* Abstract class for all events that need to be recorded.
* @author Richard Alam
*
*/
public abstract class RecordEvent {
protected final HashMap<String, String> eventMap = new HashMap<String, String>();
protected final static String MODULE = "module";
protected final static String TIMESTAMP = "timestamp";
protected final static String MEETING = "meetingId";
protected final static String EVENT = "eventName";
/**
* Set the module that generated the event.
* @param module
*/
public final void setModule(String module) {
eventMap.put(MODULE, module);
}
/**
* Set the timestamp of the event.
* @param timestamp
*/
public final void setTimestamp(long timestamp) {
eventMap.put(TIMESTAMP, Long.toString(timestamp));
}
/**
* Set the meetingId for this particular event.
* @param meetingId
*/
public final void setMeetingId(String meetingId) {
eventMap.put(MEETING, meetingId);
}
/**
* Set the name of the event.
* @param event
*/
public final void setEvent(String event) {
eventMap.put(EVENT, event);
}
/**
* Convert the event into a Map to be recorded.
* @return
*/
public final HashMap<String, String> toMap() {
return eventMap;
}
@Override
public String toString() {
return eventMap.get(MODULE) + " " + eventMap.get(EVENT);
}
}

View File

@ -0,0 +1,31 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder.event;
public class RecordStartedEvent extends AbstractDeskshareRecordEvent {
public RecordStartedEvent(String session) {
super(session);
setEvent("DeskshareStartedEvent");
}
public void setFile(String path) {
eventMap.put("file", path);
}
}

View File

@ -0,0 +1,31 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder.event;
public class RecordStoppedEvent extends AbstractDeskshareRecordEvent {
public RecordStoppedEvent(String session) {
super(session);
setEvent("DeskshareStoppedEvent");
}
public void setFile(String path) {
eventMap.put("file", path);
}
}

View File

@ -0,0 +1,25 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.recorder.event;
public class RecordUpdateEvent extends AbstractDeskshareRecordEvent {
public RecordUpdateEvent(String session) {
super(session);
}
}

View File

@ -0,0 +1,135 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.servlet;
import java.util.*;
import java.awt.Point;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.bigbluebutton.app.screenshare.IScreenShareApplication;
import org.bigbluebutton.app.screenshare.server.session.Dimension;
import org.bigbluebutton.app.screenshare.server.session.ISessionManagerGateway;
import org.bigbluebutton.app.screenshare.server.socket.BlockStreamEventMessageHandler;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
public class HttpTunnelStreamController extends MultiActionController {
final private Logger log = Red5LoggerFactory.getLogger(HttpTunnelStreamController.class, "screenshare");
private boolean hasSessionManager = false;
private IScreenShareApplication screenShareApplication;
public ModelAndView screenCaptureHandler(HttpServletRequest request, HttpServletResponse response) throws Exception {
String event = request.getParameterValues("event")[0];
int captureRequest = Integer.parseInt(event);
if (0 == captureRequest) {
handleCaptureStartRequest(request, response);
response.setStatus(HttpServletResponse.SC_OK);
} else if (1 == captureRequest) {
handleCaptureUpdateRequest(request, response);
response.setStatus(HttpServletResponse.SC_OK);
if (isSharingStopped(request, response)) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
} else if (2 == captureRequest) {
handleCaptureEndRequest(request, response);
response.setStatus(HttpServletResponse.SC_OK);
} else {
log.warn("Cannot handle screen capture event " + captureRequest);
response.setStatus(HttpServletResponse.SC_OK);
}
return null;
}
private Boolean isSharingStopped(HttpServletRequest request, HttpServletResponse response) throws Exception {
String meetingId = request.getParameterValues("meetingId")[0];
String streamId = request.getParameterValues("streamId")[0];
boolean stopped = screenShareApplication.isSharingStopped(meetingId, streamId);
if (stopped) {
log.info("Screensharing for stream={} has stopped.", streamId);
}
return stopped;
}
private void handleCaptureStartRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String meetingId = request.getParameterValues("meetingId")[0];
String streamId = request.getParameterValues("streamId")[0];
String screenInfo = request.getParameterValues("screenInfo")[0];
String[] screen = screenInfo.split("x");
if (! hasSessionManager) {
screenShareApplication = getScreenShareApplication();
hasSessionManager = true;
}
screenShareApplication.sharingStarted(meetingId, streamId, Integer.parseInt(screen[0]), Integer.parseInt(screen[1]));
}
private void handleCaptureUpdateRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String meetingId = request.getParameterValues("meetingId")[0];
String streamId = request.getParameterValues("streamId")[0];
log.debug("Received stream update message for meetingId={} streamId={}", meetingId, streamId);
if (! hasSessionManager) {
screenShareApplication = getScreenShareApplication();
hasSessionManager = true;
}
screenShareApplication.updateShareStatus(meetingId, streamId, 0);
}
private void handleCaptureEndRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String meetingId = request.getParameterValues("meetingId")[0];
String streamId = request.getParameterValues("streamId")[0];
if (! hasSessionManager) {
screenShareApplication = getScreenShareApplication();
hasSessionManager = true;
}
System.out.println("HttpTunnel: Received Capture Enfd Event.");
screenShareApplication.sharingStopped(meetingId, streamId);
}
private IScreenShareApplication getScreenShareApplication() {
//Get the servlet context
ServletContext ctx = getServletContext();
//Grab a reference to the application context
ApplicationContext appCtx = (ApplicationContext) ctx.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
//Get the bean holding the parameter
IScreenShareApplication manager = (IScreenShareApplication) appCtx.getBean("screenShareApplication");
if (manager != null) {
log.debug("Got the IScreenShareApplication context: *****");
}
return manager;
}
}

View File

@ -0,0 +1,48 @@
package org.bigbluebutton.app.screenshare.server.servlet;
import org.bigbluebutton.app.screenshare.IScreenShareApplication;
import org.bigbluebutton.app.screenshare.ScreenShareInfo;
import org.bigbluebutton.app.screenshare.ScreenShareInfoResponse;
public class JnlpConfigurator {
private String jnlpUrl;
private IScreenShareApplication screenShareApplication;
private String streamBaseUrl;
private String codecOptions;
public String getJnlpUrl() {
return jnlpUrl;
}
public void setJnlpUrl(String url) {
this.jnlpUrl = url;
}
public void setStreamBaseUrl(String baseUrl) {
streamBaseUrl = baseUrl;
}
public String getStreamBaseUrl() {
return streamBaseUrl;
}
public void setCodecOptions(String codeOptions) {
this.codecOptions = codeOptions;
}
public String getCodecOptions() {
return codecOptions;
}
public ScreenShareInfo getScreenShareInfo(String meetingId, String token) {
ScreenShareInfoResponse resp = screenShareApplication.getScreenShareInfo(meetingId, token);
if (resp.error != null) return null;
else return resp.info;
}
public void setApplication(IScreenShareApplication screenShareApplication) {
this.screenShareApplication = screenShareApplication;
}
}

View File

@ -0,0 +1,41 @@
/**
*
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
**/
package org.bigbluebutton.app.screenshare.server.session;
public final class Dimension {
private final int width;
private final int height;
public Dimension(int width, int height) {
this.width = width;
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
}

View File

@ -0,0 +1,35 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.session;
/**
* Interface between Java -> Scala
* @author Richard Alam
*
*/
public interface ISessionManagerGateway {
public void createSession(String streamId);
public void removeSession(String streamId, int seqNum);
public void updateSession(String streamId, int seqNum);
public boolean isSharingStopped(String meetingId);
}

View File

@ -0,0 +1,118 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.socket;
import org.apache.mina.core.future.CloseFuture;
import org.bigbluebutton.app.screenshare.IScreenShareApplication;
import org.bigbluebutton.app.screenshare.server.messages.CaptureEndMessage;
import org.bigbluebutton.app.screenshare.server.messages.CaptureStartMessage;
import org.bigbluebutton.app.screenshare.server.messages.CaptureUpdateMessage;
import org.bigbluebutton.app.screenshare.server.messages.MouseLocationMessage;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
public class BlockStreamEventMessageHandler extends IoHandlerAdapter {
final private Logger log = Red5LoggerFactory.getLogger(BlockStreamEventMessageHandler.class, "screenshare");
private IScreenShareApplication app;
private static final String ROOM = "ROOM";
@Override
public void exceptionCaught( IoSession session, Throwable cause ) throws Exception {
log.warn(cause.toString() + " \n " + cause.getMessage());
cause.printStackTrace();
closeSession(session);
}
private void closeSession(IoSession session) {
String room = (String)session.getAttribute(ROOM, null);
if (room != null) {
log.info("Closing session [" + room + "]. ");
} else {
log.info("Cannot determine session to close.");
}
CloseFuture future = session.close(true);
}
@Override
public void messageReceived( IoSession session, Object message ) throws Exception
{
if (message instanceof CaptureStartMessage) {
System.out.println("Got CaptureStartBlockEvent");
CaptureStartMessage event = (CaptureStartMessage) message;
// sessionManager.createSession(event.getRoom());
} else if (message instanceof CaptureUpdateMessage) {
// System.out.println("Got CaptureUpdateBlockEvent");
CaptureUpdateMessage event = (CaptureUpdateMessage) message;
// sessionManager.updateBlock(event.getRoom(), event.getSequenceNum());
if (app.isSharingStopped(event.getRoom(), event.getRoom())) {
// The flash client told us to stop sharing. Force stopping by closing connection from applet.
// We're changing how to tell the applet to stop sharing as AS ExternalInterface to JS to Applet calls
// generates a popup dialog that users may or may not see causing the browser to hang. (ralam aug 24, 2014)
log.info("Sharing has stopped for meeting [" + event.getRoom() + "]. Closing connection.");
session.close(true);
}
} else if (message instanceof CaptureEndMessage) {
CaptureEndMessage event = (CaptureEndMessage) message;
// sessionManager.removeSession(event.getRoom(), event.getSequenceNum());
} else if (message instanceof MouseLocationMessage) {
MouseLocationMessage event = (MouseLocationMessage) message;
// sessionManager.updateMouseLocation(event.getRoom(), event.getLoc(), event.getSequenceNum());
}
}
@Override
public void sessionIdle( IoSession session, IdleStatus status ) throws Exception
{
log.debug( "IDLE " + session.getIdleCount( status ));
super.sessionIdle(session, status);
}
@Override
public void sessionCreated(IoSession session) throws Exception {
log.debug("Session Created");
super.sessionCreated(session);
}
@Override
public void sessionOpened(IoSession session) throws Exception {
log.debug("Session Opened.");
super.sessionOpened(session);
}
@Override
public void sessionClosed(IoSession session) throws Exception {
log.debug("Session Closed.");
String room = (String) session.getAttribute("ROOM");
if (room != null) {
log.debug("Session Closed for room " + room);
app.sharingStopped(room, room);
} else {
log.warn("Closing session for a NULL room");
}
}
public void setApplication(IScreenShareApplication app) {
this.app = app;
}
}

View File

@ -0,0 +1,283 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.socket;
import java.awt.Point;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.Arrays;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.bigbluebutton.app.screenshare.server.messages.CaptureEndMessage;
import org.bigbluebutton.app.screenshare.server.messages.CaptureStartMessage;
import org.bigbluebutton.app.screenshare.server.messages.CaptureUpdateMessage;
import org.bigbluebutton.app.screenshare.server.messages.MouseLocationMessage;
import org.bigbluebutton.app.screenshare.server.session.Dimension;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
public class BlockStreamProtocolDecoder extends CumulativeProtocolDecoder {
final private Logger log = Red5LoggerFactory.getLogger(BlockStreamProtocolDecoder.class, "screenshare");
private static final String ROOM = "ROOM";
private static final byte[] POLICY_REQUEST = new byte[] {'<','p','o','l','i','c','y','-','f','i','l','e','-','r','e','q','u','e','s','t','/','>',0};
private static final byte[] END_FRAME = new byte[] {'S', 'S', '-', 'E', 'N', 'D'};
private static final byte[] HEADER = new byte[] {'B', 'B', 'B', '-', 'S', 'S'};
private static final byte CAPTURE_START_EVENT = 0;
private static final byte CAPTURE_UPDATE_EVENT = 1;
private static final byte CAPTURE_END_EVENT = 2;
private static final byte MOUSE_LOCATION_EVENT = 3;
protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
// Remember the initial position.
int start = in.position();
byte[] endFrame = new byte[END_FRAME.length];
// Now find the END FRAME delimeter in the buffer.
int curpos = 0;
while (in.remaining() >= END_FRAME.length) {
curpos = in.position();
in.get(endFrame);
if (Arrays.equals(endFrame, END_FRAME)) {
//log.debug("***** END FRAME {} = {}", endFrame, END_FRAME);
// Remember the current position and limit.
int position = in.position();
int limit = in.limit();
try {
in.position(start);
in.limit(position);
// The bytes between in.position() and in.limit()
// now contain a full frame.
parseFrame(session, in.slice(), out);
} finally {
// Set the position to point right after the
// detected END FRAME and set the limit to the old
// one.
in.position(position);
in.limit(limit);
}
return true;
}
in.position(curpos+1);
}
// Try to find a policy request, used by the BigBlueButton Client Checker
in.position(start);
if (tryToParsePolicyRequest(session, in, out)) {
return true;
}
// Could not find END FRAME in the buffer. Reset the initial
// position to the one we recorded above.
in.position(start);
return false;
}
private boolean tryToParsePolicyRequest(IoSession session, IoBuffer in, ProtocolDecoderOutput out) {
byte[] message = new byte[POLICY_REQUEST.length];
if (in.remaining() >= POLICY_REQUEST.length) {
in.get(message, 0, message.length);
if (Arrays.equals(message, POLICY_REQUEST)) {
log.debug("Sending cross domain policy to the user");
IoBuffer buffer = IoBuffer.allocate(8);
buffer.setAutoExpand(true);
try {
buffer.putString("<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>", Charset.forName("UTF-8").newEncoder());
buffer.put((byte) 0);
} catch (CharacterCodingException e) {
e.printStackTrace();
return false;
}
buffer.flip();
session.write(buffer);
return true;
}
}
return false;
}
private void parseFrame(IoSession session, IoBuffer in, ProtocolDecoderOutput out) {
//log.debug("Frame = {}", in.toString());
try {
byte[] header = new byte[HEADER.length];
in.get(header, 0, HEADER.length);
if (! Arrays.equals(header, HEADER)) {
log.info("Invalid header. Discarding. {}", header);
return;
}
int messageLength = in.getInt();
if (in.remaining() < messageLength) {
log.info("Invalid length. Discarding. [{} < {}]", in.remaining(), messageLength);
return;
}
decodeMessage(session, in, out);
return;
} catch (Exception e) {
log.warn("Failed to parse frame. Discarding.");
}
}
private void decodeMessage(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
byte event = in.get();
switch (event) {
case CAPTURE_START_EVENT:
log.info("Decoding CAPTURE_START_EVENT");
decodeCaptureStartEvent(session, in, out);
break;
case CAPTURE_UPDATE_EVENT:
//log.info("Decoding CAPTURE_UPDATE_EVENT");
decodeCaptureUpdateEvent(session, in, out);
break;
case CAPTURE_END_EVENT:
log.info("Got CAPTURE_END_EVENT event: " + event);
decodeCaptureEndEvent(session, in, out);
break;
case MOUSE_LOCATION_EVENT:
decodeMouseLocationEvent(session, in, out);
break;
default:
log.error("Unknown event: " + event);
throw new Exception("Unknown event: " + event);
}
}
private void decodeMouseLocationEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
String room = decodeRoom(session, in);
if ("".equals(room)) {
log.warn("Empty meeting name in decoding mouse location.");
throw new Exception("Empty meeting name in decoding mouse location.");
}
int seqNum = in.getInt();
int mouseX = in.getInt();
int mouseY = in.getInt();
/** Swallow end frame **/
in.get(new byte[END_FRAME.length]);
MouseLocationMessage event = new MouseLocationMessage(room, new Point(mouseX, mouseY), seqNum);
out.write(event);
}
private void decodeCaptureEndEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
String room = decodeRoom(session, in);
if ("".equals(room)) {
log.warn("Empty meeting name in decoding capture end event.");
throw new Exception("Empty meeting name in decoding capture end event.");
}
log.info("CaptureEndEvent for " + room);
int seqNum = in.getInt();
/** Swallow end frame **/
in.get(new byte[END_FRAME.length]);
CaptureEndMessage event = new CaptureEndMessage(room, seqNum);
out.write(event);
}
private void decodeCaptureStartEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
String room = decodeRoom(session, in);
if ("".equals(room)) {
log.warn("Empty meeting name in decoding capture start event.");
throw new Exception("Empty meeting name in decoding capture start event.");
}
session.setAttribute(ROOM, room);
int seqNum = in.getInt();
Dimension blockDim = decodeDimension(in);
Dimension screenDim = decodeDimension(in);
boolean useSVC2 = (in.get() == 1);
/** Swallow end frame **/
in.get(new byte[END_FRAME.length]);
log.info("CaptureStartEvent for " + room);
CaptureStartMessage event = new CaptureStartMessage(room, screenDim, blockDim, seqNum, useSVC2);
out.write(event);
}
private Dimension decodeDimension(IoBuffer in) {
int width = in.getInt();
int height = in.getInt();
return new Dimension(width, height);
}
private String decodeRoom(IoSession session, IoBuffer in) {
int roomLength = in.get();
// System.out.println("Room length = " + roomLength);
String room = "";
try {
room = in.getString(roomLength, Charset.forName( "UTF-8" ).newDecoder());
if (session.containsAttribute(ROOM)) {
String attRoom = (String) session.getAttribute(ROOM);
if (!attRoom.equals(room)) {
log.warn(room + " is not the same as room in attribute [" + attRoom + "]");
}
}
} catch (CharacterCodingException e) {
log.error(e.getMessage());
}
return room;
}
private void decodeCaptureUpdateEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
String room = decodeRoom(session, in);
if ("".equals(room)) {
log.warn("Empty meeting name in decoding capture start event.");
throw new Exception("Empty meeting name in decoding capture start event.");
}
int seqNum = in.getInt();
int numBlocks = in.getShort();
String blocksStr = "Blocks changed ";
for (int i = 0; i < numBlocks; i++) {
int position = in.getShort();
blocksStr += " " + position;
boolean isKeyFrame = (in.get() == 1) ? true : false;
int length = in.getInt();
byte[] data = new byte[length];
in.get(data, 0, length);
CaptureUpdateMessage event = new CaptureUpdateMessage(room, position, data, isKeyFrame, seqNum);
out.write(event);
}
/** Swallow end frame **/
in.get(new byte[END_FRAME.length]);
}
}

View File

@ -0,0 +1,67 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.socket;
import java.io.IOException;
import java.net.InetSocketAddress;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
public class DeskShareServer {
final private Logger log = Red5LoggerFactory.getLogger(DeskShareServer.class, "deskshare");
private int port = 1270;
private IoHandlerAdapter screenCaptureHandler;
private NioSocketAcceptor acceptor;
public void start()
{
acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter(new ScreenCaptureProtocolCodecFactory()));
acceptor.setHandler( screenCaptureHandler);
acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
acceptor.setReuseAddress(true);
try {
acceptor.bind( new InetSocketAddress(port) );
} catch (IOException e) {
log.error("IOException while binding to port {}", port);
}
}
public void setScreenCaptureHandler(IoHandlerAdapter screenCaptureHandler) {
this.screenCaptureHandler = screenCaptureHandler;
}
public void stop() {
acceptor.unbind();
acceptor.dispose();
}
public void setPort(int port) {
this.port = port;
}
}

View File

@ -0,0 +1,38 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.socket;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
public class NullProtocolEncoder implements ProtocolEncoder {
public void dispose(IoSession in) throws Exception {
// TODO Auto-generated method stub
}
public void encode(IoSession session, Object message, ProtocolEncoderOutput out)
throws Exception {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,42 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.socket;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;
public class ScreenCaptureProtocolCodecFactory implements ProtocolCodecFactory {
private ProtocolEncoder encoder;
private ProtocolDecoder decoder;
public ScreenCaptureProtocolCodecFactory() {
encoder = new NullProtocolEncoder();
decoder = new BlockStreamProtocolDecoder();
}
public ProtocolEncoder getEncoder(IoSession ioSession) throws Exception {
return encoder;
}
public ProtocolDecoder getDecoder(IoSession ioSession) throws Exception {
return decoder;
}
}

View File

@ -0,0 +1,32 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.app.screenshare.server.util;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
public final class StackTraceUtil {
public static String getStackTrace(Throwable aThrowable) {
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
aThrowable.printStackTrace(printWriter);
return result.toString();
}
}

View File

@ -0,0 +1,5 @@
package org.bigbluebutton.app.screenshare.store;
public interface IDataStore {
}

Some files were not shown because too many files have changed in this diff Show More