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                Channel.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(), Channel.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}