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}