- new screenshare red5 app
This commit is contained in:
parent
dd37445b7d
commit
bd30fd8c6f
38
bbb-screenshare/.classpath
Executable file
38
bbb-screenshare/.classpath
Executable 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
8
bbb-screenshare/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.manager
|
||||
.scala_dependencies
|
||||
.classpath
|
||||
.gradle/
|
||||
.project
|
||||
app/build/
|
||||
lib/
|
||||
build
|
18
bbb-screenshare/.project
Executable file
18
bbb-screenshare/.project
Executable 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>
|
26
bbb-screenshare/app/.classpath.old
Executable file
26
bbb-screenshare/app/.classpath.old
Executable 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
160
bbb-screenshare/app/build.gradle
Executable 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
4
bbb-screenshare/app/deploy.sh
Executable 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
|
||||
|
50
bbb-screenshare/app/jws/screenshare.jnlp
Executable file
50
bbb-screenshare/app/jws/screenshare.jnlp
Executable 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>
|
642
bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiff.java
Executable file
642
bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiff.java
Executable 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();
|
||||
}
|
||||
}
|
52
bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiffConstants.java
Executable file
52
bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiffConstants.java
Executable 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";
|
||||
}
|
329
bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiffPatcher.java
Executable file
329
bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiffPatcher.java
Executable 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();
|
||||
}
|
||||
}
|
60
bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/Patcher.java
Executable file
60
bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/Patcher.java
Executable 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);
|
||||
}
|
||||
}
|
@ -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:
|
227
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/DownloadRequest.java
Executable file
227
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/DownloadRequest.java
Executable 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();
|
||||
}
|
||||
}
|
296
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/DownloadResponse.java
Executable file
296
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/DownloadResponse.java
Executable 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() + "]"; }
|
||||
}
|
||||
}
|
@ -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(); }
|
||||
}
|
388
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JarDiffHandler.java
Executable file
388
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JarDiffHandler.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
249
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpDownloadServlet.java
Executable file
249
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpDownloadServlet.java
Executable 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());
|
||||
}
|
||||
|
||||
|
||||
}
|
495
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpFileHandler.java
Executable file
495
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpFileHandler.java
Executable 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;
|
||||
}
|
||||
|
||||
}
|
266
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpResource.java
Executable file
266
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpResource.java
Executable 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();
|
||||
}
|
||||
}
|
200
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/Logger.java
Executable file
200
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/Logger.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
514
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/ResourceCatalog.java
Executable file
514
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/ResourceCatalog.java
Executable 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
82
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLAttribute.java
Executable file
82
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLAttribute.java
Executable 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 + "\"";
|
||||
}
|
||||
}
|
||||
}
|
150
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLNode.java
Executable file
150
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLNode.java
Executable 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();
|
||||
}
|
||||
}
|
189
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLParsing.java
Executable file
189
bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLParsing.java
Executable 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
260
bbb-screenshare/app/src/main/java/jnlp/sample/util/VersionID.java
Executable file
260
bbb-screenshare/app/src/main/java/jnlp/sample/util/VersionID.java
Executable 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();
|
||||
}
|
||||
}
|
105
bbb-screenshare/app/src/main/java/jnlp/sample/util/VersionString.java
Executable file
105
bbb-screenshare/app/src/main/java/jnlp/sample/util/VersionString.java
Executable 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package org.bigbluebutton.app.screenshare;
|
||||
|
||||
public class Error {
|
||||
|
||||
public final String reason;
|
||||
|
||||
public Error(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.bigbluebutton.app.screenshare.events;
|
||||
|
||||
public interface IEvent {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.bigbluebutton.app.screenshare.events;
|
||||
|
||||
public interface IEventListener {
|
||||
void handleMessage(IEvent msg);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.bigbluebutton.app.screenshare.events;
|
||||
|
||||
public interface IEventsMessageBus {
|
||||
void send(IEvent msg);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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";
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.bigbluebutton.app.screenshare.store;
|
||||
|
||||
public interface IDataStore {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.bigbluebutton.app.screenshare.store.redis;
|
||||
|
||||
public interface IScreenShareData {
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
25
bbb-screenshare/app/src/main/resources/logback-screenshare.xml
Executable file
25
bbb-screenshare/app/src/main/resources/logback-screenshare.xml
Executable 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>
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
@ -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
Loading…
Reference in New Issue
Block a user