// ======================================================================== // 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; /** * 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(request.getContextPath() + request.getServletPath() + (request.getPathInfo() != null ? request.getPathInfo() : "") + "/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; } } }