001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2016-2018 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jgrapes.webconsole.examples.consoleapp; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.net.InetSocketAddress; 024import java.net.URI; 025import java.net.URISyntaxException; 026import java.security.KeyManagementException; 027import java.security.KeyStore; 028import java.security.KeyStoreException; 029import java.security.NoSuchAlgorithmException; 030import java.security.SecureRandom; 031import java.security.UnrecoverableKeyException; 032import java.security.cert.CertificateException; 033import java.util.Arrays; 034import java.util.Collections; 035import java.util.logging.Level; 036 037import javax.net.ssl.KeyManagerFactory; 038import javax.net.ssl.SSLContext; 039import org.jgrapes.core.Channel; 040import org.jgrapes.core.ClassChannel; 041import org.jgrapes.core.Component; 042import org.jgrapes.core.Components; 043import org.jgrapes.core.NamedChannel; 044import org.jgrapes.core.annotation.Handler; 045import org.jgrapes.core.events.HandlingError; 046import org.jgrapes.core.events.Stop; 047import org.jgrapes.http.HttpServer; 048import org.jgrapes.http.InMemorySessionManager; 049import org.jgrapes.http.LanguageSelector; 050import org.jgrapes.http.events.Request; 051import org.jgrapes.io.NioDispatcher; 052import org.jgrapes.io.util.PermitsPool; 053import org.jgrapes.net.SocketServer; 054import org.jgrapes.net.SslCodec; 055import org.jgrapes.util.ComponentCollector; 056import org.jgrapes.util.PreferencesStore; 057import org.jgrapes.webconsole.base.BrowserLocalBackedKVStore; 058import org.jgrapes.webconsole.base.ConletComponentFactory; 059import org.jgrapes.webconsole.base.ConsoleWeblet; 060import org.jgrapes.webconsole.base.KVStoreBasedConsolePolicy; 061import org.jgrapes.webconsole.base.PageResourceProviderFactory; 062import org.jgrapes.webconsole.base.WebConsole; 063import org.jgrapes.webconsole.vuejs.VueJsConsoleWeblet; 064 065/** 066 * 067 */ 068public class ConsoleApp extends Component { 069 070 private ConsoleApp app; 071 072 /** 073 * Instantiates a new http(s) web console demo. 074 */ 075 public ConsoleApp() { 076 super(new ClassChannel() { 077 }); 078 } 079 080 /** 081 * Log the exception when a handling error is reported. 082 * 083 * @param event the event 084 */ 085 @Handler(channels = Channel.class, priority = -10_000) 086 @SuppressWarnings("PMD.GuardLogStatement") 087 public void onHandlingError(HandlingError event) { 088 logger.log(Level.WARNING, event.throwable(), 089 () -> "Problem invoking handler with " + event.event() + ": " 090 + event.message()); 091 event.stop(); 092 } 093 094 /** 095 * Start the application. 096 * 097 * @throws Exception the exception 098 */ 099 @SuppressWarnings("PMD.SignatureDeclareThrowsException") 100 public void start() throws Exception { 101 // This class represents the application and its instance is 102 // therefore the root of the component tree. 103 app = new ConsoleApp(); 104 // Attach a general nio dispatcher 105 app.attach(new NioDispatcher()); 106 107 // Create a channel for the network level I/O. 108 Channel httpTransport = new NamedChannel("httpTransport"); 109 110 // Create a TCP server listening on port 8888 that 111 // uses (i.e. receives events from and sends events to) 112 // the network channel. 113 app.attach(new SocketServer(httpTransport) 114 .setServerAddress(new InetSocketAddress(8888))); 115 116 // The third component that uses (i.e. receives events from 117 // and sends events to) the httpTransport channel is 118 // an SslCodec. This actually uses two channels. The second 119 // channel is used for the encrypted I/O which is exchanged 120 // with another SocketServer. 121 SSLContext sslContext = createSslContext(); 122 Channel securedNetwork = app.attach( 123 new SocketServer().setServerAddress(new InetSocketAddress(8443)) 124 .setBacklog(3000).setConnectionLimiter(new PermitsPool(50))); 125 app.attach(new SslCodec(httpTransport, securedNetwork, sslContext)); 126 127 // Create an HTTP server as converter between transport and 128 // application layer. It connects to the httpTransport channel 129 // and the application channel which is provided by the 130 // ConsoleApp instance. The HTTP related events are send on/ 131 // received from the http channel. 132 Channel httpApplication = new NamedChannel("http"); 133 var httpServer = app.attach(new HttpServer(httpApplication, 134 httpTransport, Request.In.Get.class, Request.In.Post.class)); 135 136 // Build application layer. The application layer events are 137 // exchanged over the application channel. 138 httpServer.attach( 139 new PreferencesStore(httpApplication, this.getClass())); 140 httpServer.attach(new InMemorySessionManager(httpApplication)); 141 httpServer.attach(new LanguageSelector(httpApplication)); 142 143 // Assemble console components 144 createVueJsConsole(httpServer); 145 Components.start(app); 146 } 147 148 private SSLContext createSslContext() throws KeyStoreException, IOException, 149 NoSuchAlgorithmException, CertificateException, 150 UnrecoverableKeyException, KeyManagementException { 151 KeyStore serverStore = KeyStore.getInstance("JKS"); 152 try (InputStream keyFile 153 = ConsoleApp.class.getResourceAsStream("localhost.jks")) { 154 serverStore.load(keyFile, "nopass".toCharArray()); 155 } 156 KeyManagerFactory kmf = KeyManagerFactory.getInstance( 157 KeyManagerFactory.getDefaultAlgorithm()); 158 kmf.init(serverStore, "nopass".toCharArray()); 159 SSLContext sslContext = SSLContext.getInstance("TLS"); 160 sslContext.init(kmf.getKeyManagers(), null, new SecureRandom()); 161 return sslContext; 162 } 163 164 @SuppressWarnings("PMD.TooFewBranchesForASwitchStatement") 165 private void createVueJsConsole(HttpServer httpServer) 166 throws URISyntaxException { 167 ConsoleWeblet consoleWeblet 168 = httpServer.attach(new VueJsConsoleWeblet(httpServer.channel(), 169 Channel.SELF, new URI("/"))) 170 .prependClassTemplateLoader(this.getClass()) 171 .prependResourceBundleProvider(ConsoleApp.class) 172 .prependConsoleResourceProvider(ConsoleApp.class); 173 WebConsole console = consoleWeblet.console(); 174 // consoleWeblet.setConnectionInactivityTimeout(300_000); 175 console.attach(new BrowserLocalBackedKVStore( 176 console.channel(), consoleWeblet.prefix().getPath())); 177 console.attach(new KVStoreBasedConsolePolicy(console.channel())); 178 // Add all available page resource providers 179 console.attach(new ComponentCollector<>( 180 PageResourceProviderFactory.class, console.channel())); 181 // Add all available conlets 182 console.attach(new ComponentCollector<>( 183 ConletComponentFactory.class, console.channel(), type -> { 184 switch (type) { 185 case "org.jgrapes.webconlet.examples.login.LoginConlet": 186 return Collections.emptyList(); 187 default: 188 return Arrays.asList(Collections.emptyMap()); 189 } 190 })); 191 } 192 193 /** 194 * Stop the application. 195 * @throws Exception 196 * 197 * @throws Exception the exception 198 */ 199 @SuppressWarnings("PMD.SignatureDeclareThrowsException") 200 public void stop() throws Exception { 201 app.fire(new Stop(), Channel.BROADCAST); 202 Components.awaitExhaustion(); 203 } 204 205 /** 206 * @param args 207 * @throws IOException 208 * @throws InterruptedException 209 * @throws NoSuchAlgorithmException 210 * @throws KeyStoreException 211 * @throws UnrecoverableKeyException 212 * @throws CertificateException 213 * @throws KeyManagementException 214 */ 215 @SuppressWarnings("PMD.SignatureDeclareThrowsException") 216 public static void main(String[] args) throws Exception { 217 new ConsoleApp().start(); 218 } 219 220}