bug 346605 commit the demo of jetty's continuation and webscoket to 'chat with the equinox console

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@3221 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Hugues Malphettes 2011-05-20 08:33:57 +00:00
parent 538038a7f2
commit 850170ba5f
20 changed files with 1391 additions and 25 deletions

View File

@ -25,12 +25,12 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>osgi</artifactId>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>services</artifactId>
<artifactId>org.eclipse.osgi.services</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>

View File

@ -21,8 +21,8 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>osgi</artifactId>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
@ -30,7 +30,7 @@
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>services</artifactId>
<artifactId>org.eclipse.osgi.services</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>

View File

@ -19,8 +19,8 @@
<artifactId>jetty-util</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>osgi</artifactId>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
</dependencies>

View File

@ -31,12 +31,12 @@
<artifactId>jetty-nested</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>osgi</artifactId>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>services</artifactId>
<artifactId>org.eclipse.osgi.services</artifactId>
</dependency>
</dependencies>

View File

@ -0,0 +1,20 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Console
Bundle-SymbolicName: org.eclipse.jetty.osgi.equinoxtools
Bundle-Description: Example application: equinox console accesssible on the web
Bundle-Version: 7.4.2.qualifier
Bundle-Activator: org.eclipse.jetty.osgi.equinoxtools.WebEquinoxToolsActivator
Import-Package: javax.servlet;version="2.5.0",
javax.servlet.http;version="2.5.0",
org.eclipse.jetty.continuation;version="7.4.0",
org.eclipse.jetty.io;version="7.4.0",
org.eclipse.jetty.util;version="7.4.0",
org.eclipse.jetty.util.log;version="7.4.0",
org.eclipse.jetty.websocket;version="7.4.0",
org.eclipse.osgi.framework.console;version="1.1.0",
org.osgi.framework;version="1.3.0",
org.osgi.service.http;version="1.2.0",
org.osgi.util.tracker;version="1.3.0"
Bundle-RequiredExecutionEnvironment: JavaSE-1.6

View File

@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.

View File

@ -0,0 +1,85 @@
<html><head>
<title>Async Equinox Console</title>
<script type='text/javascript'>
function $() { return document.getElementById(arguments[0]); }
function $F() { return document.getElementById(arguments[0]).value; }
function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; }
function xhr(method,uri,body,handler) {
var req=(window.XMLHttpRequest)?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');
req.onreadystatechange=function() { if (req.readyState==4 && handler) { eval('var o='+req.responseText);handler(o);} }
req.open(method,uri,true);
req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
req.send(body);
};
function send(action,user,message,handler){
if (message) message=message.replace('%','%25').replace('&','%26').replace('=','%3D');
if (user) user=user.replace('%','%25').replace('&','%26').replace('=','%3D');
xhr('POST','chat','action='+action+'&user='+user+'&message='+message,handler);
};
var room = {
join: function(name) {
this._username=name;
$('join').className='hidden';
$('joined').className='';
$('phrase').focus();
send('join', room._username,null);
send('chat', room._username,'has joined!');
send('poll', room._username,null, room._poll);
},
chat: function(text) {
if (text != null && text.length>0 )
send('chat',room._username,text);
},
_poll: function(m) {
//console.debug(m);
if (m.chat){
var chat=document.getElementById('chat');
var spanFrom = document.createElement('span');
spanFrom.className='from';
spanFrom.innerHTML=m.from+'&nbsp;';
var spanText = document.createElement('span');
spanText.className='text';
spanText.innerHTML=m.chat;
var lineBreak = document.createElement('br');
chat.appendChild(spanFrom);
chat.appendChild(spanText);
chat.appendChild(lineBreak);
chat.scrollTop = chat.scrollHeight - chat.clientHeight;
}
if (m.action=='poll')
send('poll', room._username,null, room._poll);
},
_end:''
};
</script>
<style type='text/css'>
div { border: 0px solid black; }
div#chat { clear: both; width: 40em; height: 20ex; overflow: auto; background-color: #f0f0f0; padding: 4px; border: 1px solid black; }
div#input { clear: both; width: 40em; padding: 4px; background-color: #e0e0e0; border: 1px solid black; border-top: 0px }
input#phrase { width:30em; background-color: #e0f0f0; }
input#username { width:14em; background-color: #e0f0f0; }
div.hidden { display: none; }
span.from { font-weight: bold; }
span.alert { font-style: italic; }
</style>
</head><body>
<div id='chat'></div>
<div id='input'>
<div id='join' >
Username:&nbsp;<input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join' value='Join'/>
</div>
<div id='joined' class='hidden'>
OSGi:&nbsp;<input id='phrase' type='text'/>
<input id='sendB' class='button' type='submit' name='join' value='Send'/>
</div>
</div>
<script type='text/javascript'>
$('username').setAttribute('autocomplete','OFF');
$('username').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.join($F('username')); return false; } return true; } ;
$('joinB').onclick = function(event) { room.join($F('username')); return false; };
$('phrase').setAttribute('autocomplete','OFF');
$('phrase').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.chat($F('phrase')); $('phrase').value=''; return false; } return true; };
$('sendB').onclick = function(event) { room.chat($F('phrase')); $('phrase').value=''; return false; };
</script>
</body></html>

