- new screenshare red5 app

This commit is contained in:
Richard Alam 2016-02-10 19:53:01 +00:00
parent dd37445b7d
commit bd30fd8c6f
220 changed files with 17418 additions and 0 deletions

38
bbb-screenshare/.classpath Executable file
View File

@ -0,0 +1,38 @@
<?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="output" path="bin"/>
</classpath>

8
bbb-screenshare/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.manager
.scala_dependencies
.classpath
.gradle/
.project
app/build/
lib/
build

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

View File

@ -0,0 +1,50 @@
<?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>
<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 {
}

View File

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

View File

@ -0,0 +1,79 @@
package org.bigbluebutton.app.screenshare.store.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 RedisDataStore {
private static Logger log = Red5LoggerFactory.getLogger(RedisDataStore.class, "screenshare");
private JedisPool redisPool;
private volatile boolean sendMessage = false;
private int maxThreshold = 1024;
private final Executor msgSenderExec = Executors.newSingleThreadExecutor();
private BlockingQueue<IScreenShareData> dataToStore = new LinkedBlockingQueue<IScreenShareData>();
private final Executor runExec = Executors.newSingleThreadExecutor();
public void stop() {
sendMessage = false;
}
public void start() {
try {
sendMessage = true;
Runnable messageSender = new Runnable() {
public void run() {
while (sendMessage) {
try {
IScreenShareData data = dataToStore.take();
storeData(data);
} catch (InterruptedException e) {
log.warn("Failed to get data from queue.");
}
}
}
};
msgSenderExec.execute(messageSender);
} catch (Exception e) {
log.error("Error storing data into redis: " + e.getMessage());
}
}
public void store(IScreenShareData data) {
if (dataToStore.size() > maxThreshold) {
log.warn("Queued number of data [{}] is greater than threshold [{}]", dataToStore.size(), maxThreshold);
}
dataToStore.add(data);
}
private void storeData(IScreenShareData data) {
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;
}
public void setMaxThreshold(int threshold) {
maxThreshold = threshold;
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="screenshare" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>log/screenshare-slf.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>log/screenshare-slf.%d{yyyy-MM-dd}.log</FileNamePattern>
<!-- keep 30 days worth of history -->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%d{ISO8601} [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="screenshare" />
</root>
<!-- LEVEL CAN NOT BE DEBUG -->
<logger name="org.apache" level="INFO"></logger>
</configuration>

View File

@ -0,0 +1,183 @@
package org.bigbluebutton.app.screenshare
import org.bigbluebutton.app.screenshare.events.IEventsMessageBus
import org.bigbluebutton.app.screenshare.server.sessions.ScreenshareSessionManager
import org.bigbluebutton.app.screenshare.server.sessions.messages._
import org.bigbluebutton.app.screenshare.server.util.LogHelper
class ScreenShareApplication(val bus: IEventsMessageBus, val jnlpFile: String,
val streamBaseUrl: String)
extends IScreenShareApplication with LogHelper {
val sessionManager: ScreenshareSessionManager = new ScreenshareSessionManager(bus)
sessionManager.start
val initError: Error = new Error("Uninitialized error.")
def userDisconnected(meetingId: String, userId: String) {
if (logger.isDebugEnabled()) {
logger.debug("Received user disconnected on meeting=" + meetingId
+ "] userid=[" + userId + "]")
}
sessionManager ! new UserDisconnected(meetingId, userId)
}
def isScreenSharing(meetingId: String):IsScreenSharingResponse = {
if (logger.isDebugEnabled()) {
logger.debug("Received is screen sharing on meeting=" + meetingId
+ "]")
}
var response: IsScreenSharingResponse = new IsScreenSharingResponse(null, initError)
sessionManager !? (3000, IsScreenSharing(meetingId)) match {
case None => {
logger.info("Failed to get response to is screen sharing request on meeting=" + meetingId + "]")
val info = new StreamInfo(false, "none", 0, 0, "none")
response = new IsScreenSharingResponse(info, new Error("Timedout waiting for response"))
}
case Some(rep) => {
val reply = rep.asInstanceOf[IsScreenSharingReply]
val info = new StreamInfo(true, reply.streamId, reply.width, reply.height, reply.url)
response = new IsScreenSharingResponse(info, null)
}
}
response
}
def getScreenShareInfo(meetingId: String, token: String):ScreenShareInfoResponse = {
if (logger.isDebugEnabled()) {
logger.debug("Received get screen sharing info on token=" + token
+ "]")
}
var response: ScreenShareInfoResponse = new ScreenShareInfoResponse(null, initError)
sessionManager !? (3000, ScreenShareInfoRequest(meetingId, token)) match {
case None => {
logger.info("Failed to get response to get screen sharing info request on token=" + token + "]")
response = new ScreenShareInfoResponse(null, new Error("Timedout waiting for response."))
}
case Some(rep) => {
val reply = rep.asInstanceOf[ScreenShareInfoRequestReply]
val publishUrl = streamBaseUrl + "/" + meetingId + "/" + reply.streamId
val info = new ScreenShareInfo(publishUrl, reply.streamId)
response = new ScreenShareInfoResponse(info, null)
}
}
response
}
def recordStream(meetingId: String, streamId: String):java.lang.Boolean = {
if (logger.isDebugEnabled()) {
logger.debug("Received record stream request on stream=" + streamId + "]")
}
var record = false
sessionManager !? (3000, IsStreamRecorded(meetingId, streamId)) match {
case None => {
logger.info("Failed to get response to record stream request on streamId="
+ streamId + "]")
record = false
}
case Some(rep) => {
val reply = rep.asInstanceOf[IsStreamRecordedReply]
record = reply.record
}
}
record
}
def startShareRequest(meetingId: String, userId: String, record: java.lang.Boolean): StartShareRequestResponse = {
if (logger.isDebugEnabled()) {
logger.debug("Received start share request on meeting=" + meetingId
+ "for user=" + userId + "]")
}
var response: StartShareRequestResponse = new StartShareRequestResponse(null, null, initError)
sessionManager !? (3000, StartShareRequestMessage(meetingId, userId, record)) match {
case None => {
logger.info("Failed to get response to start share request on meeting="
+ meetingId + " for user=" + userId + "]")
response = new StartShareRequestResponse(null, null, new Error("Timedout waiting for response"))
}
case Some(rep) => {
val reply = rep.asInstanceOf[StartShareRequestReplyMessage]
response = new StartShareRequestResponse(reply.token, jnlpFile, null)
}
}
response
}
def stopShareRequest(meetingId: String, streamId: String) {
if (logger.isDebugEnabled()) {
logger.debug("Received stop share request on meeting=[" + meetingId
+ "] for stream=[" + streamId + "]")
}
sessionManager ! new StopShareRequestMessage(meetingId, streamId)
}
def streamStarted(meetingId: String, streamId: String, url: String) {
if (logger.isDebugEnabled()) {
logger.debug("Received stream started on meeting=[" + meetingId
+ "] for stream=[" + streamId + "]")
}
sessionManager ! new StreamStartedMessage(meetingId, streamId, url)
}
def streamStopped(meetingId: String, streamId: String) {
if (logger.isDebugEnabled()) {
logger.debug("Received stream stopped on meeting=[" + meetingId
+ "] for stream=[" + streamId + "]")
}
sessionManager ! new StreamStoppedMessage(meetingId, streamId)
}
def sharingStarted(meetingId: String, streamId: String, width: java.lang.Integer, height: java.lang.Integer) {
if (logger.isDebugEnabled()) {
logger.debug("Received share started on meeting=[" + meetingId
+ "] for stream=[" + streamId + "] with region=[" + width + "x" + height + "]")
}
sessionManager ! new SharingStartedMessage(meetingId, streamId, width, height)
}
def sharingStopped(meetingId: String, streamId: String) {
if (logger.isDebugEnabled()) {
logger.debug("Received sharing stopped on meeting=" + meetingId
+ "for stream=" + streamId + "]")
}
sessionManager ! new SharingStoppedMessage(meetingId, streamId)
}
def updateShareStatus(meetingId: String, streamId : String, seqNum: java.lang.Integer) {
if (logger.isDebugEnabled()) {
logger.debug("Received sharing status on meeting=" + meetingId
+ "for stream=" + streamId + "]")
}
sessionManager ! new UpdateShareStatus(meetingId, streamId, seqNum)
}
def isSharingStopped(meetingId: String, streamId: String): java.lang.Boolean = {
if (logger.isDebugEnabled()) {
logger.debug("Received sharing status on meeting=" + meetingId
+ "for stream=" + streamId + "]")
}
var stopped = false
sessionManager !? (3000, IsSharingStopped(meetingId, streamId)) match {
case None => stopped = true
case Some(rep) => {
val reply = rep.asInstanceOf[IsSharingStoppedReply]
stopped = reply.stopped
}
}
stopped
}
}

View File

@ -0,0 +1,252 @@
package org.bigbluebutton.app.screenshare.server.sessions
import scala.actors.Actor
import scala.actors.Actor._
import scala.collection.mutable.HashMap
import org.bigbluebutton.app.screenshare.events.IEventsMessageBus
import org.bigbluebutton.app.screenshare.server.util._
import org.bigbluebutton.app.screenshare.server.sessions.messages._
class MeetingActor(val sessionManager: ScreenshareSessionManager,
val bus: IEventsMessageBus,
val meetingId: String) extends Actor with LogHelper {
private val sessions = new HashMap[String, ScreenshareSession]
private var lastHasSessionCheck:Long = TimeUtil.getCurrentMonoTime
private var activeSession:Option[ScreenshareSession] = None
private var stopped = false
private val IS_MEETING_RUNNING = "IsMeetingRunning"
def scheduleIsMeetingRunningCheck() {
val mainActor = self
actor {
Thread.sleep(60000)
mainActor ! IS_MEETING_RUNNING
}
}
def act() = {
loop {
react {
case msg: StartShareRequestMessage => handleStartShareRequestMessage(msg)
case msg: StopShareRequestMessage => handleStopShareRequestMessage(msg)
case msg: StreamStartedMessage => handleStreamStartedMessage(msg)
case msg: StreamStoppedMessage => handleStreamStoppedMessage(msg)
case msg: SharingStartedMessage => handleSharingStartedMessage(msg)
case msg: SharingStoppedMessage => handleSharingStoppedMessage(msg)
case msg: IsSharingStopped => handleIsSharingStopped(msg)
case msg: IsScreenSharing => handleIsScreenSharing(msg)
case msg: IsStreamRecorded => handleIsStreamRecorded(msg)
case msg: UpdateShareStatus => handleUpdateShareStatus(msg)
case msg: UserDisconnected => handleUserDisconnected(msg)
case msg: ScreenShareInfoRequest => handleScreenShareInfoRequest(msg)
case IS_MEETING_RUNNING => handleIsMeetingRunning()
case msg: KeepAliveTimeout => handleKeepAliveTimeout(msg)
case m: Any => logger.warn("Session: Unknown message [{}]", m)
}
}
}
private def findSessionByUser(userId: String):Option[ScreenshareSession] = {
sessions.values find (su => su.userId == userId)
}
private def findSessionWithToken(token: String):Option[ScreenshareSession] = {
sessions.values find (su => su.token == token)
}
private def handleUserDisconnected(msg: UserDisconnected) {
if (logger.isDebugEnabled()) {
logger.debug("Received UserDisconnected for meetingId=[" + msg.meetingId + "]")
}
findSessionByUser(msg.userId) foreach (s => s forward msg)
}
private def handleIsScreenSharing(msg: IsScreenSharing) {
if (logger.isDebugEnabled()) {
logger.debug("Received IsScreenSharing for meetingId=[" + msg.meetingId + "]")
}
activeSession foreach (s => s forward msg)
}
private def handleScreenShareInfoRequest(msg: ScreenShareInfoRequest) {
if (logger.isDebugEnabled()) {
logger.debug("Received ScreenShareInfoRequest for token=[" + msg.token + "]")
}
findSessionWithToken(msg.token) foreach (s => s forward msg)
}
private def handleIsStreamRecorded(msg: IsStreamRecorded) {
if (logger.isDebugEnabled()) {
logger.debug("Received IsStreamRecorded for streamId=[" + msg.streamId + "]")
}
sessions.get(msg.streamId) match {
case Some(session) => {
session forward msg
}
case None => {
logger.info("IsStreamRecorded on a non-existing session=[" + msg.streamId + "]")
}
}
}
private def handleUpdateShareStatus(msg: UpdateShareStatus) {
if (logger.isDebugEnabled()) {
logger.debug("Received UpdateShareStatus for streamId=[" + msg.streamId + "]")
}
sessions.get(msg.streamId) match {
case Some(session) => {
session forward msg
}
case None => {
logger.info("Sharing stopped on a non-existing session=[" + msg.streamId + "]")
}
}
}
private def handleSharingStoppedMessage(msg: SharingStoppedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received SharingStoppedMessage for streamId=[" + msg.streamId + "]")
}
sessions.get(msg.streamId) match {
case Some(session) => {
session forward msg
}
case None => {
logger.info("Sharing stopped on a non-existing session=[" + msg.streamId + "]")
}
}
}
private def handleSharingStartedMessage(msg: SharingStartedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received SharingStartedMessage for streamId=[" + msg.streamId + "]")
}
sessions.get(msg.streamId) match {
case Some(session) => {
session forward msg
}
case None => {
logger.info("Sharing started on a non-existing session=[" + msg.streamId + "]")
}
}
}
private def handleStreamStoppedMessage(msg: StreamStoppedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received StreamStoppedMessage for streamId=[" + msg.streamId + "]")
}
sessions.get(msg.streamId) match {
case Some(session) => {
session forward msg
activeSession = None
}
case None => {
logger.info("Stream stopped on a non-existing session=[" + msg.streamId + "]")
}
}
}
private def handleStreamStartedMessage(msg: StreamStartedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received StreamStartedMessage for streamId=[" + msg.streamId + "]")
}
sessions.get(msg.streamId) match {
case Some(session) => {
session forward msg
activeSession = Some(session)
}
case None => {
logger.info("Stream started on a non-existing session=[" + msg.streamId + "]")
}
}
}
private def handleStopShareRequestMessage(msg: StopShareRequestMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received StopShareRequestMessage for streamId=[" + msg.streamId + "]")
}
sessions.get(msg.streamId) match {
case Some(session) => {
session forward msg
}
case None => {
logger.info("Stop share request on a non-existing session=[" + msg.streamId + "]")
}
}
}
private def handleStartShareRequestMessage(msg: StartShareRequestMessage) {
val token = RandomStringGenerator.randomAlphanumericString(16)
val streamId = msg.meetingId + "-" + System.currentTimeMillis();
val session: ScreenshareSession = new ScreenshareSession(this, bus,
meetingId, streamId, token,
msg.record, msg.userId)
sessions += streamId -> session
session.start
session forward msg
}
private def handleIsSharingStopped(msg: IsSharingStopped) {
sessions.get(msg.streamId) match {
case Some(session) => {
session forward msg
}
case None => {
logger.info("Stream stopped on a non-existing session=[" + msg.streamId + "]")
}
}
}
private def handleStopSession() {
stopped = true
}
private def handleStartSession() {
stopped = false
scheduleIsMeetingRunningCheck
}
private def handleIsMeetingRunning() {
// If not sessions in the last 5 minutes, then assume meeting has ended.
if (sessions.isEmpty) {
if (TimeUtil.getCurrentMonoTime - lastHasSessionCheck > 300000) {
sessionManager ! MeetingHasEnded(meetingId)
} else {
scheduleIsMeetingRunningCheck
}
} else {
lastHasSessionCheck = TimeUtil.getCurrentMonoTime
scheduleIsMeetingRunningCheck
}
}
private def handleKeepAliveTimeout(msg: KeepAliveTimeout) {
sessions.remove(msg.streamId) foreach { s =>
if (activeSession != None) {
activeSession foreach { as =>
if (as.streamId == s.streamId) activeSession = None
}
}
}
}
}

