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            .setInstantiable());
107    }
108
109    /*
110     * (non-Javadoc)
111     * 
112     * @see org.jgrapes.portal.AbstractPortlet#generatePortletId()
113     */
114    @Override
115    protected String generatePortletId() {
116        return type() + "-" + super.generatePortletId();
117    }
118
119    /*
120     * (non-Javadoc)
121     * 
122     * @see org.jgrapes.portal.AbstractPortlet#modelFromSession
123     */
124    @SuppressWarnings("unchecked")
125    @Override
126    protected <T extends Serializable> Optional<T> stateFromSession(
127            Session session, String portletId, Class<T> type) {
128        if (portletId.startsWith(type() + "-")) {
129            return Optional.of((T) new SysInfoModel(portletId));
130        }
131        return Optional.empty();
132    }
133
134    @Override
135    public String doAddPortlet(AddPortletRequest event,
136            PortalSession portalSession) throws Exception {
137        String portletId = generatePortletId();
138        SysInfoModel portletModel = putInSession(
139            portalSession.browserSession(), new SysInfoModel(portletId));
140        renderPortlet(event, portalSession, portletModel);
141        return portletId;
142    }
143
144    /*
145     * (non-Javadoc)
146     * 
147     * @see org.jgrapes.portal.AbstractPortlet#doRenderPortlet
148     */
149    @Override
150    protected void doRenderPortlet(RenderPortletRequest event,
151            PortalSession portalSession, String portletId,
152            Serializable retrievedState) throws Exception {
153        SysInfoModel portletModel = (SysInfoModel) retrievedState;
154        renderPortlet(event, portalSession, portletModel);
155    }
156
157    @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
158    private void renderPortlet(RenderPortletRequestBase<?> event,
159            PortalSession portalSession, SysInfoModel portletModel)
160            throws TemplateNotFoundException, MalformedTemplateNameException,
161            ParseException, IOException {
162        switch (event.renderMode()) {
163        case Preview:
164        case DeleteablePreview: {
165            Template tpl
166                = freemarkerConfig().getTemplate("SysInfo-preview.ftl.html");
167            portalSession.respond(new RenderPortletFromTemplate(event,
168                SysInfoPortlet.class, portletModel.getPortletId(),
169                tpl, fmModel(event, portalSession, portletModel))
170                    .setRenderMode(RenderMode.DeleteablePreview)
171                    .setSupportedModes(MODES)
172                    .setForeground(event.isForeground()));
173            updateView(portalSession, portletModel.getPortletId());
174            break;
175        }
176        case View: {
177            Template tpl
178                = freemarkerConfig().getTemplate("SysInfo-view.ftl.html");
179            portalSession.respond(new RenderPortletFromTemplate(event,
180                SysInfoPortlet.class, portletModel.getPortletId(),
181                tpl, fmModel(event, portalSession, portletModel))
182                    .setSupportedModes(MODES)
183                    .setForeground(event.isForeground()));
184            break;
185        }
186        default:
187            break;
188        }
189    }
190
191    private void updateView(PortalSession portalSession, String portletId) {
192        if (!portalSession.isConnected()) {
193            return;
194        }
195        Runtime runtime = Runtime.getRuntime();
196        portalSession.respond(new NotifyPortletView(type(),
197            portletId, "updateMemorySizes",
198            System.currentTimeMillis(), runtime.maxMemory(),
199            runtime.totalMemory(),
200            runtime.totalMemory() - runtime.freeMemory()));
201    }
202
203    /*
204     * (non-Javadoc)
205     * 
206     * @see org.jgrapes.portal.AbstractPortlet#doDeletePortlet
207     */
208    @Override
209    protected void doDeletePortlet(DeletePortletRequest event,
210            PortalSession portalSession, String portletId,
211            Serializable retrievedState) throws Exception {
212        portalSession.respond(new DeletePortlet(portletId));
213    }
214
215    /**
216     * Handle the periodic update event by sending {@link NotifyPortletView}
217     * events.
218     *
219     * @param event the event
220     * @param portalSession the portal session
221     */
222    @Handler
223    public void onUpdate(Update event, PortalSession portalSession) {
224        for (String portletId : portletIds(portalSession)) {
225            updateView(portalSession, portletId);
226        }
227    }
228
229    @Override
230    @SuppressWarnings("PMD.DoNotCallGarbageCollectionExplicitly")
231    protected void doNotifyPortletModel(NotifyPortletModel event,
232            PortalSession portalSession, Serializable portletState)
233            throws Exception {
234        event.stop();
235        System.gc();
236        for (String portletId : portletIds(portalSession)) {
237            updateView(portalSession, portletId);
238        }
239    }
240
241    /**
242     * The portlet's model.
243     */
244    @SuppressWarnings("serial")
245    public static class SysInfoModel extends PortletBaseModel {
246
247        /**
248         * Creates a new model with the given type and id.
249         * 
250         * @param portletId the portlet id
251         */
252        @ConstructorProperties({ "portletId" })
253        public SysInfoModel(String portletId) {
254            super(portletId);
255        }
256
257        /**
258         * Return the system properties.
259         *
260         * @return the properties
261         */
262        public Properties systemProperties() {
263            return System.getProperties();
264        }
265
266        /**
267         * Return the {@link Runtime}.
268         *
269         * @return the runtime
270         */
271        public Runtime runtime() {
272            return Runtime.getRuntime();
273        }
274    }
275
276}