package org.myrobotlab.service; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import org.atmosphere.cpr.ApplicationConfig; import org.atmosphere.cpr.AtmosphereRequest; // import org.atmosphere.cpr.AtmosphereRequestImpl.Body; import org.atmosphere.cpr.AtmosphereRequest.Body; import org.atmosphere.cpr.AtmosphereResource; import org.atmosphere.cpr.AtmosphereResponse; import org.atmosphere.cpr.Broadcaster; import org.atmosphere.cpr.BroadcasterFactory; import org.atmosphere.nettosphere.Config; import org.atmosphere.nettosphere.Handler; import org.atmosphere.nettosphere.Nettosphere; import org.myrobotlab.codec.Codec; import org.myrobotlab.codec.CodecFactory; import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.codec.MethodCache; import org.myrobotlab.framework.Message; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.ServiceEnvironment; import org.myrobotlab.framework.ServiceType; import org.myrobotlab.framework.Status; import org.myrobotlab.framework.StatusLevel; import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.net.BareBonesBrowserLaunch; import org.myrobotlab.net.Connection; //import org.myrobotlab.service.WebGUI3.Error; import org.myrobotlab.service.interfaces.AuthorizationProvider; import org.myrobotlab.service.interfaces.Gateway; import org.myrobotlab.service.interfaces.ServiceInterface; //import org.myrobotlab.webgui.WebGUIServlet; import org.slf4j.Logger; /** * * WebGui - This service is the AngularJS based GUI TODO - messages & services * are already APIs - perhaps a data API - same as service without the message * wrapper */ public class WebGui extends Service implements AuthorizationProvider, Gateway, Handler { private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(WebGui.class); public Integer port; public Integer sslPort; transient Nettosphere nettosphere; transient Broadcaster broadcaster; transient BroadcasterFactory broadcastFactory; public String root = "root"; boolean useLocalResources = false; boolean autoStartBrowser = true; public String startURL = "http://localhost:%d"; // FIXME might need to change to HashMap<String, HashMap<String,String>> to // add client session public HashMap<String, String> servicePanels = new HashMap<String, String>(); // SHOW INTERFACE // FIXME - allowAPI1(true|false) // FIXME - allowAPI2(true|false) // FIXME - allow Protobuf/Thrift/Avro // FIXME - NO JSON ENCODING SHOULD BE IN THIS FILE !!! transient LiveVideoStreamHandler stream = new LiveVideoStreamHandler(); public static class PanelPos { String name; int x; int y; int z; public PanelPos(String name, int x, int y, int z) { this.name = name; this.x = x; this.y = y; this.z = z; } } /** * Static list of third party dependencies for this service. The list will * be consumed by Ivy to download and manage the appropriate resources * * @return */ public static class LiveVideoStreamHandler implements Handler { @Override public void handle(AtmosphereResource r) { // TODO Auto-generated method stub try { /* * OpenCV opencv = (OpenCV) Runtime.start("opencv", "OpenCV"); * OpenCVFilterFFMEG ffmpeg = new OpenCVFilterFFMEG("ffmpeg"); * opencv.addFilter(ffmpeg); opencv.capture(); sleep(1000); * opencv.removeFilters(); ffmpeg.stopRecording(); */ AtmosphereResponse response = r.getResponse(); // response.setContentType("video/mp4"); // response.setContentType("video/x-flv"); response.setContentType("video/avi"); // FIXME - mime type of avi ?? ServletOutputStream out = response.getOutputStream(); // response.addHeader(name, value); // byte[] data = FileIO.fileToByteArray(new // File("flvTest.flv")); // byte[] data = FileIO.fileToByteArray(new // File("src/resource/WebGUI/video/ffmpeg.1443989700495.mp4")); // byte[] data = FileIO.fileToByteArray(new // File("mp4Test.mp4")); byte[] data = FileIO.toByteArray(new File("test.avi.h264.mp4")); log.info("bytes {}", data.length); out.write(data); out.flush(); // out.close(); // r.write(data); // r.writeOnTimeout(arg0) // r.forceBinaryWrite(); // r.close(); } catch (Exception e) { Logging.logError(e); } } } public WebGui(String n) { super(n); } // ================ Gateway begin =========================== @Override public void addConnectionListener(String name) { // TODO Auto-generated method stub } @Override public void connect(String uri) throws URISyntaxException { // TODO Auto-generated method stub } @Override public HashMap<URI, Connection> getClients() { // TODO Auto-generated method stub return null; } @Override public List<Connection> getConnections(URI clientKey) { // TODO Auto-generated method stub return null; } @Override public String getPrefix(URI protocolKey) { // TODO Auto-generated method stub return null; } @Override public Connection publishConnect(Connection keys) { // TODO Auto-generated method stub return null; } @Override public void sendRemote(String key, Message msg) throws URISyntaxException { // TODO Auto-generated method stub } @Override public void sendRemote(URI key, Message msg) { // TODO Auto-generated method stub } // ================ Gateway end =========================== // ================ AuthorizationProvider begin =========================== @Override public boolean allowExport(String serviceName) { // TODO Auto-generated method stub return false; } @Override public boolean isAuthorized(HashMap<String, String> security, String serviceName, String method) { // TODO Auto-generated method stub return false; } @Override public boolean isAuthorized(Message msg) { // TODO Auto-generated method stub return false; } // ================ AuthorizationProvider end =========================== // ================ Broadcaster begin =========================== /** * FIXME - needs to be LogListener interface with * LogListener.onLogEvent(String logEntry) !!!! THIS SHALL LOG NO ENTRIES OR * ABANDON ALL HOPE !!! * * This is completely out of band - it does not use the regular queues inbox * or outbox * * We want to broadcast this - but THERE CAN NOT BE ANY log.info/warn/error * etc !!!! or there will be an infinite loop and you will be at the gates * of hell ! * * @param logEntry */ public void onLogEvent(Message msg) { try { if (broadcaster != null) { Codec codec = CodecFactory.getCodec(CodecUtils.MIME_TYPE_MESSAGES); ByteArrayOutputStream bos = new ByteArrayOutputStream(); codec.encode(bos, msg); bos.close(); broadcaster.broadcast(new String(bos.toByteArray())); // wtf } } catch (Exception e) { System.out.print(e.getMessage()); } } public void broadcast(Message msg) { try { if (broadcaster != null) { Codec codec = CodecFactory.getCodec(CodecUtils.MIME_TYPE_MESSAGES); ByteArrayOutputStream bos = new ByteArrayOutputStream(); codec.encode(bos, msg); bos.close(); broadcaster.broadcast(new String(bos.toByteArray())); // wtf } } catch (Exception e) { Logging.logError(e); } } /** * redirects browser to new url * * @param url * @return */ public String redirect(String url) { return url; } // ================ Broadcaster end =========================== public Config.Builder getConfig() { Config.Builder configBuilder = new Config.Builder(); configBuilder /* * did not work :( .resource( * "jar:file:/C:/mrl/myrobotlab/dist/myrobotlab.jar!/resource") * .resource( * "jar:file:/C:/mrl/myrobotlab/dist/myrobotlab.jar!/resource/WebGui" * ) */ .resource("/stream", stream) // .resource("/video/ffmpeg.1443989700495.mp4", test) // for debugging .resource("./src/resource/WebGui").resource("./src/resource") // for runtime - after extractions .resource("./resource/WebGui").resource("./resource") // Support 2 APIs // REST - http://host/object/method/param0/param1/... // synchronous DO NOT SUSPEND .resource("/api", this) // if Jetty is in the classpath it will use it by default - we // want to use Netty // .initParam("org.atmosphere.websocket.maxTextMessageSize", // "100000") // .initParam("org.atmosphere.websocket.maxBinaryMessageSize", // "100000") .initParam("org.atmosphere.cpr.asyncSupport", "org.atmosphere.container.NettyCometSupport").initParam(ApplicationConfig.SCAN_CLASSPATH, "false") .initParam(ApplicationConfig.PROPERTY_SESSION_SUPPORT, "true").port(port).host("0.0.0.0"); // all // ips SSLContext sslContext = createSSLContext(); if (sslContext != null) { configBuilder.sslContext(sslContext); } // SessionSupport ss = new SessionSupport(); configBuilder.build(); return configBuilder; } public boolean save() { return super.save(); } SSLContext createSSLContext() { try { if (sslPort != null) { return SSLContext.getInstance("TLS"); } } catch (Exception e) { log.warn("can not make ssl context", e); } return null; } public void start() { try { if (port == null) { port = 8888; } // Broadcaster b = broadcasterFactory.get(); // a session "might" be nice - but for now we are stateless // SessionSupport ss = new SessionSupport(); if (nettosphere != null && nettosphere.isStarted()) { // is running info("{} currently running on port {} - stop first, then start"); return; } nettosphere = new Nettosphere.Builder().config(getConfig().build()).build(); sleep(1000); // needed ? try { nettosphere.start(); } catch (Exception e) { Logging.logError(e); } broadcastFactory = nettosphere.framework().getBroadcasterFactory(); // get default boadcaster broadcaster = broadcastFactory.get("/*"); log.info("WebGui {} started on port {}", getName(), port); if (autoStartBrowser) { log.info("auto starting default browser"); BareBonesBrowserLaunch.openURL(String.format(startURL, port)); } // get all instances // we want all onState & onStatus events from all services ServiceEnvironment se = Runtime.getLocalServices(); for (String name : se.serviceDirectory.keySet()) { ServiceInterface si = se.serviceDirectory.get(name); onRegistered(si); } // additionally we will want onState & onStatus events from all // services // from all new services which were created "after" the webgui // so susbcribe to our Runtimes methods of interest Runtime runtime = Runtime.getInstance(); subscribe(runtime.getName(), "registered"); subscribe(runtime.getName(), "released"); } catch (Exception e) { Logging.logError(e); } } public void startService() { super.startService(); // extract all resources // if resource directory exists - do not overwrite ! // could whipe out user mods try { extract(); } catch (Exception e) { Logging.logError(e); } start(); } public void onRegistered(ServiceInterface si) { // new service // subscribe to the status events subscribe(si.getName(), "publishStatus"); subscribe(si.getName(), "publishState"); // for distributed Runtimes if (si.isRuntime()) { subscribe(si.getName(), "registered"); } // broadcast it too // repackage message /* * don't need to do this :) Message m = createMessage(getName(), * "onRegistered", si); m.sender = Runtime.getInstance().getName(); * broadcast(m); */ } public Map<String, String> getHeadersInfo(HttpServletRequest request) { Map<String, String> map = new HashMap<String, String>(); /* * Atmosphere (nearly) always gives a ConcurrentModificationException * its supposed to be fixed in later versions - but later version have * proven very unstable * * Enumeration<String> headerNames = request.getHeaderNames(); while * (headerNames.hasMoreElements()) { String key = (String) * headerNames.nextElement(); String value = request.getHeader(key); * map.put(key.toLowerCase(), value); } */ return map; } /** * With a single method Atmosphere does so much !!! It sets up the * connection, possibly gets a session, turns the request into something * like a HTTPServletRequest, provides us with input & output streams - and * manages all the "long polling" or websocket upgrades on its own ! * * Atmosphere Rocks ! */ @Override public void handle(AtmosphereResource r) { Codec codec = null; OutputStream out = null; String httpMethod = r.getRequest().getMethod(); // default api type String apiTypeKey = CodecUtils.TYPE_MESSAGES; try { AtmosphereRequest request = r.getRequest(); AtmosphereResponse response = r.getResponse(); InputStream in = r.getRequest().getInputStream(); out = r.getResponse().getOutputStream(); String pathInfo = request.getPathInfo(); String[] parts = null; log.info("{} {}", request.getMethod(), pathInfo); // Broadcaster bc = r.getBroadcaster(); // if (bc != null || r.getBroadcaster() != broadcaster){ r.setBroadcaster(broadcaster); // } // good debug material // log.info("sessionId {}", r); if (log.isDebugEnabled()) { String sessionId = r.getAtmosphereResourceEvent().getResource().getRequest().getSession().getId(); log.debug("sessionId {}", sessionId); } Map<String, String> headers = getHeadersInfo(request); if (headers.containsKey("content-type")) { log.debug(String.format(String.format("in encoding : content-type %s", headers.get("content-type")))); } if (headers.containsKey("accept")) { log.debug(String.format(String.format("out encoding : accept %s", headers.get("accept")))); } // GET vs POST - post assumes low-level messaging // GET is high level synchronous // String httpMethod = request.getMethod(); // get default encoder // FIXME FIXME FIXME - this IS A CODEC !!! NOT AN API-TYPE !!! - // CHANGE to MIME_TYPE_APPLICATION_JSON !!! codec = CodecFactory.getCodec(CodecUtils.MIME_TYPE_MESSAGES); if (pathInfo != null) { parts = pathInfo.split("/"); } if (parts == null || parts.length < 3) { // http://host:port/api FIXME SWAGGER ???? FIXME ??? response.addHeader("Content-Type", codec.getMimeType()); handleError(httpMethod, out, codec, "API", "http(s)://{host}:{port}/api/{api-type}", apiTypeKey); return; } // set to requested api type apiTypeKey = parts[2]; if ("messages".equals(apiTypeKey)) { if (!r.isSuspended()) { r.suspend(); } } // FIXME - this is currently useless // simple - from apiType - get the mime type - if you want to mess // with headers <--==--> encoding then do that... String codecMimeType = CodecUtils.getKeyToMimeType(apiTypeKey); if (!codecMimeType.equals(codec.getMimeType())) { // request to switch codec types on codec = CodecFactory.getCodec(codecMimeType); } /* * interesting .... switch (r.transport()) { case JSONP: case * LONG_POLLING: event.getResource().resume(); break; case * WEBSOCKET: case STREAMING: res.getWriter().flush(); break; } */ // FIXME - should NOT be set until resolved !!! response.addHeader("Content-Type", codec.getMimeType()); if (parts.length == 3) { // ======================================== // POST || GET http://{host}:{port}/api/messages // POST || GET http://{host}:{port}/api/services // ======================================== // if message api-type - we only have/need 3 URI parts if ("messages".equals(parts[2]) && "POST".equals(httpMethod)) { request.body(); Body body = request.body(); processMessageAPI(codec, body); return; } // ServiceEnvironment env = Runtime.getLocalServices(); HashMap<URI, ServiceEnvironment> env = Runtime.getEnvironments(); // FIXME - getEnvironments() // FIXME - relfect with javdoc info log.info("inspecting"); respond(out, codec, "getLocalServices", env, apiTypeKey); return; } else if (parts.length == 4) { // *** /api/messages/runtime/ *** // *** /api/services/servo/ **** ServiceInterface si = Runtime.getService(parts[3]); if (pathInfo.endsWith("/")) { respond(out, codec, "onDeclaredMethods", si.getMethodMap(), apiTypeKey); } else { respond(out, codec, "onService", si, apiTypeKey); } return; } // parts.length > 4 => /api/services/{name}/method String name = parts[3]; ServiceInterface si = Runtime.getService(name); Class<?> clazz = si.getClass(); Class<?>[] paramTypes = null; Object[] params = new Object[0]; // FIXME - decode body assumption is that its in an ARRAY // MUST MAKE DECISION ON PRECEDENCE // String body = convertStreamToString(in); int cl = request.getContentLength(); byte[] body = null; // FIXME - need to take care of this - client does not always send // correct // length if (cl > 0) { body = new byte[cl]; int bytesRead = in.read(body); if (bytesRead != cl) { handleError(httpMethod, out, codec, "BadInput", String.format("client said it would send %d bytes but only %d were read", cl, bytesRead), apiTypeKey); return; } } // FIXME - sloppy to convert to String here - should be done in the // Encoder (if that happens) String b = null; if (body != null) { b = new String(body); } if (b != null){ log.info(String.format("POST Body [%s]", b)); } // FIXED ME // 1. get method "name" and incoming ordinal - generate method // signature (optional)- check method cache // 2. "attempt" to get method // 3. (optional) - if failure - scan methods - find one with // signature - cache it - call it String methodName = String.format("%s", parts[4]); // decoded array of encoded parameters Object[] encodedArray = new Object[0]; // BODY - PARAMETERS if (cl > 0) { // REQUIREMENT must be in an encoded array - even binary // 1. decode the array // 2. will need to decode contents of each parameter later based // on signature of reflected method encodedArray = codec.decodeArray(b); // WE NOW HAVE ORDINAL // URI - PARAMETERS - TODO - define added encoding spec > 5 ? } else if (parts.length > 5) { // REQUIREMENT must be in an encoded array - even binary // 1. array is URI / // 2. will need to decode contents of each parameter later based // on signature of reflected method // get params from uri - its our array // difference is initial state regardless of encoding we are // guaranteed the URI parts are strings // encodedArray = new Object[parts.length - 3]; encodedArray = new Object[parts.length - 5]; for (int i = 0; i < encodedArray.length; ++i) { String result = URLDecoder.decode(parts[i + 5], "UTF-8"); encodedArray[i] = result; } // WE NOW HAVE ORDINAL } // FETCH AND MERGE METHOD - we have ordinal count now - but NOT the // decoded // parameters // NOW HAVE ORDINAL - fetch the method with its types paramTypes = MethodCache.getCandidateOnOrdinalSignature(si.getClass(), methodName, encodedArray.length); // WE NOW HAVE ORDINAL AND TYPES params = new Object[encodedArray.length]; // DECODE AND FILL THE PARAMS for (int i = 0; i < params.length; ++i) { params[i] = codec.decode(encodedArray[i], paramTypes[i]); } Method method = clazz.getMethod(methodName, paramTypes); // NOTE -------------- // strategy of find correct method with correct parameter types // "name" is the strongest binder - but without a method cache we // are condemned to scan through all methods // also without a method cache - we have to figure out if the // signature would fit with instanceof for each object // and "boxed" types as well // best to fail - then attempt to resolve through scanning through // methods and trying types - then cache the result // FIXME - this is duplicated in processMessageAPI :( if (si.isLocal()) { log.debug("{} is local", name); Object ret = method.invoke(si, params); respond(out, codec, method.getName(), ret, apiTypeKey); } else { // FIXME - creat blocking send based on api requested ? log.debug("{} is is remote", name); Message msg = createMessage(name, method.getName(), params); out(msg); } MethodCache.cache(clazz, method); // FIXME - there is no content mime-type being set !!! this would // depend on codec being used // FIXME - currently a keyword - "json" internally defines the codec // - getMimeType !! } catch (Exception e) { handleError(httpMethod, out, codec, e, apiTypeKey); } } public void processMessageAPI(Codec codec, Body body) throws Exception { // first decoding will give you an array of types in msg.data[] // but they are un-coerced - we need the method signature candidate // to determine what we should coerce them into Message msg = CodecUtils.fromJson(body.asString(), Message.class); if (msg == null) { log.error(String.format("msg is null %s", body.asString())); return; } msg.sender = getName(); log.debug("got msg {}", msg.toString()); // out(msg); // get the service ServiceInterface si = Runtime.getService(msg.name); if (si == null) { error("could not get service %s for msg %s", msg.name, msg); return; } Class<?> clazz = si.getClass(); Class<?>[] paramTypes = null; Object[] params = new Object[msg.data.length]; // decoded array of encoded parameters // FIXME - not "really" correct ! Object[] encodedArray = msg.data;// new Object[msg.data.length]; // encodedArray = codec.decodeArray(b); paramTypes = MethodCache.getCandidateOnOrdinalSignature(si.getClass(), msg.method, encodedArray.length); if (log.isDebugEnabled()) { StringBuffer sb = new StringBuffer(String.format("(%s)%s.%s(", clazz.getSimpleName(), msg.name, msg.method)); for (int i = 0; i < paramTypes.length; ++i) { if (i != 0) { sb.append(","); } sb.append(paramTypes[i].getSimpleName()); } sb.append(")"); log.debug(sb.toString()); } // WE NOW HAVE ORDINAL AND TYPES params = new Object[encodedArray.length]; // DECODE AND FILL THE PARAMS for (int i = 0; i < params.length; ++i) { params[i] = codec.decode(encodedArray[i], paramTypes[i]); } // FIXME FIXME FIXME !!!! // Service.invoke needs to use method cach BUT - internal queues HAVE // type information // AND decoded json DOES NOT - needs to be optimized such that it knows // the encoding // before using the method cache - and the "hint" determins // getBestCanidate !!!! Method method = clazz.getMethod(msg.method, paramTypes); // NOTE -------------- // strategy of find correct method with correct parameter types // "name" is the strongest binder - but without a method cache we // are condemned to scan through all methods // also without a method cache - we have to figure out if the // signature would fit with instanceof for each object // and "boxed" types as well // best to fail - then attempt to resolve through scanning through // methods and trying types - then cache the result // FIXME - not good - using my thread to execute another services // method and put its return on the the services out queue :P if (si.isLocal()) { log.debug("{} is local", si.getName()); Object retobj = method.invoke(si, params); // FIXME - Is this how to support synchronous ? // What does this mean ? // respond(out, codec, method.getName(), ret); si.out(msg.method, retobj); } else { log.debug("{} is remote", si.getName()); // send(msg.name, msg.method, msg.data); send(msg.name, msg.method, params); // out(msg); LETHAL ! } MethodCache.cache(clazz, method); } // FIXME !!! - ALL CODECS SHOULD HANDLE MSG INSTEAD OF OBJECT !!! // THEN YOU COULD ALSO HAVE urlToMsg(URL url) // "lower layer encoders can strip down to the data" !!! public void respond(OutputStream out, Codec codec, String method, Object ret, String apiTypeKey) throws Exception { // getName() ? -> should it be AngularJS client name ? Message msg = createMessage(getName(), CodecUtils.getCallBackName(method), ret); if (CodecUtils.API_TYPE_SERVICES.equals(apiTypeKey)) { codec.encode(out, msg.data[0]); } else { // API_TYPE_MESSAGES codec.encode(out, msg); } } public void handleError(String httpMethod, OutputStream out, Codec codec, Throwable e, String apiTypeKey) { handleError(httpMethod, out, codec, e.getMessage(), Logging.logError(e), apiTypeKey); } // FIXME - APP_EVENT_LOG for normalizing (if available) public void handleError(String httpMethod, OutputStream out, Codec codec, String key, String detail, String apiTypeKey) { try { log.error(detail); Status error = new Status(getName(), StatusLevel.ERROR, key, detail); if ("POST".equals(httpMethod)) { broadcast(createMessage(getName(), "onStatus", error)); } else { respond(out, codec, "handleError", error, apiTypeKey); } } catch (Exception e) { Logging.logError(e); } } public void extract() throws IOException { extract(false); } public void extract(boolean overwrite) throws IOException { // FIXME - check resource version vs self version // overwrite if different ? FileIO.extractResources(overwrite); /* * try { Zip.extractFromFile("./myrobotlab.jar", "root", * "resource/WebGui"); } catch (IOException e) { error(e); } */ } /** * - use the service's error() pub sub return public void handleError(){ * * } */ /** * determines if references to JQuery JavaScript library are local or if the * library is linked to using content delivery network. Default (false) is * to use the CDN * * @param b */ public void useLocalResources(boolean useLocalResources) { this.useLocalResources = useLocalResources; } public void startBrowser(String URL) { BareBonesBrowserLaunch.openURL(String.format(URL, port)); } public void autoStartBrowser(boolean autoStartBrowser) { this.autoStartBrowser = autoStartBrowser; } @Override public boolean preProcessHook(Message m) { // FIXME - problem with collisions of this service's methods // and dialog methods ?!?!? // broadcast broadcast(m); // if the method name is == to a method in the WebGui // process it if (methodSet.contains(m.method)) { // process the message like a regular service return true; } // otherwise send the message to the dialog with the senders name // broadcast(m); return false; } public void savePanel(String name, String panel) { servicePanels.put(name, panel); } public String loadPanel(String name) { if (servicePanels.containsKey(name)) { return servicePanels.get(name); } return null; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; // startNettosphere(); } public void stopService() { super.stopService(); stop(); } public void restart() { stop(); start(); } public void stop() { if (nettosphere != null) { log.info("stopping nettosphere"); // Must not be called from a I/O-Thread to prevent deadlocks! (new Thread("stopping nettophere") { public void run() { /* * nettosphere.framework().removeAllAtmosphereHandler(); * nettosphere.framework().resetStates(); * nettosphere.framework().destroy(); */ nettosphere.stop(); } }).start(); sleep(1000); } } /** * https://github.com/Atmosphere/nettosphere/issues/17 A callback used to * configure {@link javax.net.ssl.SSLEngine} before they get injected in * Netty. */ public interface SSLContextListener { SSLContextListener DEFAULT = new SSLContextListener() { @Override public void onPostCreate(SSLEngine e) { e.setEnabledCipherSuites(new String[] { "SSL_DH_anon_WITH_RC4_128_MD5" }); e.setUseClientMode(false); } }; /** * Invoked just after the {@link SSLEngine} has been created, but not * yet injected in Netty. * * @param e * SSLEngine; */ public void onPostCreate(SSLEngine e); } // === begin positioning panels plumbing === public void set(String name, int x, int y) { set(name, x, y, 0); // or is z -1 ? } public void set(String name, int x, int y, int z) { invoke("publishSet", new PanelPos(name, x, y, z)); } public void showAll(boolean b) { invoke("publishShowAll", b); } public void show(String name) { invoke("publishShow", name); } public void hide(String name) { invoke("publishHide", name); } public String publishShow(String name) { return name; } public String publishHide(String name) { return name; } public boolean publishShowAll(boolean b) { return b; } public PanelPos publishSet(PanelPos pos) { return pos; } // === end positioning panels plumbing === /** * This static method returns all the details of the class without it having * to be constructed. It has description, categories, dependencies, and peer * definitions. * * @return ServiceType - returns all the data * */ static public ServiceType getMetaData() { ServiceType meta = new ServiceType(WebGui.class.getCanonicalName()); meta.addDescription("web display"); meta.addCategory("display"); // MAKE NOTE !!! - we currently distribute myrobotlab.jar with a webgui // hence these following dependencies are zipped with myrobotlab.jar ! // and are NOT listed as dependencies, because they are already included // Its now part of myrobotlab.jar - unzipped in // build.xml (part of myrobotlab.jar now) // meta.addDependency("io.netty", "3.10.0"); // netty-3.10.0.Final.jar // meta.addDependency("org.atmosphere.nettosphere", "2.3.0"); // // nettosphere-assembly-2.3.0.jar // meta.addDependency("org.atmosphere.nettosphere", "2.3.0");// // geronimo-servlet_3.0_spec-1.0.jar return meta; } public static void main(String[] args) { LoggingFactory.getInstance().configure(); LoggingFactory.getInstance().setLevel(Level.INFO); try { Double level = Runtime.getBatteryLevel(); log.info("" + level); Runtime.start("python", "Python"); // Runtime.start("arduino", "Arduino"); //Runtime.start("srf05", "UltrasonicSensor"); Runtime.start("webgui", "WebGui"); } catch (Exception e) { Logging.logError(e); } } @Override public String publishConnect() { // TODO Auto-generated method stub return null; } @Override public String publishDisconnect() { // TODO Auto-generated method stub return null; } @Override public Status publishError() { // TODO Auto-generated method stub return null; } }