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        "PMD.TooFewBranchesForSwitch" })
165    private void createVueJsConsole(HttpServer httpServer)
166            throws URISyntaxException {
167        ConsoleWeblet consoleWeblet
168            = httpServer.attach(new VueJsConsoleWeblet(httpServer.channel(),
169                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(), 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}