View File

@ -0,0 +1,109 @@
<html><head>
<title>Equinox Console (WebSocket)</title>
<script type='text/javascript'>
if (!window.WebSocket)
alert("WebSocket not supported by this browser");
function $() { return document.getElementById(arguments[0]); }
function $F() { return document.getElementById(arguments[0]).value; }
function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; }
var room = {
join: function(name) {
this._username=name;
var location = document.location.toString().replace('http://','ws://').replace('https://','wss://').replace('/index.html','');
this._ws=new WebSocket(location);
this._ws.onopen=this._onopen;
this._ws.onmessage=this._onmessage;
this._ws.onclose=this._onclose;
},
_onopen: function(){
$('join').className='hidden';
$('joined').className='';
$('phrase').focus();
room._send(room._username,'has joined!');
},
_send: function(user,message){
user=user.replace(':','_');
if (this._ws)
this._ws.send(user+':'+message);
},
chat: function(text) {
if (text != null && text.length>0 )
room._send(room._username,text);
},
_onmessage: function(m) {
if (m.data){
var c=m.data.indexOf(':');
var from=m.data.substring(0,c).replace('<','&lt;').replace('>','&gt;');
var text=m.data.substring(c+1).replace('<','&lt;').replace('>','&gt;');
var chat=$('chat');
var spanFrom = document.createElement('span');
spanFrom.className='from';
spanFrom.innerHTML=from+':&nbsp;';
var spanText = document.createElement('span');
spanText.className='text';
spanText.innerHTML=text;
var lineBreak = document.createElement('br');
chat.appendChild(spanFrom);
chat.appendChild(spanText);
chat.appendChild(lineBreak);
chat.scrollTop = chat.scrollHeight - chat.clientHeight;
}
},
_onclose: function(m) {
this._ws=null;
$('join').className='';
$('joined').className='hidden';
$('username').focus();
$('chat').innerHTML='';
}
};
</script>
<style type='text/css'>
div { border: 0px solid black; }
div#chat { clear: both; width: 40em; height: 20ex; overflow: auto; background-color: #f0f0f0; padding: 4px; border: 1px solid black; }
div#input { clear: both; width: 40em; padding: 4px; background-color: #e0e0e0; border: 1px solid black; border-top: 0px }
input#phrase { width:30em; background-color: #e0f0f0; }
input#username { width:14em; background-color: #e0f0f0; }
div.hidden { display: none; }
span.from { font-weight: bold; }
span.alert { font-style: italic; }
</style>
</head><body>
<div id='chat'></div>
<div id='input'>
<div id='join' >
Username:&nbsp;<input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join' value='Join'/>
</div>
<div id='joined' class='hidden'>
Chat:&nbsp;<input id='phrase' type='text'/>
<input id='sendB' class='button' type='submit' name='join' value='Send'/>
</div>
</div>
<script type='text/javascript'>
$('username').setAttribute('autocomplete','OFF');
$('username').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.join($F('username')); return false; } return true; } ;
$('joinB').onclick = function(event) { room.join($F('username')); return false; };
$('phrase').setAttribute('autocomplete','OFF');
$('phrase').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.chat($F('phrase')); $('phrase').value=''; return false; } return true; };
$('sendB').onclick = function(event) { room.chat($F('phrase')); $('phrase').value=''; return false; };
</script>
<p>
This is a demonstration of the Jetty websocket server.
</p>
</body></html>

View File

