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.portlets.sysinfo;
020
021import freemarker.core.ParseException;
022import freemarker.template.MalformedTemplateNameException;
023import freemarker.template.Template;
024import freemarker.template.TemplateNotFoundException;
025
026import java.beans.ConstructorProperties;
027import java.io.IOException;
028import java.io.Serializable;
029import java.time.Duration;
030import java.util.Optional;
031import java.util.Properties;
032import java.util.ResourceBundle;
033import java.util.Set;
034
035import org.jgrapes.core.Channel;
036import org.jgrapes.core.Event;
037import org.jgrapes.core.Manager;
038import org.jgrapes.core.annotation.Handler;
039import org.jgrapes.http.Session;
040import org.jgrapes.portal.base.PortalSession;
041import org.jgrapes.portal.base.PortalUtils;
042import org.jgrapes.portal.base.Portlet.RenderMode;
043import org.jgrapes.portal.base.events.AddPageResources.ScriptResource;
044import org.jgrapes.portal.base.events.AddPortletRequest;
045import org.jgrapes.portal.base.events.AddPortletType;
046import org.jgrapes.portal.base.events.DeletePortlet;
047import org.jgrapes.portal.base.events.DeletePortletRequest;
048import org.jgrapes.portal.base.events.NotifyPortletModel;
049import org.jgrapes.portal.base.events.NotifyPortletView;
050import org.jgrapes.portal.base.events.PortalReady;
051import org.jgrapes.portal.base.events.RenderPortletRequest;
052import org.jgrapes.portal.base.events.RenderPortletRequestBase;
053import org.jgrapes.portal.base.freemarker.FreeMarkerPortlet;
054
055/**
056 * 
057 */
058public class SysInfoPortlet extends FreeMarkerPortlet {
059
060    private static final Set<RenderMode> MODES = RenderMode.asSet(
061        RenderMode.DeleteablePreview, RenderMode.View);
062
063    /**
064     * The periodically generated update event.
065     */
066    public static class Update extends Event<Void> {
067    }
068
069    /**
070     * Creates a new component with its channel set to the given channel.
071     * 
072     * @param componentChannel the channel that the component's handlers listen
073     *            on by default and that {@link Manager#fire(Event, Channel...)}
074     *            sends the event to
075     */
076    public SysInfoPortlet(Channel componentChannel) {
077        super(componentChannel, true);
078        setPeriodicRefresh(Duration.ofSeconds(1), () -> new Update());
079    }
080
081    /**
082     * On {@link PortalReady}, fire the {@link AddPortletType}.
083     *
084     * @param event the event
085     * @param portalSession the portal session
086     * @throws TemplateNotFoundException the template not found exception
087     * @throws MalformedTemplateNameException the malformed template name
088     *             exception
089     * @throws ParseException the parse exception
090     * @throws IOException Signals that an I/O exception has occurred.
091     */
092    @Handler
093    public void onPortalReady(PortalReady event, PortalSession portalSession)
094            throws TemplateNotFoundException, MalformedTemplateNameException,
095            ParseException, IOException {
096        ResourceBundle resourceBundle = resourceBundle(portalSession.locale());
097        // Add SysInfoPortlet resources to page
098        portalSession.respond(new AddPortletType(type())
099            .setDisplayName(resourceBundle.getString("portletName"))
100            .addScript(new ScriptResource()
101                .setRequires(new String[] { "chartjs.org" })
102                .setScriptUri(event.renderSupport().portletResource(
103                    type(), "SysInfo-functions.ftl.js")))
104            .addCss(event.renderSupport(), PortalUtils.uriFromPath(
105                "SysInfo-style.css")));
106    }
107
108    /*
109     * (non-Javadoc)
110     * 
111     * @see org.jgrapes.portal.AbstractPortlet#generatePortletId()
112     */
113    @Override
114    protected String generatePortletId() {
115        return type() + "-" + super.generatePortletId();
116    }
117
118    /*
119     * (non-Javadoc)
120     * 
121     * @see org.jgrapes.portal.AbstractPortlet#modelFromSession
122     */
123    @SuppressWarnings("unchecked")
124    @Override
125    protected <T extends Serializable> Optional<T> stateFromSession(
126            Session session, String portletId, Class<T> type) {
127        if (portletId.startsWith(type() + "-")) {
128            return Optional.of((T) new SysInfoModel(portletId));
129        }
130        return Optional.empty();
131    }
132
133    @Override
134    public String doAddPortlet(AddPortletRequest event,
135            PortalSession portalSession) throws Exception {
136        String portletId = generatePortletId();
137        SysInfoModel portletModel = putInSession(
138            portalSession.browserSession(), new SysInfoModel(portletId));
139        renderPortlet(event, portalSession, portletModel);
140        return portletId;
141    }
142
143    /*
144     * (non-Javadoc)
145     * 
146     * @see org.jgrapes.portal.AbstractPortlet#doRenderPortlet
147     */
148    @Override
149    protected void doRenderPortlet(RenderPortletRequest event,
150            PortalSession portalSession, String portletId,
151            Serializable retrievedState) throws Exception {
152        SysInfoModel portletModel = (SysInfoModel) retrievedState;
153        renderPortlet(event, portalSession, portletModel);
154    }
155
156    @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
157    private void renderPortlet(RenderPortletRequestBase<?> event,
158            PortalSession portalSession, SysInfoModel portletModel)
159            throws TemplateNotFoundException, MalformedTemplateNameException,
160            ParseException, IOException {
161        switch (event.preferredRenderMode()) {
162        case Preview:
163        case DeleteablePreview: {
164            Template tpl
165                = freemarkerConfig().getTemplate("SysInfo-preview.ftl.html");
166            portalSession.respond(new RenderPortletFromTemplate(event,
167                SysInfoPortlet.class, portletModel.getPortletId(),
168                tpl, fmModel(event, portalSession, portletModel))
169                    .setRenderMode(RenderMode.DeleteablePreview)
170                    .setSupportedModes(MODES)
171                    .setForeground(event.isForeground()));
172            updateView(portalSession, portletModel.getPortletId());
173            break;
174        }
175        case View: {
176            Template tpl
177                = freemarkerConfig().getTemplate("SysInfo-view.ftl.html");
178            portalSession.respond(new RenderPortletFromTemplate(event,
179                SysInfoPortlet.class, portletModel.getPortletId(),
180                tpl, fmModel(event, portalSession, portletModel))
181                    .setSupportedModes(MODES)
182                    .setForeground(event.isForeground()));
183            break;
184        }
185        default:
186            break;
187        }
188    }
189
190    private void updateView(PortalSession portalSession, String portletId) {
191        if (!portalSession.isConnected()) {
192            return;
193        }
194        Runtime runtime = Runtime.getRuntime();
195        portalSession.respond(new NotifyPortletView(type(),
196            portletId, "updateMemorySizes",
197            System.currentTimeMillis(), runtime.maxMemory(),
198            runtime.totalMemory(),
199            runtime.totalMemory() - runtime.freeMemory()));
200    }
201
202    /*
203     * (non-Javadoc)
204     * 
205     * @see org.jgrapes.portal.AbstractPortlet#doDeletePortlet
206     */
207    @Override
208    protected void doDeletePortlet(DeletePortletRequest event,
209            PortalSession portalSession, String portletId,
210            Serializable retrievedState) throws Exception {
211        portalSession.respond(new DeletePortlet(portletId));
212    }
213
214    /**
215     * Handle the periodic update event by sending {@link NotifyPortletView}
216     * events.
217     *
218     * @param event the event
219     * @param portalSession the portal session
220     */
221    @Handler
222    public void onUpdate(Update event, PortalSession portalSession) {
223        for (String portletId : portletIds(portalSession)) {
224            updateView(portalSession, portletId);
225        }
226    }
227
228    @Override
229    @SuppressWarnings("PMD.DoNotCallGarbageCollectionExplicitly")
230    protected void doNotifyPortletModel(NotifyPortletModel event,
231            PortalSession portalSession, Serializable portletState)
232            throws Exception {
233        event.stop();
234        System.gc();
235        for (String portletId : portletIds(portalSession)) {
236            updateView(portalSession, portletId);
237        }
238    }
239
240    /**
241     * The portlet's model.
242     */
243    @SuppressWarnings("serial")
244    public static class SysInfoModel extends PortletBaseModel {
245
246        /**
247         * Creates a new model with the given type and id.
248         * 
249         * @param portletId the portlet id
250         */
251        @ConstructorProperties({ "portletId" })
252        public SysInfoModel(String portletId) {
253            super(portletId);
254        }
255
256        /**
257         * Return the system properties.
258         *
259         * @return the properties
260         */
261        public Properties systemProperties() {
262            return System.getProperties();
263        }
264
265        /**
266         * Return the {@link Runtime}.
267         *
268         * @return the runtime
269         */
270        public Runtime runtime() {
271            return Runtime.getRuntime();
272        }
273    }
274
275}