001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2017-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.webconlet.sysinfo;
020
021import freemarker.core.ParseException;
022import freemarker.template.MalformedTemplateNameException;
023import freemarker.template.Template;
024import freemarker.template.TemplateNotFoundException;
025import java.beans.ConstructorProperties;
026import java.io.IOException;
027import java.time.Duration;
028import java.util.HashSet;
029import java.util.Optional;
030import java.util.Properties;
031import java.util.Set;
032import org.jgrapes.core.Channel;
033import org.jgrapes.core.Event;
034import org.jgrapes.core.Manager;
035import org.jgrapes.core.annotation.Handler;
036import org.jgrapes.http.Session;
037import org.jgrapes.webconsole.base.AbstractConlet;
038import org.jgrapes.webconsole.base.Conlet.RenderMode;
039import org.jgrapes.webconsole.base.ConsoleSession;
040import org.jgrapes.webconsole.base.WebConsoleUtils;
041import org.jgrapes.webconsole.base.events.AddConletRequest;
042import org.jgrapes.webconsole.base.events.AddConletType;
043import org.jgrapes.webconsole.base.events.AddPageResources.ScriptResource;
044import org.jgrapes.webconsole.base.events.ConsoleReady;
045import org.jgrapes.webconsole.base.events.NotifyConletModel;
046import org.jgrapes.webconsole.base.events.NotifyConletView;
047import org.jgrapes.webconsole.base.events.RenderConletRequest;
048import org.jgrapes.webconsole.base.events.RenderConletRequestBase;
049import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet;
050
051/**
052 * 
053 */
054public class SysInfoConlet
055        extends FreeMarkerConlet<SysInfoConlet.SysInfoModel> {
056
057    private static final Set<RenderMode> MODES = RenderMode.asSet(
058        RenderMode.Preview, RenderMode.View);
059
060    /**
061     * The periodically generated update event.
062     */
063    public static class Update extends Event<Void> {
064    }
065
066    /**
067     * Creates a new component with its channel set to the given channel.
068     * 
069     * @param componentChannel the channel that the component's handlers listen
070     *            on by default and that {@link Manager#fire(Event, Channel...)}
071     *            sends the event to
072     */
073    public SysInfoConlet(Channel componentChannel) {
074        super(componentChannel);
075        setPeriodicRefresh(Duration.ofSeconds(1), () -> new Update());
076    }
077
078    /**
079     * On {@link ConsoleReady}, fire the {@link AddConletType}.
080     *
081     * @param event the event
082     * @param consoleSession the console session
083     * @throws TemplateNotFoundException the template not found exception
084     * @throws MalformedTemplateNameException the malformed template name
085     *             exception
086     * @throws ParseException the parse exception
087     * @throws IOException Signals that an I/O exception has occurred.
088     */
089    @Handler
090    public void onConsoleReady(ConsoleReady event,
091            ConsoleSession consoleSession)
092            throws TemplateNotFoundException, MalformedTemplateNameException,
093            ParseException, IOException {
094        // Add SysInfoConlet resources to page
095        consoleSession.respond(new AddConletType(type())
096            .setDisplayNames(
097                localizations(consoleSession.supportedLocales(), "conletName"))
098            .addScript(new ScriptResource()
099                .setRequires("chart.js")
100                .setScriptUri(event.renderSupport().conletResource(
101                    type(), "SysInfo-functions.ftl.js")))
102            .addCss(event.renderSupport(), WebConsoleUtils.uriFromPath(
103                "SysInfo-style.css")));
104    }
105
106    @Override
107    protected String generateConletId() {
108        return type() + "-" + super.generateConletId();
109    }
110
111    @Override
112    protected Optional<SysInfoModel> stateFromSession(
113            Session session, String conletId) {
114        if (conletId.startsWith(type() + "-")) {
115            return Optional.of(new SysInfoModel(conletId));
116        }
117        return Optional.empty();
118    }
119
120    @Override
121    public ConletTrackingInfo doAddConlet(AddConletRequest event,
122            ConsoleSession consoleSession) throws Exception {
123        String conletId = generateConletId();
124        SysInfoModel conletModel = putInSession(
125            consoleSession.browserSession(), new SysInfoModel(conletId));
126        return new ConletTrackingInfo(conletId)
127            .addModes(renderConlet(event, consoleSession, conletModel));
128    }
129
130    @Override
131    protected Set<RenderMode> doRenderConlet(RenderConletRequest event,
132            ConsoleSession consoleSession, String conletId,
133            SysInfoModel conletModel) throws Exception {
134        return renderConlet(event, consoleSession, conletModel);
135    }
136
137    @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
138    private Set<RenderMode> renderConlet(RenderConletRequestBase<?> event,
139            ConsoleSession consoleSession, SysInfoModel conletModel)
140            throws TemplateNotFoundException, MalformedTemplateNameException,
141            ParseException, IOException {
142        Set<RenderMode> renderedAs = new HashSet<>();
143        if (event.renderAs().contains(RenderMode.Preview)) {
144            Template tpl
145                = freemarkerConfig().getTemplate("SysInfo-preview.ftl.html");
146            consoleSession.respond(new RenderConletFromTemplate(event,
147                type(), conletModel.getConletId(), tpl,
148                fmModel(event, consoleSession, conletModel))
149                    .setRenderAs(
150                        RenderMode.Preview.addModifiers(event.renderAs()))
151                    .setSupportedModes(MODES));
152            renderedAs.add(RenderMode.Preview);
153        }
154        if (event.renderAs().contains(RenderMode.View)) {
155            Template tpl
156                = freemarkerConfig().getTemplate("SysInfo-view.ftl.html");
157            consoleSession.respond(new RenderConletFromTemplate(event,
158                type(), conletModel.getConletId(), tpl,
159                fmModel(event, consoleSession, conletModel))
160                    .setRenderAs(RenderMode.View.addModifiers(event.renderAs()))
161                    .setSupportedModes(MODES));
162            renderedAs.add(RenderMode.Preview);
163        }
164        if (renderedAs.size() > 0) {
165            updateView(consoleSession, conletModel.getConletId());
166        }
167        return renderedAs;
168    }
169
170    private void updateView(ConsoleSession consoleSession, String conletId) {
171        if (!consoleSession.isConnected()) {
172            return;
173        }
174        Runtime runtime = Runtime.getRuntime();
175        consoleSession.respond(new NotifyConletView(type(),
176            conletId, "updateMemorySizes",
177            System.currentTimeMillis(), runtime.maxMemory(),
178            runtime.totalMemory(),
179            runtime.totalMemory() - runtime.freeMemory()));
180    }
181
182    /**
183     * Handle the periodic update event by sending {@link NotifyConletView}
184     * events.
185     *
186     * @param event the event
187     * @param consoleSession the console session
188     */
189    @Handler
190    public void onUpdate(Update event, ConsoleSession consoleSession) {
191        for (String conletId : conletIds(consoleSession)) {
192            updateView(consoleSession, conletId);
193        }
194    }
195
196    @Override
197    @SuppressWarnings("PMD.DoNotCallGarbageCollectionExplicitly")
198    protected void doNotifyConletModel(NotifyConletModel event,
199            ConsoleSession consoleSession, SysInfoModel conletState)
200            throws Exception {
201        event.stop();
202        System.gc();
203        for (String conletId : conletIds(consoleSession)) {
204            updateView(consoleSession, conletId);
205        }
206    }
207
208    /**
209     * The conlet's model.
210     */
211    @SuppressWarnings("serial")
212    public static class SysInfoModel
213            extends AbstractConlet.ConletBaseModel {
214
215        /**
216         * Creates a new model with the given type and id.
217         * 
218         * @param conletId the web console component id
219         */
220        @ConstructorProperties({ "conletId" })
221        public SysInfoModel(String conletId) {
222            super(conletId);
223        }
224
225        /**
226         * Return the system properties.
227         *
228         * @return the properties
229         */
230        public Properties systemProperties() {
231            return System.getProperties();
232        }
233
234        /**
235         * Return the {@link Runtime}.
236         *
237         * @return the runtime
238         */
239        public Runtime runtime() {
240            return Runtime.getRuntime();
241        }
242    }
243
244}