@ -0,0 +1,96 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
<version>7.4.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-osgi-equinoxtools</artifactId>
<name>Jetty :: OSGi :: Example Equinox Tools</name>
<description>Jetty OSGi Example Equinox Tools</description>
<properties>
<bundle-symbolic-name>${project.groupId}.equinoxtools</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-continuation</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>process-resources</phase>
<configuration>
<tasks>
<replace file="target/classes/META-INF/MANIFEST.MF" token="Bundle-Version: 7.4.2.qualifier" value="Bundle-Version: ${parsedVersion.osgiVersion}" />
<copy todir="target/classes/equinoxconsole">
<fileset dir="equinoxconsole" />
</copy>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>artifact-jar</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
<execution>
<id>test-jar</id>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<onlyAnalyze>org.eclipse.jetty.osgi.equinoxtools.*</onlyAnalyze>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,127 @@
// ========================================================================
// Copyright (c) 2006-2011 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.osgi.equinoxtools;
import javax.servlet.ServletException;
import org.eclipse.jetty.osgi.equinoxtools.console.EquinoxChattingSupport;
import org.eclipse.jetty.osgi.equinoxtools.console.EquinoxConsoleContinuationServlet;
import org.eclipse.jetty.osgi.equinoxtools.console.EquinoxConsoleSyncServlet;
import org.eclipse.jetty.osgi.equinoxtools.console.EquinoxConsoleWebSocketServlet;
import org.eclipse.jetty.osgi.equinoxtools.console.WebConsoleSession;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.osgi.framework.console.ConsoleSession;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* When started will register on the HttpService 3 servlets for 3 different styles of equinox consoles.
*/
public class WebEquinoxToolsActivator implements BundleActivator
{
private static BundleContext context;
public static BundleContext getContext()
{
return context;
}
private HttpService _httpService;
private ServiceTracker _tracker;
private EquinoxChattingSupport _equinoxChattingSupport;
/*
* (non-Javadoc)
*
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext bundleContext) throws Exception
{
WebEquinoxToolsActivator.context = bundleContext;
ServiceTrackerCustomizer httpServiceTrackerCustomizer = new ServiceTrackerCustomizer()
{
@Override
public void removedService(ServiceReference reference, Object service)
{
_httpService = null;
}
@Override
public void modifiedService(ServiceReference reference, Object service)
{
_httpService = (HttpService)context.getService(reference);
}
@Override
public Object addingService(ServiceReference reference)
{
_httpService = (HttpService)context.getService(reference);
try
{
//TODO; some effort to use the same console session on the 2 async console servlets?
//websocket:
// WebConsoleSession wsSession = new WebConsoleSession();
// WebEquinoxConsoleActivator.context.registerService(ConsoleSession.class.getName(), wsSession, null);
// EquinoxChattingSupport wsEquinoxChattingSupport = new EquinoxChattingSupport(wsSession);
_httpService.registerResources("/equinoxconsole/ws/index.html","/equinoxconsole/ws/index.html",null);
_httpService.registerServlet("/equinoxconsole/ws",new EquinoxConsoleWebSocketServlet(/*wsSession, wsEquinoxChattingSupport*/),null,null);
//continuations:
// WebConsoleSession contSession = new WebConsoleSession();
// WebEquinoxConsoleActivator.context.registerService(ConsoleSession.class.getName(), contSession, null);
// EquinoxChattingSupport contEquinoxChattingSupport = new EquinoxChattingSupport(contSession);
_httpService.registerResources("/equinoxconsole/index.html","/equinoxconsole/index.html",null);
_httpService.registerServlet("/equinoxconsole",new EquinoxConsoleContinuationServlet(/*contSession, contEquinoxChattingSupport*/),null,null);
//legacy synchroneous; keep it in a separate console session.
WebConsoleSession syncSession = new WebConsoleSession();
WebEquinoxToolsActivator.context.registerService(ConsoleSession.class.getName(), syncSession, null);
_httpService.registerServlet("/equinoxconsole/sync",new EquinoxConsoleSyncServlet(syncSession),null,null);
}
catch (ServletException e)
{
Log.warn(e);
}
catch (NamespaceException e)
{
Log.warn(e);
}
return _httpService;
}
};
_tracker = new ServiceTracker(context,HttpService.class.getName(),httpServiceTrackerCustomizer);
_tracker.open();
}
/*
* (non-Javadoc)
*
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext bundleContext) throws Exception
{
_tracker.close();
WebEquinoxToolsActivator.context = null;
}
}

View File

@ -0,0 +1,150 @@
// ========================================================================
// Copyright (c) 2006-2011 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.osgi.equinoxtools.console;
import java.util.LinkedList;
import java.util.Queue;
import org.eclipse.jetty.osgi.equinoxtools.console.WebConsoleWriterOutputStream.OnFlushListener;
/**
* Processing of the messages to be received and sent to the chat servlets.
* Made to be extended for filtering of the messages and commands.
*/
public class EquinoxChattingSupport
{
private WebConsoleSession _consoleSession;
public EquinoxChattingSupport(WebConsoleSession consoleSession)
{
_consoleSession = consoleSession;
}
/**
* Split the output into multiple lines.
* Format them for the json messages sent to the chat.
* Empties the console output from what is already displayed in the chat.
* @return The lines to add to the message queue of each client.
*/
protected Queue<String> processConsoleOutput(boolean escape, OnFlushListener onflush)
{
Queue<String> result = new LinkedList<String>();
String toDisplay = _consoleSession.getOutputAsWriter().getBuffer().toString();
//the last listener to be called is in charge of clearing the console.
boolean clearConsole = _consoleSession.getOnFlushListeners().indexOf(onflush) == _consoleSession.getOnFlushListeners().size();
if (clearConsole)
{
_consoleSession.clearOutput();
}
boolean lastLineIsComplete = toDisplay.endsWith("\n") || toDisplay.endsWith("\r");
String[] lines = toDisplay.split("\n");
String lastLine = lastLineIsComplete ? null : lines[lines.length-1];
if (clearConsole)
{
_consoleSession.getOutputAsWriter().append(lastLine);
}
for (int lnNb = 0; lnNb < (lastLineIsComplete ? lines.length : lines.length-1); lnNb++)
{
String line = lines[lnNb];
while (line.trim().startsWith("null"))
{//hum..
line = line.trim().substring("null".length()).trim();
}
if (line.startsWith("osgi>"))
{
result.add("osgi>");
result.add(escape ? jsonEscapeString(line.substring("osgi>".length())) : line.substring("osgi>".length()));
}
else
{
result.add("&#10;");
result.add(escape ? jsonEscapeString(line) : line);
}
}
return result;
}
/**
* http://www.ietf.org/rfc/rfc4627.txt
* @param str
* @return The same string escaped according to the JSON RFC.
*/
public static String jsonEscapeString(String str)
{
StringBuilder sb = new StringBuilder();
char[] asChars = str.toCharArray();
for (char ch : asChars)
{
switch (ch)
{
//the reserved characters
case '"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
case '\b':
sb.append("\\b");
break;
case '\f':
sb.append("\\f");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
case '/':
sb.append("\\/");
break;
default:
//The non reserved characters
if (ch >= '\u0000' && ch <= '\u001F')
{
//escape as a unicode number when out of range.
String ss = Integer.toHexString(ch);
sb.append("\\u");
for (int i = 0; i < 4 - ss.length(); i++)
{
//padding
sb.append('0');
}
sb.append(ss.toUpperCase());
}
else
{
sb.append(ch);
}
}
}
return sb.toString();
}
public void broadcast(OnFlushListener source)
{
for (OnFlushListener onflush : _consoleSession.getOnFlushListeners())
{
if (onflush != source)
{
onflush.onFlush();
}
}
}
}

