// Copyright (c) 2006 - 2008, Markus Strauch. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions 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. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. package net.sf.sdedit.server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.LinkedList; import java.util.PriorityQueue; import net.sf.sdedit.config.ConfigurationManager; import net.sf.sdedit.diagram.Diagram; import net.sf.sdedit.error.SemanticError; import net.sf.sdedit.error.SyntaxError; import net.sf.sdedit.text.TextHandler; /** * This class implements a simple server that uses a TCP socket to receive a * diagram source text and to send an image of the resulting diagram. * <p> * The exchange must follow this simple protocol: <br> * <ol> * <li>The client sends the mimetype of the image he would like to receive.</li> * <li>The client sends the diagram source text. * <li>The client sends 'END'</li> * <li> * <ul> * <li> If the diagram cannot be created due to an error, the server sends a * message starting with 'ERROR:' and ending with a description of the error. * </li> * <li> Otherwise the server sends the image data.</li> * </ul> * </li> * </ol> * * There are currently two mime-types supported: * * <ol> * <li>image/png</li> * <li>image/svg+xml (requires the SVG plugin)</li> * </ol> * * @author Markus Strauch * */ public class DiagramServer extends Thread { private ServerSocket serverSocket; private PriorityQueue<ServerThread> threadPool; // the number of worker threads for processing requests (i. e. drawing // diagrams) private static int poolSize = 5; /** * Creates and starts a new DiagramServer listening at the given port. * * @param port * the TCP port where the diagram server listens * @throws IOException * if no DiagramServer listening at that port could be created */ public DiagramServer(int port) throws IOException { serverSocket = new ServerSocket(port); setName("DiagramServer"); setDaemon(true); threadPool = new PriorityQueue<ServerThread>(); for (int i = 0; i < poolSize; i++) { ServerThread worker = new ServerThread(); worker.start(); threadPool.add(worker); } start(); } private static class ServerThread extends Thread implements Comparable<ServerThread> { private LinkedList<Socket> queue; ServerThread() { queue = new LinkedList<Socket>(); setName("DiagramServer worker thread"); setDaemon(true); } synchronized void addJob(Socket socket) { queue.addFirst(socket); notify(); } public synchronized int compareTo(ServerThread st) { return queue.size() - st.queue.size(); } public synchronized boolean equals(Object o) { if (!(o instanceof ServerThread)) { return false; } ServerThread st = (ServerThread) o; return compareTo(st) == 0; } public void run() { while (true) { Socket socket = null; synchronized (this) { while (queue.isEmpty()) { try { wait(); } catch (InterruptedException ignored) { /* empty */ } } socket = queue.removeLast(); } try { InputStreamReader isr = new InputStreamReader(socket .getInputStream(), "utf-8"); BufferedReader reader = new BufferedReader(isr); String type = reader.readLine().trim(); StringBuffer buffer = new StringBuffer(); String line; while (true) { line = reader.readLine(); if (line == null || line.equals("END")) { break; } buffer.append(line + "\n"); } OutputStreamWriter osr = new OutputStreamWriter(socket .getOutputStream(), "utf-8"); PrintWriter pw = new PrintWriter(osr); try { Exporter exporter = Exporter .getExporter(type, null, "A4", socket.getOutputStream()); if (exporter == null) { throw new RuntimeException( "FreeHEP library missing."); } Diagram diagram = new Diagram(ConfigurationManager .createNewDefaultConfiguration().getDataObject(), new TextHandler(buffer.toString()), exporter); diagram.generate(); exporter.export(); } catch (SyntaxError se) { TextHandler th = (TextHandler) se.getProvider(); String msg = "ERROR:syntax error in line " + th.getLineNumber() + ": " + se.getMessage(); pw.println(msg); pw.flush(); pw.close(); } catch (SemanticError se) { TextHandler th = (TextHandler) se.getProvider(); String msg = "ERROR:semantic error in line " + th.getLineNumber() + ": " + se.getMessage(); pw.println(msg); pw.flush(); pw.close(); } catch (Throwable t) { pw.println("ERROR:fatal error: " + t.getMessage()); pw.flush(); pw.close(); } socket.close(); } catch (Throwable t) { t.printStackTrace(); } finally { if (socket != null) { try { socket.close(); } catch (Exception ignored) { /* empty */ } } } } } } /** * Blocks until a connection is made, then creates a socket for the * connection and blocks again, waiting for the next connection. */ public void run() { while (true) { try { Socket socket = serverSocket.accept(); ServerThread worker = threadPool.remove(); worker.addJob(socket); threadPool.add(worker); } catch (Throwable t) { t.printStackTrace(); } } } }