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.KeyStore;
027import java.security.KeyStoreException;
028import java.security.NoSuchAlgorithmException;
029import java.security.SecureRandom;
030import java.security.UnrecoverableKeyException;
031import java.security.cert.CertificateException;
032import java.util.Arrays;
033import java.util.Collections;
034import java.util.Map;
035import javax.net.ssl.KeyManagerFactory;
036import javax.net.ssl.SSLContext;
037import org.jgrapes.core.Channel;
038import org.jgrapes.core.ClassChannel;
039import org.jgrapes.core.Component;
040import org.jgrapes.core.Components;
041import org.jgrapes.core.NamedChannel;
042import org.jgrapes.core.events.Stop;
043import org.jgrapes.http.HttpServer;
044import org.jgrapes.http.InMemorySessionManager;
045import org.jgrapes.http.LanguageSelector;
046import org.jgrapes.http.events.Request;
047import org.jgrapes.io.NioDispatcher;
048import org.jgrapes.io.util.PermitsPool;
049import org.jgrapes.net.SslCodec;
050import org.jgrapes.net.TcpServer;
051import org.jgrapes.util.ComponentCollector;
052import org.jgrapes.util.PreferencesStore;
053import org.jgrapes.webconsole.base.BrowserLocalBackedKVStore;
054import org.jgrapes.webconsole.base.ConletComponentFactory;
055import org.jgrapes.webconsole.base.ConsoleWeblet;
056import org.jgrapes.webconsole.base.KVStoreBasedConsolePolicy;
057import org.jgrapes.webconsole.base.PageResourceProviderFactory;
058import org.jgrapes.webconsole.base.WebConsole;
059import org.jgrapes.webconsole.vuejs.VueJsConsoleWeblet;
060
061/**
062 *
063 */
064public class ConsoleApp extends Component {
065
066    private ConsoleApp app;
067
068    /**
069     * Instantiates a new http(s) web console demo.
070     */
071    public ConsoleApp() {
072        super(new ClassChannel() {
073        });
074    }
075
076    /**
077     * Start the application.
078     *
079     * @throws Exception the exception
080     */
081    @SuppressWarnings("PMD.SignatureDeclareThrowsException")
082    public void start() throws Exception {
083        // The demo component is the application
084        app = new ConsoleApp();
085        // Attach a general nio dispatcher
086        app.attach(new NioDispatcher());
087
088        // Network level unencrypted channel.
089        Channel httpTransport = new NamedChannel("httpTransport");
090        // Create a TCP server listening on port 8888
091        app.attach(new TcpServer(httpTransport)
092            .setServerAddress(new InetSocketAddress(8888)));
093
094        // Create TLS "converter"
095        KeyStore serverStore = KeyStore.getInstance("JKS");
096        try (InputStream keyFile
097            = ConsoleApp.class.getResourceAsStream("localhost.jks")) {
098            serverStore.load(keyFile, "nopass".toCharArray());
099        }
100        KeyManagerFactory kmf = KeyManagerFactory.getInstance(
101            KeyManagerFactory.getDefaultAlgorithm());
102        kmf.init(serverStore, "nopass".toCharArray());
103        SSLContext sslContext = SSLContext.getInstance("TLS");
104        sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
105        // Create a TCP server for SSL
106        Channel securedNetwork = app.attach(
107            new TcpServer().setServerAddress(new InetSocketAddress(8443))
108                .setBacklog(3000).setConnectionLimiter(new PermitsPool(50)));
109        app.attach(new SslCodec(httpTransport, securedNetwork, sslContext));
110
111        // Create an HTTP server as converter between transport and application
112        // layer.
113        app.attach(new HttpServer(app.channel(),
114            httpTransport, Request.In.Get.class, Request.In.Post.class));
115
116        // Build application layer
117        app.attach(new PreferencesStore(app.channel(), this.getClass()));
118        app.attach(new InMemorySessionManager(app.channel()));
119        app.attach(new LanguageSelector(app.channel()));
120
121        // Assemble console components
122        createVueJsConsole();
123        Components.start(app);
124    }
125
126    @SuppressWarnings("PMD.TooFewBranchesForASwitchStatement")
127    private void createVueJsConsole() throws URISyntaxException {
128        ConsoleWeblet consoleWeblet
129            = app.attach(new VueJsConsoleWeblet(app.channel(), Channel.SELF,
130                new URI("/")))
131                .prependClassTemplateLoader(this.getClass())
132                .prependResourceBundleProvider(ConsoleApp.class)
133                .prependConsoleResourceProvider(ConsoleApp.class);
134        WebConsole console = consoleWeblet.console();
135        // consoleWeblet.setConnectionInactivityTimeout(300_000);
136        console.attach(new BrowserLocalBackedKVStore(
137            console.channel(), consoleWeblet.prefix().getPath()));
138        console.attach(new KVStoreBasedConsolePolicy(console.channel()));
139        // Add all available page resource providers
140        console.attach(new ComponentCollector<>(
141            PageResourceProviderFactory.class, console.channel(), type -> {
142                switch (type) {
143                case "org.jgrapes.webconsole.provider.gridstack.GridstackProvider":
144                    return Arrays.asList(
145                        Map.of("requireTouchPunch", true,
146                            "configuration", "CoreWithJQUiPlugin"));
147                default:
148                    return Arrays.asList(Collections.emptyMap());
149                }
150            }));
151        // Add all available conlets
152        console.attach(new ComponentCollector<>(
153            ConletComponentFactory.class, console.channel(), type -> {
154                switch (type) {
155                case "org.jgrapes.webconlet.examples.login.LoginConlet":
156                    return Collections.emptyList();
157                default:
158                    return Arrays.asList(Collections.emptyMap());
159                }
160            }));
161    }
162
163    /**
164     * Stop the application.
165     * @throws Exception 
166     *
167     * @throws Exception the exception
168     */
169    @SuppressWarnings("PMD.SignatureDeclareThrowsException")
170    public void stop() throws Exception {
171        app.fire(new Stop(), Channel.BROADCAST);
172        Components.awaitExhaustion();
173    }
174
175    /**
176     * @param args
177     * @throws IOException
178     * @throws InterruptedException
179     * @throws NoSuchAlgorithmException
180     * @throws KeyStoreException
181     * @throws UnrecoverableKeyException
182     * @throws CertificateException
183     * @throws KeyManagementException
184     */
185    @SuppressWarnings("PMD.SignatureDeclareThrowsException")
186    public static void main(String[] args) throws Exception {
187        new ConsoleApp().start();
188    }
189
190}