View File

@ -0,0 +1,248 @@
// ========================================================================
// Copyright (c) 2006-2011 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.osgi.equinoxtools.console;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationSupport;
import org.eclipse.jetty.osgi.equinoxtools.WebEquinoxToolsActivator;
import org.eclipse.jetty.osgi.equinoxtools.console.WebConsoleWriterOutputStream.OnFlushListener;
import org.eclipse.osgi.framework.console.ConsoleSession;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
/**
* Async servlet with jetty continuations to interact with the equinox console.
* Ported from jetty's example 'ChatServlet'
*/
public class EquinoxConsoleContinuationServlet extends HttpServlet implements OnFlushListener
{
private static final long serialVersionUID = 1L;
private Map<String,ConsoleUser> _consoleUsers = new HashMap<String, ConsoleUser>();
private WebConsoleSession _consoleSession;
private EquinoxChattingSupport _support;
/**
* @param consoleSession
*/
public EquinoxConsoleContinuationServlet()
{
}
/**
* @param consoleSession
*/
public EquinoxConsoleContinuationServlet(WebConsoleSession consoleSession, EquinoxChattingSupport support)
{
_consoleSession = consoleSession;
_support = support;
}
@Override
public void init() throws ServletException
{
if (_consoleSession == null)
{
_consoleSession = new WebConsoleSession();
WebEquinoxToolsActivator.getContext().registerService(ConsoleSession.class.getName(), _consoleSession, null);
}
if (_support == null)
{
_support = new EquinoxChattingSupport(_consoleSession);
}
_consoleSession.addOnFlushListener(this);
}
@Override
public void destroy()
{
_consoleSession.removeOnFlushListener(this);
}
// Serve the HTML with embedded CSS and Javascript.
// This should be static content and should use real JS libraries.
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (request.getParameter("action")!=null)
doPost(request,response);
else
response.sendRedirect("index.html");
}
// Handle Ajax calls from browser
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// Ajax calls are form encoded
String action = request.getParameter("action");
String message = request.getParameter("message");
String username = request.getParameter("user");
if (action.equals("join"))
join(request,response,username);
else if (action.equals("poll"))
poll(request,response,username);
else if (action.equals("chat"))
chat(request,response,username,message);
}
private synchronized void join(HttpServletRequest request,HttpServletResponse response,String username)
throws IOException
{
ConsoleUser member = new ConsoleUser(username);
_consoleUsers.put(username,member);
response.setContentType("text/json;charset=utf-8");
PrintWriter out=response.getWriter();
out.print("{action:\"join\"}");
}
private synchronized void poll(HttpServletRequest request,HttpServletResponse response,String username)
throws IOException
{
ConsoleUser member = _consoleUsers.get(username);
if (member==null)
{
response.sendError(503);
return;
}
synchronized(member)
{
if (member.getMessageQueue().size()>0)
{
// Send one chat message
response.setContentType("text/json;charset=utf-8");
StringBuilder buf=new StringBuilder();
buf.append("{\"action\":\"poll\",");
buf.append("\"from\":\"");
buf.append(member.getMessageQueue().poll());
buf.append("\",");
String message = member.getMessageQueue().poll();
int quote=message.indexOf('"');
while (quote>=0)
{
message=message.substring(0,quote)+'\\'+message.substring(quote);
quote=message.indexOf('"',quote+2);
}
buf.append("\"chat\":\"");
buf.append(message);
buf.append("\"}");
byte[] bytes = buf.toString().getBytes("utf-8");
response.setContentLength(bytes.length);
response.getOutputStream().write(bytes);
}
else
{
Continuation continuation = ContinuationSupport.getContinuation(request);
if (continuation.isInitial())
{
// No chat in queue, so suspend and wait for timeout or chat
continuation.setTimeout(20000);
continuation.suspend();
member.setContinuation(continuation);
}
else
{
// Timeout so send empty response
response.setContentType("text/json;charset=utf-8");
PrintWriter out=response.getWriter();
out.print("{action:\"poll\"}");
}
}
}
}
private synchronized void chat(HttpServletRequest request,HttpServletResponse response,String username,String message)
throws IOException
{
if (!message.endsWith("has joined!"))
{
_consoleSession.processCommand(message, false);
}
// Post chat to all members
onFlush();
response.setContentType("text/json;charset=utf-8");
PrintWriter out=response.getWriter();
out.print("{action:\"chat\"}");
}
/**
* Called right after the flush method on the output stream has been executed.
*/
public void onFlush()
{
Queue<String> pendingConsoleOutputMessages = _support.processConsoleOutput(true, this);
for (ConsoleUser m:_consoleUsers.values())
{
synchronized (m)
{
// m.getMessageQueue().add("osgi>"); // from
// m.getMessageQueue().add("something was printed"); // chat
m.getMessageQueue().addAll(pendingConsoleOutputMessages);
// wakeup member if polling
if (m.getContinuation()!=null)
{
m.getContinuation().resume();
m.setContinuation(null);
}
}
}
}
class ConsoleUser
{
private String _name;
private Continuation _continuation;
private Queue<String> _queue = new LinkedList<String>();
public ConsoleUser(String name)
{
_name = name;
}
public String getName()
{
return _name;
}
public void setContinuation(Continuation continuation)
{
_continuation = continuation;
}
public Continuation getContinuation()
{
return _continuation;
}
public Queue<String> getMessageQueue()
{
return _queue;
}
}
}

