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