/* * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.max.jdwp.server; import java.io.*; import java.net.*; import java.util.*; import java.util.logging.*; import com.sun.max.jdwp.constants.Error; import com.sun.max.jdwp.data.*; /** * A JDWPServer object manages a command handler registry and can be started to listen for incoming JDWP connections. * */ public class JDWPServer { private static final Logger LOGGER = Logger.getLogger(JDWPServer.class.getName()); private static final int TIMEOUT = 2000; private ServerSocket serverSocket; private boolean shutdown; /** * Shuts down the JDWP server. The server must be running. */ public void shutdown() { assert serverSocket != null : "Not running!"; assert !shutdown : "Already shutting down!"; shutdown = true; } /** * Starts the thread that waits for incoming JDWP connections. * * @param serverSocket the server socket on which to listen for incoming connections * @throws IOException this exception is thrown, when the server socket could not be created */ public void start(ServerSocket serverSocket) throws IOException { assert this.serverSocket == null : "Already started!"; this.serverSocket = serverSocket; this.serverSocket.setSoTimeout(TIMEOUT); new Thread(waitForClientsThread).start(); } /** * This command handler registry can be used to lookup or add JDWP command handlers. * * @return the command handler registry */ public CommandHandlerRegistry commandHandlerRegistry() { return commandHandlerRegistry; } /** * Thread waiting for clients to connect to the JDWP server. */ private Runnable waitForClientsThread = new Runnable() { public void run() { LOGGER.info("JDWPServer waiting for clients"); try { while (!shutdown) { try { final Socket clientSocket = serverSocket.accept(); new ClientThread(clientSocket).start(); } catch (SocketTimeoutException e) { } } } catch (IOException e) { LOGGER.severe("Exception occurred while waiting for clients: " + e.toString()); } finally { LOGGER.info("JDWP server is shut down"); serverSocket = null; shutdown = false; } } }; /** * Registry that manages the set of command handlers. */ private final CommandHandlerRegistry commandHandlerRegistry = new CommandHandlerRegistry() { private Map<Byte, Map<Byte, CommandHandler<? extends IncomingData, ? extends OutgoingData>>> commandHandlerCache = new HashMap<Byte, Map<Byte, CommandHandler<? extends IncomingData, ? extends OutgoingData>>>(); public CommandHandler<? extends IncomingData, ? extends OutgoingData> findCommandHandler(byte commandSetId, byte commandId) { if (!commandHandlerCache.containsKey(commandSetId)) { return null; } final CommandHandler<? extends IncomingData, ? extends OutgoingData> result = commandHandlerCache.get(commandSetId).get(commandId); assert result == null || result.getCommandSetId() == commandSetId : "Command set ID must match."; assert result == null || result.getCommandId() == commandId : "Command ID must match."; return result; } public void addCommandHandler(CommandHandler<? extends IncomingData, ? extends OutgoingData> commandHandler) { if (!commandHandlerCache.containsKey(commandHandler.getCommandSetId())) { commandHandlerCache.put(commandHandler.getCommandSetId(), new HashMap<Byte, CommandHandler<? extends IncomingData, ? extends OutgoingData>>()); } assert commandHandlerCache.containsKey(commandHandler.getCommandSetId()); final Map<Byte, CommandHandler<? extends IncomingData, ? extends OutgoingData>> map = commandHandlerCache.get(commandHandler.getCommandSetId()); if (map.containsKey(commandHandler.getCommandId())) { throw new IllegalArgumentException("Command handler with set id " + commandHandler.getCommandSetId() + " and command id " + commandHandler.getCommandId() + " is already installed."); } map.put(commandHandler.getCommandId(), commandHandler); LOGGER.info("added command handler " + commandHandler.getCommandSetId() + "/" + commandHandler.getCommandId()); } }; /** * Thread that is started for each client connection. */ private class ClientThread extends Thread { private Socket socket; public ClientThread(Socket socket) { this.socket = socket; } @Override public void run() { try { final JDWPStream stream = new JDWPStream(socket.getInputStream(), socket.getOutputStream()); stream.handshake(); LOGGER.info("Handshake passed successfully!"); while (!shutdown) { try { final IncomingPacket<? extends IncomingData, ? extends OutgoingData> incomingPacket = stream.receive(commandHandlerRegistry); try { final ReplyPacket<? extends IncomingData, ? extends OutgoingData> replyPacket = incomingPacket.handle(stream); if (replyPacket == null) { LOGGER.warning("No handler found for command " + incomingPacket.getCommandSetId() + "/" + incomingPacket.getCommandId() + "!"); throw new JDWPNotImplementedException(); } stream.send(replyPacket); } catch (JDWPException e) { LOGGER.warning("JDWP exception occured: " + e); stream.send(incomingPacket.createErrorReply((short) e.errorCode())); } catch (Throwable t) { LOGGER.log(Level.SEVERE, "Severe generic exception occured while handling packet", t); stream.send(incomingPacket.createErrorReply((short) Error.INTERNAL)); } } catch (JDWPIncomingPacketException e) { LOGGER.warning("JDWP exception occured while reading packet: " + e.innerException()); stream.send(e.packet().createErrorReply((short) e.innerException().errorCode())); } } } catch (IOException e) { LOGGER.log(Level.SEVERE, "IO exception in the client thread", e); } finally { try { socket.close(); } catch (IOException e) { LOGGER.log(Level.SEVERE, "IO exception when closing socket", e); } LOGGER.info("Client shutdown!"); } } } }