001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.stream.net; 033 034import java.io.IOException; 035import java.io.UnsupportedEncodingException; 036import java.net.InetSocketAddress; 037import java.net.URLDecoder; 038import java.util.HashMap; 039import java.util.LinkedList; 040 041import org.graphstream.stream.SourceBase; 042 043import com.sun.net.httpserver.HttpExchange; 044import com.sun.net.httpserver.HttpHandler; 045import com.sun.net.httpserver.HttpServer; 046 047/** 048 * This source allows to control a graph from a web browser. Control is done 049 * calling the following url : 050 * <code>http://host/graphId/edit?q=ACTION&...</code>. ACTION is one of the 051 * following action : 052 * <ul> 053 * <li>an : add node</li> 054 * <li>cn : change node</li> 055 * <li>dn : delete node</li> 056 * <li>ae : add edge</li> 057 * <li>ce : change edge</li> 058 * <li>de : delete edge</li> 059 * <li>cg : change graph</li> 060 * <li>st : step begins</li> 061 * <li>clear : clear the whole graph</li> 062 * </ul> 063 * 064 * Each of these actions needs some argument. 065 * <dl> 066 * <dt>an</dt> 067 * <dd> 068 * <ul> 069 * <li>id</li> 070 * </ul> 071 * </dd> 072 * <dt>cn</dt> 073 * <dd> 074 * <ul> 075 * <li>id</li> 076 * <li>key</li> 077 * <li>value</li> 078 * </ul> 079 * </dd> 080 * <dt>dn</dt> 081 * <dd> 082 * <ul> 083 * <li>id</li> 084 * </ul> 085 * </dd> 086 * <dt>ae</dt> 087 * <dd> 088 * <ul> 089 * <li>id</li> 090 * <li>from</li> 091 * <li>to</li> 092 * <li>[directed]</li> 093 * </ul> 094 * </dd> 095 * <dt>ce</dt> 096 * <dd> 097 * <ul> 098 * <li>id</li> 099 * <li>key</li> 100 * <li>value</li> 101 * </ul> 102 * </dd> 103 * <dt>de</dt> 104 * <dd> 105 * <ul> 106 * <li>id</li> 107 * </ul> 108 * </dd> 109 * <dt>cg</dt> 110 * <dd> 111 * <ul> 112 * <li>key</li> 113 * <li>value</li> 114 * </ul> 115 * </dd> 116 * <dt>st</dt> 117 * <dd> 118 * <ul> 119 * <li>step</li> 120 * </ul> 121 * </dd> 122 * </dl> 123 */ 124public class HTTPSource extends SourceBase { 125 126 /** 127 * Http server. 128 */ 129 protected final HttpServer server; 130 131 /** 132 * Create a new http source. The source will be available on 133 * 'http://localhost/graphId' where graphId is passed as parameter of this 134 * constructor. 135 * 136 * @param graphId 137 * id of the graph 138 * @param port 139 * port on which server will be bound 140 * @throws IOException 141 * if server creation failed. 142 */ 143 public HTTPSource(String graphId, int port) throws IOException { 144 super(String.format("http://%s", graphId)); 145 146 server = HttpServer.create(new InetSocketAddress(port), 4); 147 server.createContext(String.format("/%s/edit", graphId), 148 new EditHandler()); 149 150 } 151 152 /** 153 * Start the http server. 154 */ 155 public void start() { 156 server.start(); 157 } 158 159 /** 160 * Stop the http server. 161 */ 162 public void stop() { 163 server.stop(0); 164 } 165 166 private class EditHandler implements HttpHandler { 167 168 public void handle(HttpExchange ex) throws IOException { 169 HashMap<String, Object> get = GET(ex); 170 Action a; 171 172 try { 173 a = Action.valueOf(get.get("q").toString().toUpperCase()); 174 } catch (Exception e) { 175 error(ex, "invalid action"); 176 return; 177 } 178 179 switch (a) { 180 case AN: 181 HTTPSource.this.sendNodeAdded(sourceId, get.get("id") 182 .toString()); 183 break; 184 case CN: 185 break; 186 case DN: 187 HTTPSource.this.sendNodeRemoved(sourceId, get.get("id") 188 .toString()); 189 break; 190 case AE: 191 HTTPSource.this.sendEdgeAdded(sourceId, get.get("id") 192 .toString(), get.get("from").toString(), get.get("to") 193 .toString(), get.containsKey("directed")); 194 break; 195 case CE: 196 break; 197 case DE: 198 HTTPSource.this.sendEdgeRemoved(sourceId, get.get("id") 199 .toString()); 200 break; 201 case CG: 202 break; 203 case ST: 204 HTTPSource.this.sendStepBegins(sourceId, Double.valueOf(get 205 .get("step").toString())); 206 break; 207 } 208 209 ex.sendResponseHeaders(200, 0); 210 ex.getResponseBody().close(); 211 } 212 } 213 214 protected static void error(HttpExchange ex, String message) 215 throws IOException { 216 byte[] data = message.getBytes(); 217 218 ex.sendResponseHeaders(400, data.length); 219 ex.getResponseBody().write(data); 220 ex.getResponseBody().close(); 221 } 222 223 @SuppressWarnings("unchecked") 224 protected static HashMap<String, Object> GET(HttpExchange ex) { 225 HashMap<String, Object> get = new HashMap<String, Object>(); 226 String[] args = ex.getRequestURI().getRawQuery().split("[&]"); 227 228 for (String arg : args) { 229 String[] kv = arg.split("[=]"); 230 String k, v; 231 232 k = null; 233 v = null; 234 235 try { 236 if (kv.length > 0) 237 k = URLDecoder.decode(kv[0], System 238 .getProperty("file.encoding")); 239 240 if (kv.length > 1) 241 v = URLDecoder.decode(kv[1], System 242 .getProperty("file.encoding")); 243 244 if (get.containsKey(k)) { 245 Object o = get.get(k); 246 247 if (o instanceof LinkedList<?>) 248 ((LinkedList<Object>) o).add(v); 249 else { 250 LinkedList<Object> l = new LinkedList<Object>(); 251 l.add(o); 252 l.add(v); 253 get.put(k, l); 254 } 255 } else { 256 get.put(k, v); 257 } 258 } catch (UnsupportedEncodingException e) { 259 e.printStackTrace(); 260 } 261 } 262 263 return get; 264 } 265 266 static enum Action { 267 AN, CN, DN, AE, CE, DE, CG, ST, CLEAR 268 } 269}