View File

@ -0,0 +1,76 @@
// ========================================================================
// Copyright (c) 2006-2011 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.osgi.equinoxtools.console;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Take the example ChatServlet and use it to make an Equinox Console on the web.
*/
public class EquinoxConsoleSyncServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
private WebConsoleSession _consoleSession;
public EquinoxConsoleSyncServlet(WebConsoleSession consoleSession)
{
_consoleSession = consoleSession;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String cmd = request.getParameter("cmd");
String Action = request.getParameter("Action");
if (Action != null && Action.toLowerCase().indexOf("clear") != -1)
{
_consoleSession.clearOutput();
}
if (cmd != null)
{
_consoleSession.processCommand(cmd, true);
}
response.setContentType("text/html;charset=utf-8");
PrintWriter p = response.getWriter();
p.println("<html><head><title>Equinox Console (Synchroneous)</title></head><body>");
p.println("<textarea rows=\"30\" cols=\"110\">");
p.println(_consoleSession.getOutputAsWriter().toString());
p.println("</textarea>");
p.println("<form method=\"GET\" action=\""+response.encodeURL(getURI(request))+"\">");
p.println("osgi>&nbsp;<input type=\"text\" name=\"cmd\" value=\"\"/><br/>\n");
p.println("<input type=\"submit\" name=\"Action\" value=\"Submit or Refresh\"><br/>");
p.println("<input type=\"submit\" name=\"Action\" value=\"Clear and Submit\"><br/>");
p.println("</form>");
p.println("<br/>");
}
private String getURI(HttpServletRequest request)
{
String uri= (String)request.getAttribute("javax.servlet.forward.request_uri");
if (uri == null)
uri= request.getRequestURI();
return uri;
}
}

