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; 036import javax.net.ssl.KeyManagerFactory; 037import javax.net.ssl.SSLContext; 038import org.jgrapes.core.Channel; 039import org.jgrapes.core.ClassChannel; 040import org.jgrapes.core.Component; 041import org.jgrapes.core.Components; 042import org.jgrapes.core.NamedChannel; 043import org.jgrapes.core.annotation.Handler; 044import org.jgrapes.core.events.HandlingError; 045import org.jgrapes.core.events.Stop; 046import org.jgrapes.http.HttpServer; 047import org.jgrapes.http.InMemorySessionManager; 048import org.jgrapes.http.LanguageSelector; 049import org.jgrapes.http.events.Request; 050import org.jgrapes.io.NioDispatcher; 051import org.jgrapes.io.util.PermitsPool; 052import org.jgrapes.net.SocketServer; 053import org.jgrapes.net.SslCodec; 054import org.jgrapes.util.ComponentCollector; 055import org.jgrapes.util.PreferencesStore; 056import org.jgrapes.webconsole.base.BrowserLocalBackedKVStore; 057import org.jgrapes.webconsole.base.ConletComponentFactory; 058import org.jgrapes.webconsole.base.ConsoleWeblet; 059import org.jgrapes.webconsole.base.KVStoreBasedConsolePolicy; 060import org.jgrapes.webconsole.base.PageResourceProviderFactory; 061import org.jgrapes.webconsole.base.WebConsole; 062import org.jgrapes.webconsole.vuejs.VueJsConsoleWeblet; 063 064/** 065 * 066 */ 067public class ConsoleApp extends Component { 068 069 private ConsoleApp app; 070 071 /** 072 * Instantiates a new http(s) web console demo. 073 */ 074 public ConsoleApp() { 075 super(new ClassChannel() { 076 }); 077 } 078 079 /** 080 * Log the exception when a handling error is reported. 081 * 082 * @param event the event 083 */ 084 @Handler(channels = Channel.class, priority = -10_000) 085 @SuppressWarnings("PMD.GuardLogStatement") 086 public void onHandlingError(HandlingError event) { 087 logger.log(Level.WARNING, event.throwable(), 088 () -> "Problem invoking handler with " + event.event() + ": " 089 + event.message()); 090 event.stop(); 091 } 092 093 /** 094 * Start the application. 095 * 096 * @throws Exception the exception 097 */ 098 @SuppressWarnings("PMD.SignatureDeclareThrowsException") 099 public void start() throws Exception { 100 // This class represents the application and its instance is 101 // therefore the root of the component tree. 102 app = new ConsoleApp(); 103 // Attach a general nio dispatcher 104 app.attach(new NioDispatcher()); 105 106 // Create a channel for the network level I/O. 107 Channel httpTransport = new NamedChannel("httpTransport"); 108 109 // Create a TCP server listening on port 8888 that 110 // uses (i.e. receives events from and sends events to) 111 // the network channel. 112 app.attach(new SocketServer(httpTransport) 113 .setServerAddress(new InetSocketAddress(8888))); 114 115 // The third component that uses (i.e. receives events from 116 // and sends events to) the httpTransport channel is 117 // an SslCodec. This actually uses two channels. The second 118 // channel is used for the encrypted I/O which is exchanged 119 // with another SocketServer. 120 SSLContext sslContext = createSslContext(); 121 Channel securedNetwork = app.attach( 122 new SocketServer().setServerAddress(new InetSocketAddress(8443)) 123 .setBacklog(3000).setConnectionLimiter(new PermitsPool(50))); 124 app.attach(new SslCodec(httpTransport, securedNetwork, sslContext)); 125 126 // Create an HTTP server as converter between transport and 127 // application layer. It connects to the httpTransport channel 128 // and the application channel which is provided by the 129 // ConsoleApp instance. The HTTP related events are send on/ 130 // received from the http channel. 131 Channel httpApplication = new NamedChannel("http"); 132 var httpServer = app.attach(new HttpServer(httpApplication, 133 httpTransport, Request.In.Get.class, Request.In.Post.class)); 134 135 // Build application layer. The application layer events are 136 // exchanged over the application channel. 137 httpServer.attach( 138 new PreferencesStore(httpApplication, this.getClass())); 139 httpServer.attach(new InMemorySessionManager(httpApplication)); 140 httpServer.attach(new LanguageSelector(httpApplication)); 141 142 // Assemble console components 143 createVueJsConsole(httpServer); 144 Components.start(app); 145 } 146 147 private SSLContext createSslContext() throws KeyStoreException, IOException, 148 NoSuchAlgorithmException, CertificateException, 149 UnrecoverableKeyException, KeyManagementException { 150 KeyStore serverStore = KeyStore.getInstance("JKS"); 151 try (InputStream keyFile 152 = ConsoleApp.class.getResourceAsStream("localhost.jks")) { 153 serverStore.load(keyFile, "nopass".toCharArray()); 154 } 155 KeyManagerFactory kmf = KeyManagerFactory.getInstance( 156 KeyManagerFactory.getDefaultAlgorithm()); 157 kmf.init(serverStore, "nopass".toCharArray()); 158 SSLContext sslContext = SSLContext.getInstance("TLSv1.3"); 159 sslContext.init(kmf.getKeyManagers(), null, new SecureRandom()); 160 return sslContext; 161 } 162 163 @SuppressWarnings("PMD.TooFewBranchesForASwitchStatement") 164 private void createVueJsConsole(HttpServer httpServer) 165 throws URISyntaxException { 166 ConsoleWeblet consoleWeblet 167 = httpServer.attach(new VueJsConsoleWeblet(httpServer.channel(), 168 SELF, new URI("/"))) 169 .prependClassTemplateLoader(this.getClass()) 170 .prependResourceBundleProvider(ConsoleApp.class) 171 .prependConsoleResourceProvider(ConsoleApp.class); 172 WebConsole console = consoleWeblet.console(); 173 // consoleWeblet.setConnectionInactivityTimeout(300_000); 174 console.attach(new BrowserLocalBackedKVStore( 175 console.channel(), consoleWeblet.prefix().getPath())); 176 console.attach(new KVStoreBasedConsolePolicy(console.channel())); 177 // Add all available page resource providers 178 console.attach(new ComponentCollector<>( 179 PageResourceProviderFactory.class, console.channel())); 180 // Add all available conlets 181 console.attach(new ComponentCollector<>( 182 ConletComponentFactory.class, console.channel(), type -> { 183 switch (type) { 184 case "org.jgrapes.webconlet.examples.login.LoginConlet": 185 return Collections.emptyList(); 186 default: 187 return Arrays.asList(Collections.emptyMap()); 188 } 189 })); 190 } 191 192 /** 193 * Stop the application. 194 * @throws Exception 195 * 196 * @throws Exception the exception 197 */ 198 @SuppressWarnings("PMD.SignatureDeclareThrowsException") 199 public void stop() throws Exception { 200 app.fire(new Stop(), BROADCAST); 201 Components.awaitExhaustion(); 202 } 203 204 /** 205 * @param args 206 * @throws IOException 207 * @throws InterruptedException 208 * @throws NoSuchAlgorithmException 209 * @throws KeyStoreException 210 * @throws UnrecoverableKeyException 211 * @throws CertificateException 212 * @throws KeyManagementException 213 */ 214 @SuppressWarnings("PMD.SignatureDeclareThrowsException") 215 public static void main(String[] args) throws Exception { 216 new ConsoleApp().start(); 217 } 218 219}