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}