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}