View File

@ -0,0 +1,205 @@
/**
* 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.sessions
import scala.actors.Actor
import scala.actors.Actor._
import net.lag.logging.Logger
import org.bigbluebutton.app.screenshare.server.util.LogHelper
import org.bigbluebutton.app.screenshare.server.util.TimeUtil
import org.bigbluebutton.app.screenshare.server.sessions.messages._
import org.bigbluebutton.app.screenshare.events.IEventsMessageBus
import org.bigbluebutton.app.screenshare.events.ShareStartedEvent
import org.bigbluebutton.app.screenshare.events.ShareStoppedEvent
import org.bigbluebutton.app.screenshare.events.StreamStoppedEvent
import org.bigbluebutton.app.screenshare.events.StreamStartedEvent
case object StartSession
case object StopSession
case class KeepAliveTimeout(streamId: String)
class ScreenshareSession(parent: MeetingActor,
bus: IEventsMessageBus,
val meetingId: String,
val streamId: String,
val token: String,
val recorded: Boolean,
val userId: String) extends Actor with LogHelper {
private var timeOfLastKeepAliveUpdate:Long = TimeUtil.getCurrentMonoTime
private val KEEP_ALIVE_TIMEOUT = 60000
// if ffmpeg is still broadcasting
private var streamStopped = true
// if jws is still running
private var shareStopped = true
// if the user has requested to stop sharing
private var stopShareRequested = false
private var width: Int = 0
private var height: Int = 0
private var streamUrl: String = ""
private var timestamp = 0L;
private var lastUpdate:Long = System.currentTimeMillis();
private val IS_STREAM_ALIVE = "IsStreamAlive"
def scheduleKeepAliveCheck() {
val mainActor = self
actor {
Thread.sleep(5000)
mainActor ! IS_STREAM_ALIVE
}
}
def act() = {
loop {
react {
case msg: StartShareRequestMessage => handleStartShareRequestMessage(msg)
case msg: StopShareRequestMessage => handleStopShareRequestMessage(msg)
case msg: StreamStartedMessage => handleStreamStartedMessage(msg)
case msg: StreamStoppedMessage => handleStreamStoppedMessage(msg)
case msg: SharingStartedMessage => handleSharingStartedMessage(msg)
case msg: SharingStoppedMessage => handleSharingStoppedMessage(msg)
case msg: IsSharingStopped => handleIsSharingStopped(msg)
case msg: IsScreenSharing => handleIsScreenSharing(msg)
case msg: IsStreamRecorded => handleIsStreamRecorded(msg)
case msg: UpdateShareStatus => handleUpdateShareStatus(msg)
case msg: UserDisconnected => handleUserDisconnected(msg)
case msg: ScreenShareInfoRequest => handleScreenShareInfoRequest(msg)
case IS_STREAM_ALIVE => checkIfStreamIsAlive()
case m: Any => logger.warn("Session: Unknown message [%s]", m)
}
}
}
private def handleUserDisconnected(msg: UserDisconnected) {
if (logger.isDebugEnabled()) {
logger.debug("Received UserDisconnected for streamId=[" + streamId + "]")
}
stopShareRequested = true
}
private def handleIsStreamRecorded(msg: IsStreamRecorded) {
if (logger.isDebugEnabled()) {
logger.debug("Received IsStreamRecorded for streamId=[" + msg.streamId + "]")
}
reply(new IsStreamRecordedReply(recorded))
}
private def handleIsScreenSharing(msg: IsScreenSharing) {
if (logger.isDebugEnabled()) {
logger.debug("Received IsScreenSharing for meetingId=[" + msg.meetingId + "]")
}
reply(new IsScreenSharingReply(true, streamId, width, height, streamUrl))
}
private def handleScreenShareInfoRequest(msg: ScreenShareInfoRequest) {
if (logger.isDebugEnabled()) {
logger.debug("Received ScreenShareInfoRequest for token=" + msg.token + " streamId=[" + streamId + "]")
}
reply(new ScreenShareInfoRequestReply(msg.meetingId, streamId))
}
private def handleSharingStoppedMessage(msg: SharingStoppedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received SharingStoppedMessage for streamId=[" + msg.streamId + "]")
}
shareStopped = true
width = 0
height = 0
bus.send(new ShareStoppedEvent(meetingId, streamId))
}
private def handleSharingStartedMessage(msg: SharingStartedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received SharingStartedMessagefor streamId=[" + msg.streamId + "]")
}
stopShareRequested = false
shareStopped = false
width = msg.width
height = msg.height
bus.send(new ShareStartedEvent(meetingId, streamId))
}
private def handleStreamStoppedMessage(msg: StreamStoppedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received StreamStoppedMessage streamId=[" + msg.streamId + "]")
}
streamStopped = true
bus.send(new StreamStoppedEvent(meetingId, streamId))
}
private def handleStreamStartedMessage(msg: StreamStartedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received StreamStartedMessage for streamId=[" + msg.streamId + "]")
}
streamStopped = false
streamUrl = msg.url
bus.send(new StreamStartedEvent(meetingId, streamId, width, height, msg.url))
}
private def handleStopShareRequestMessage(msg: StopShareRequestMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received StopShareRequestMessage for streamId=[" + msg.streamId + "]")
}
stopShareRequested = true
bus.send(new ShareStoppedEvent(meetingId, streamId))
}
private def handleStartShareRequestMessage(msg: StartShareRequestMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received StartShareRequestMessage for streamId=[" + msg.meetingId + "]")
}
scheduleKeepAliveCheck()
reply(new StartShareRequestReplyMessage(token))
}
private def handleIsSharingStopped(msg: IsSharingStopped) {
reply(new IsSharingStoppedReply(stopShareRequested))
}
private def handleUpdateShareStatus(msg: UpdateShareStatus): Unit = {
timeOfLastKeepAliveUpdate = TimeUtil.getCurrentMonoTime
}
private def checkIfStreamIsAlive() {
if (TimeUtil.getCurrentMonoTime - timeOfLastKeepAliveUpdate > KEEP_ALIVE_TIMEOUT) {
logger.warn("Did not received updates for more than 1 minute. Removing stream {}", streamId)
parent ! new KeepAliveTimeout(streamId)
} else {
scheduleKeepAliveCheck()
}
}
}

View File

@ -0,0 +1,207 @@
/**
* 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.sessions
import scala.actors.Actor
import scala.actors.Actor._
import net.lag.logging.Logger
import scala.collection.mutable.HashMap
import org.bigbluebutton.app.screenshare.events.IEventsMessageBus
import org.bigbluebutton.app.screenshare.server.sessions.messages._
import org.bigbluebutton.app.screenshare.server.util.LogHelper
case class HasScreenShareSession(meetingId: String)
case class HasScreenShareSessionReply(meetingId: String, sharing: Boolean, streamId:Option[String])
case class MeetingHasEnded(meetingId: String)
class ScreenshareSessionManager(val bus: IEventsMessageBus)
extends Actor with LogHelper {
private val meetings = new HashMap[String, MeetingActor]
def act() = {
loop {
react {
case msg: StartShareRequestMessage => handleStartShareRequestMessage(msg)
case msg: StopShareRequestMessage => handleStopShareRequestMessage(msg)
case msg: StreamStartedMessage => handleStreamStartedMessage(msg)
case msg: StreamStoppedMessage => handleStreamStoppedMessage(msg)
case msg: SharingStartedMessage => handleSharingStartedMessage(msg)
case msg: SharingStoppedMessage => handleSharingStoppedMessage(msg)
case msg: IsStreamRecorded => handleIsStreamRecorded(msg)
case msg: IsSharingStopped => handleIsSharingStopped(msg)
case msg: IsScreenSharing => handleIsScreenSharing(msg)
case msg: ScreenShareInfoRequest => handleScreenShareInfoRequest(msg)
case msg: UpdateShareStatus => handleUpdateShareStatus(msg)
case msg: UserDisconnected => handleUserDisconnected(msg)
case msg: MeetingHasEnded => handleMeetingHasEnded(msg)
case msg: Any => logger.warn("Unknown message " + msg)
}
}
}
private def handleUserDisconnected(msg: UserDisconnected) {
if (logger.isDebugEnabled()) {
logger.debug("Received UserDisconnected message for meeting=[" + msg.meetingId + "]")
}
meetings.get(msg.meetingId) foreach { meeting =>
meeting forward msg
}
}
private def handleIsStreamRecorded(msg: IsStreamRecorded) {
if (logger.isDebugEnabled()) {
logger.debug("Received IsStreamRecorded message for meeting=[" + msg.meetingId + "]")
}
meetings.get(msg.meetingId) foreach { meeting =>
meeting forward msg
}
}
private def handleIsScreenSharing(msg: IsScreenSharing) {
if (logger.isDebugEnabled()) {
logger.debug("Received IsScreenSharing message for meeting=[" + msg.meetingId + "]")
}
meetings.get(msg.meetingId) foreach { meeting =>
meeting forward msg
}
}
private def handleMeetingHasEnded(msg: MeetingHasEnded) {
logger.info("Removing meeting [" + msg.meetingId + "]")
meetings -= msg.meetingId
}
private def handleScreenShareInfoRequest(msg: ScreenShareInfoRequest) {
if (logger.isDebugEnabled()) {
logger.debug("Received ScreenShareInfoRequest message for meetingId=[" + msg.meetingId + "]")
}
meetings.get(msg.meetingId) foreach { meeting =>
meeting forward msg
}
}
private def handleUpdateShareStatus(msg: UpdateShareStatus) {
if (logger.isDebugEnabled()) {
logger.debug("Received update share message for meeting=[" + msg.streamId + "]")
}
meetings.get(msg.meetingId) foreach { meeting =>
meeting forward msg
}
}
private def handleSharingStoppedMessage(msg: SharingStoppedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received sharing stopped message for meeting=[" + msg.streamId + "]")
}
meetings.get(msg.meetingId) foreach { meeting =>
meeting forward msg
}
}
private def handleSharingStartedMessage(msg: SharingStartedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received sharing started message for meeting=[" + msg.streamId + "]")
}
meetings.get(msg.meetingId) foreach { meeting =>
meeting forward msg
}
}
private def handleIsSharingStopped(msg: IsSharingStopped) {
meetings.get(msg.meetingId) foreach { s => s forward msg }
}
private def handleStreamStoppedMessage(msg: StreamStoppedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received stream stopped message for meeting=[" + msg.streamId + "]")
}
meetings.get(msg.meetingId) foreach { meeting =>
meeting forward msg
}
}
private def handleStreamStartedMessage(msg: StreamStartedMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received stream started message for meeting=[" + msg.meetingId + "]")
}
meetings.get(msg.meetingId) foreach { meeting =>
meeting forward msg
}
}
private def handleStopShareRequestMessage(msg: StopShareRequestMessage) {
if (logger.isDebugEnabled()) {
logger.debug("Received stop share request message for meeting=[" + msg.meetingId + "]")
}
meetings.get(msg.meetingId) foreach { meeting =>
meeting forward msg
}
}
private def handleStartShareRequestMessage(msg: StartShareRequestMessage): Unit = {
if (logger.isDebugEnabled()) {
logger.debug("Received start share request message for meeting=[" + msg.meetingId + "]")
}
meetings.get(msg.meetingId) match {
case None => {
if (logger.isDebugEnabled()) {
logger.debug("Creating meeting=[" + msg.meetingId + "]")
}
val meeting: MeetingActor = new MeetingActor(this, bus, msg.meetingId)
meetings += msg.meetingId -> meeting
meeting.start
meeting forward msg
}
case Some(meeting) => {
if (logger.isDebugEnabled()) {
logger.debug("Meeting already exists. meeting=[" + msg.meetingId + "]")
}
meeting forward msg
}
}
}
private def removeSession(meetingId: String): Unit = {
logger.debug("SessionManager: Removing session " + meetingId);
meetings.get(meetingId) foreach { s =>
s ! StopSession
val old:Int = meetings.size
meetings -= meetingId;
logger.debug("RemoveSession: Session length [%d,%d]", old, meetings.size)
}
}
}

View File

@ -0,0 +1,36 @@
package org.bigbluebutton.app.screenshare.server.sessions.messages
case class StartShareRequestMessage(meetingId: String, userId: String, record: Boolean)
case class StartShareRequestReplyMessage(token: String)
case class StopShareRequestMessage(meetingId: String, streamId: String)
case class StreamStartedMessage(meetingId: String, streamId: String, url: String)
case class StreamStoppedMessage(meetingId: String, streamId: String)
case class SharingStartedMessage(meetingId: String, streamId: String, width: Int, height: Int)
case class SharingStoppedMessage(meetingId: String, streamId: String)
case class IsStreamRecorded(meetingId: String, streamId: String)
case class IsStreamRecordedReply(record: Boolean)
case class IsSharingStopped(meetingId: String, streamId: String)
case class IsSharingStoppedReply(stopped: Boolean)
case class UpdateShareStatus(meetingId: String, streamId: String, sequence: Int)
case class IsScreenSharing(meetingId: String)
case class IsScreenSharingReply(sharing: Boolean, streamId: String,
width: Int, height: Int, url: String)
case class ScreenShareInfoRequest(meetingId: String, token: String)
case class ScreenShareInfoRequestReply(meetingId: String, streamId: String)
case class UserDisconnected(meetingId: String, userId: String)

View File

@ -0,0 +1,5 @@
package org.bigbluebutton.app.screenshare.server.sessions.messages
class ShareScreenResponse {
}

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