View File

@ -0,0 +1,170 @@
// ========================================================================
// Copyright (c) 2006-2011 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.osgi.equinoxtools.console;
import java.io.IOException;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.osgi.equinoxtools.WebEquinoxToolsActivator;
import org.eclipse.jetty.osgi.equinoxtools.console.WebConsoleWriterOutputStream.OnFlushListener;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;
import org.eclipse.osgi.framework.console.ConsoleSession;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
/**
* Websocket version of the Chat with equinox.
* Ported from jetty's example 'WebSocketChatServlet'
*/
public class EquinoxConsoleWebSocketServlet extends WebSocketServlet implements OnFlushListener
{
private final Set<ChatWebSocket> _members = new CopyOnWriteArraySet<ChatWebSocket>();
private static final long serialVersionUID = 1L;
private WebConsoleSession _consoleSession;
private EquinoxChattingSupport _support;
public EquinoxConsoleWebSocketServlet()
{
}
public EquinoxConsoleWebSocketServlet(WebConsoleSession consoleSession, EquinoxChattingSupport support)
{
_consoleSession = consoleSession;
_support = support;
}
@Override
public void init() throws ServletException
{
if (_consoleSession == null)
{
_consoleSession = new WebConsoleSession();
WebEquinoxToolsActivator.getContext().registerService(ConsoleSession.class.getName(), _consoleSession, null);
}
if (_support == null)
{
_support = new EquinoxChattingSupport(_consoleSession);
}
super.init();
_consoleSession.addOnFlushListener(this);
}
@Override
public void destroy()
{
_consoleSession.removeOnFlushListener(this);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws javax.servlet.ServletException ,IOException
{
//getServletContext().getNamedDispatcher("default").forward(request,response);
response.sendRedirect(request.getContextPath() + request.getServletPath()
+ (request.getPathInfo() != null ? request.getPathInfo() : "") + "/index.html");
};
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
{
return new ChatWebSocket();
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
class ChatWebSocket implements WebSocket.OnTextMessage
{
Connection _connection;
String _username;
public void onOpen(Connection connection)
{
// Log.info(this+" onConnect");
_connection=connection;
_members.add(this);
}
public void onMessage(byte frame, byte[] data,int offset, int length)
{
// Log.info(this+" onMessage: "+TypeUtil.toHexString(data,offset,length));
}
public void onMessage(String data)
{
Log.info("onMessage: {}",data);
if (data.indexOf("disconnect")>=0)
_connection.disconnect();
else
{
if (!data.endsWith(":has joined!"))
{
if (_username != null)
{
if (data.startsWith(_username + ":"))
{
data = data.substring(_username.length()+1);
}
else
{
//we should not be here?
}
}
_consoleSession.processCommand(data, false);
}
else
{
_username = data.substring(0, data.length()-":has joined!".length());
}
// Log.info(this+" onMessage: "+data);
onFlush();
}
}
public void onClose(int code, String message)
{
// Log.info(this+" onDisconnect");
_members.remove(this);
}
}
/**
* Called right after the flush method on the output stream has been executed.
*/
public void onFlush()
{
Queue<String> pendingConsoleOutputMessages = _support.processConsoleOutput(false, this);
for (ChatWebSocket member : _members)
{
try
{
for (String line : pendingConsoleOutputMessages)
{
member._connection.sendMessage(line);
}
}
catch(IOException e)
{
Log.warn(e);
}
}
}
}

View File

@ -0,0 +1,183 @@
// ========================================================================
// Copyright (c) 2006-2011 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.osgi.equinoxtools.console;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.io.StringWriter;
import java.util.List;
import org.eclipse.jetty.osgi.equinoxtools.console.WebConsoleWriterOutputStream.OnFlushListener;
import org.eclipse.osgi.framework.console.ConsoleSession;
/**
* A simple console session for equinox.
*
* @author hmalphettes
*/
public class WebConsoleSession extends ConsoleSession
{
private OutputStream _out;
private StringWriter _outWriter;
private PrintStream _source;
private InputStream _in;
public WebConsoleSession()
{
_outWriter = new StringWriter();
_out = new WebConsoleWriterOutputStream(_outWriter,"UTF-8");
try
{
PipedOutputStream source = new PipedOutputStream();
PipedInputStream sink = new PipedInputStream(source);
_in = sink;
_source = new PrintStream(source);
}
catch (IOException e)
{
//this never happens?
e.printStackTrace();
}
}
@Override
protected void doClose()
{
if (_out != null) try { _out.close(); } catch (IOException e) {}
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
}
@Override
public InputStream getInput()
{
return _in;
}
@Override
public OutputStream getOutput()
{
return _out;
}
/**
* For the output we are using a string writer in fact.
* @return
*/
public StringWriter getOutputAsWriter()
{
return _outWriter;
}
/**
* @return The print stream where commands can be written.
*/
public PrintStream getSource()
{
return _source;
}
/**
* Issue a command on the console.
* @param cmd
*/
public void issueCommand(String cmd)
{
if (cmd != null)
{
getSource().println(cmd);
}
}
/**
* @param flushListener Object that is called back when the outputstream is flushed.
*/
public void addOnFlushListener(OnFlushListener flushListener)
{
((WebConsoleWriterOutputStream)_out).addOnFlushListener(flushListener);
}
/**
* @param flushListener Object that is called back when the outputstream is flushed.
*/
public boolean removeOnFlushListener(OnFlushListener flushListener)
{
return ((WebConsoleWriterOutputStream)_out).removeOnFlushListener(flushListener);
}
/**
* Process command comming from a web UI
* @param cmd
*/
public void processCommand(String cmd, boolean wait)
{
cmd = cmd.trim();
while (cmd.startsWith("osgi>"))
{
cmd = cmd.substring("osgi>".length()).trim();
}
if (cmd.equals("clear"))
{
clearOutput();
}
else
{
getOutputAsWriter().append(cmd + "\n");
int originalOutputLength = getOutputAsWriter().getBuffer().length();
issueCommand(cmd);
if (wait)
{
//it does not get uglier than this:
//give a little time to equinox to interpret the command so we see the response
//we could do a lot better, but we might as well use the async servlets anyways.
int waitLoopNumber = 0;
int lastWaitOutputLength = -1;
while (waitLoopNumber < 10)
{
waitLoopNumber++;
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
break;
}
int newOutputLength = getOutputAsWriter().getBuffer().length();
if (newOutputLength > originalOutputLength && newOutputLength == lastWaitOutputLength)
{
break;
}
lastWaitOutputLength = newOutputLength;
}
}
}
}
public void clearOutput()
{
StringBuffer buf = getOutputAsWriter().getBuffer();
if (buf.length() > 0) buf.delete(0,buf.length()-1);
}
public List<OnFlushListener> getOnFlushListeners()
{
return ((WebConsoleWriterOutputStream)_out).getOnFlushListeners();
}
}

View File

@ -0,0 +1,87 @@
// ========================================================================
// Copyright (c) 2006-2011 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.osgi.equinoxtools.console;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
/**
* Can be set with a listener that is called back right after the flush method is called.
*/
public class WebConsoleWriterOutputStream extends org.eclipse.jetty.io.WriterOutputStream
{
/**
* Interface called back after the outputstream is flushed.
*/
public interface OnFlushListener
{
/**
* Called right after the flush method on the output stream has been executed.
*/
public void onFlush();
}
public interface MessageBroadcaster
{
public void broadcast();
}
private List<OnFlushListener> _callBacks;
public WebConsoleWriterOutputStream(Writer writer, String encoding)
{
super(writer, encoding);
}
@Override
public synchronized void flush() throws IOException
{
super.flush();
if (_callBacks != null)
{
for (OnFlushListener listener : _callBacks)
{
listener.onFlush();
}
}
}
public synchronized void addOnFlushListener(OnFlushListener callback)
{
if (_callBacks == null)
{
_callBacks = new ArrayList<WebConsoleWriterOutputStream.OnFlushListener>();
}
if (!_callBacks.contains(callback))
{
_callBacks.add(callback);
}
}
public synchronized boolean removeOnFlushListener(OnFlushListener callback)
{
if (_callBacks != null)
{
return _callBacks.remove(callback);
}
return false;
}
public synchronized List<OnFlushListener> getOnFlushListeners()
{
return _callBacks;
}
}

View File

@ -27,8 +27,8 @@
<artifactId>servlet</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>osgi</artifactId>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>

View File

@ -11,8 +11,8 @@
<name>Jetty :: OSGi</name>
<packaging>pom</packaging>
<properties>
<osgi-version>3.5.0.v20090520</osgi-version>
<osgi-services-version>3.1.200-v20070605</osgi-services-version>
<osgi-version>3.6.0.v20100517</osgi-version>
<osgi-services-version>3.2.100.v20100503</osgi-services-version>
<equinox-http-servlet-version>1.0.0-v20070606</equinox-http-servlet-version>
<!--equinox-servletbridge-version>1.0.0-v20070523</equinox-servletbridge-version-->
<jsp-2.1-glassfish-version>2.1.v20100127</jsp-2.1-glassfish-version>
@ -25,6 +25,7 @@
<module>jetty-osgi-boot-logback</module>
<module>jetty-osgi-boot-warurl</module>
<module>jetty-osgi-httpservice</module>
<module>jetty-osgi-equinoxtools</module>
<module>test-jetty-osgi</module>
</modules>
<build>
@ -72,6 +73,16 @@
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-continuation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
@ -108,14 +119,14 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>osgi</artifactId>
<version>${osgi-version}</version>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
<version>${osgi-services-version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>services</artifactId>
<version>${osgi-services-version}</version>
<artifactId>org.eclipse.osgi</artifactId>
<version>${osgi-version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.equinox.http</groupId>

View File

@ -110,14 +110,14 @@
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>osgi</artifactId>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
<version>${osgi-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>services</artifactId>
<artifactId>org.eclipse.osgi.services</artifactId>
<version>${osgi-services-version}</version>
<scope>runtime</scope>
</dependency>

View File

@ -72,8 +72,8 @@ public class TestJettyOSGiBootCore
// CoreOptions.equinox(),
mavenBundle().groupId( "org.mortbay.jetty" ).artifactId( "servlet-api" ).versionAsInProject().noStart(),
mavenBundle().groupId( "org.eclipse" ).artifactId( "osgi" ).versionAsInProject().noStart(),
mavenBundle().groupId( "org.eclipse.osgi" ).artifactId( "services" ).versionAsInProject().noStart(),
mavenBundle().groupId( "org.eclipse.osgi" ).artifactId( "org.eclipse.osgi" ).versionAsInProject().noStart(),
mavenBundle().groupId( "org.eclipse.osgi" ).artifactId( "org.eclipse.osgi.services" ).versionAsInProject().noStart(),
mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-deploy" ).versionAsInProject().noStart(),
mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-server" ).versionAsInProject().noStart(),