/** * * Copyright (c) 2009-2016 Freedomotic team http://freedomotic.com * * This file is part of Freedomotic * * This Program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * This Program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * Freedomotic; see the file COPYING. If not, see * <http://www.gnu.org/licenses/>. */ package com.freedomotic.api; import com.freedomotic.exceptions.PluginShutdownException; import com.freedomotic.exceptions.PluginStartupException; import com.freedomotic.app.ConfigPersistence; import com.freedomotic.app.Freedomotic; import com.freedomotic.bus.BusConsumer; import com.freedomotic.bus.BusMessagesListener; import com.freedomotic.bus.BusService; import com.freedomotic.events.MessageEvent; import com.freedomotic.events.PluginHasChanged; import com.freedomotic.events.PluginHasChanged.PluginActions; import com.freedomotic.model.ds.Config; import com.freedomotic.util.EqualsUtil; import com.freedomotic.settings.Info; import com.google.inject.Inject; import java.io.File; import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jms.ObjectMessage; import javax.swing.JFrame; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; /** * * @author Enrico Nicoletti */ @XmlRootElement @XmlAccessorType(XmlAccessType.NONE) public class Plugin implements Client, BusConsumer { private static final String ACTUATORS_QUEUE_DOMAIN = "app.actuators."; private static final Logger LOG = LoggerFactory.getLogger(Plugin.class.getName()); @XmlElement private String pluginName; @XmlElement private static final String type = "Plugin"; @XmlElement private volatile PluginStatus currentPluginStatus = PluginStatus.STOPPED; @XmlElement public Config configuration; @Deprecated protected JFrame gui; @XmlElement protected String description; @XmlElement protected String version; @XmlElement protected String requiredVersion; @XmlElement protected String category; @XmlElement protected String shortName; @XmlElement protected String listenOn; @XmlElement protected String sendOn; @XmlElement private File path; protected BusMessagesListener listener; @Inject private API api; @Inject private BusService busService; /** * * @param pluginName the plugin name * @param manifestPath the path of the plugin configuration file */ public Plugin(String pluginName, String manifestPath) { Freedomotic.INJECTOR.injectMembers(this); setName(pluginName); path = new File(Info.PATHS.PATH_DEVICES_FOLDER + manifestPath); init(); } /** * Used to create a Plugin placeholder for things. * * @param pluginName the plugin name */ public Plugin(String pluginName) { Freedomotic.INJECTOR.injectMembers(this); setName(pluginName); init(); } /** * Used by JoinPlugin to instantiate a new plugin. * * @param pluginName the plugin name * @param manifest the manifest config file */ public Plugin(String pluginName, Config manifest) { Freedomotic.INJECTOR.injectMembers(this); setName(pluginName); init(); } /** * Returns the path of the plugin configuration file. * * @return the path of the plugin configuration file */ public File getFile() { return path; } /** * * @return */ public API getApi() { return api; } /** * * @throws com.freedomotic.exceptions.PluginStartupException */ protected void onStart() throws PluginStartupException { } /** * * @throws com.freedomotic.exceptions.PluginShutdownException */ protected void onStop() throws PluginShutdownException { } /** * Sets the plugin description. * * @param description */ @Override public final void setDescription(String description) { if (!getDescription().equalsIgnoreCase(description)) { this.description = description; try { PluginHasChanged event = new PluginHasChanged(this, this.getName(), PluginActions.DESCRIPTION); busService.send(event); } catch (Exception e) { LOG.warn("Cannot notify new plugin description for \"" + getName() + "\"", e); } } } /** * Notifies an error on console/logfile and shows a callout on the * jfrontend. * * @param message the error message */ public void notifyError(String message) { //Log the error on console/logfiles LOG.warn(message); //write something on the GUI MessageEvent callout = new MessageEvent(this, message); callout.setType("callout"); //display as callout on frontends callout.setLevel("warning"); callout.setExpiration(10 * 1000);//message lasts 10 seconds busService.send(callout); } /** * Notifies a critical error on console/logfile, shows a callout on the * jfrontend and stops the plugin. * * @param message the error message */ public void notifyCriticalError(String message) { //Log the error on console/logfiles LOG.warn(message); //write something on the GUI MessageEvent callout = new MessageEvent(this, message); callout.setType("callout"); //display as callout on frontends callout.setLevel("warning"); callout.setExpiration(10 * 1000);//message lasts 10 seconds busService.send(callout); //stop this plugin stop(); //override plugin description setDescription(message); //plugin is now set as STOPPED, but should be marked as FAILED currentPluginStatus = PluginStatus.FAILED; } /** * Notifies a critical error on the console/logfile and keeps a stack trace. * * @param message the error message * @param ex the raised exception */ protected void notifyCriticalError(String message, Exception ex) { //Log and keep stack trace LOG.error(message, ex); notifyCriticalError(message); } /** * Returns the plugin description. * * @return the plugin description */ @Override public String getDescription() { if (description == null) { return getName(); } else { return description; } } /** * * @return */ @Override public final Config getConfiguration() { return configuration; } /** * Returns the read queue. * * @return queue the plugin listen to */ public final String getReadQueue() { return listenOn; } /** * Returns the plugin category. * * @return the plugin category */ public final String getCategory() { return category; } /** * Returns the required version for the plugin. * * @return the required version */ public String getRequiredVersion() { return requiredVersion; } /** * Returns the plugin version. * * @return the plugin version */ public String getVersion() { return version; } /** * * @param window */ public void bindGuiToPlugin(JFrame window) { gui = window; gui.setVisible(false); gui.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); } /** * */ @Override @Deprecated public void showGui() { if (!isRunning()) { start(); } onShowGui(); if (gui != null) { gui.setVisible(true); } else { LOG.warn("ERROR: plugin gui is null"); } } /** * */ @Override @Deprecated public void hideGui() { onHideGui(); if (gui != null) { gui.setVisible(false); } } /** * Returns the plugin name. * * @return the plugin name */ @Override public String getName() { return pluginName; } /** * Returns the current plugin status. * * @return the current plugin status */ public String getStatus() { return currentPluginStatus.name(); } /** * Returns the plugin type. * * @return the plugin type */ @Override public String getType() { return type; } /** * Checks if the plugin status is 'RUNNING'. * * @return true if the plugin status is 'RUNNING', false otherwise */ @Override public boolean isRunning() { return currentPluginStatus.equals(PluginStatus.RUNNING); } /** * Checks if the plugin status is 'STARTING' or 'RUNNING'. * * @return true if the plugin status is 'STARTING' or 'RUNNING', false * otherwise */ public boolean isAllowedToSend() { return currentPluginStatus.equals(PluginStatus.STARTING) || currentPluginStatus.equals(PluginStatus.RUNNING); } /** * Checks if the plugin can start. * * @return true if the plugin can start, false otherwise */ public boolean isAllowedToStart() { return PluginStatus.isAllowedToStart(currentPluginStatus); } /** * Gets the plugin class name. * * @return the plugin class name */ @XmlElement(name = "uuid") public String getClassName() { return this.getClass().getSimpleName().toLowerCase(); } /** * Sets the plugin name. * * @param name the new plugin name */ @Override public final void setName(String name) { pluginName = name; } /** * Sets the plugin status. * * @param newStatus the new plugin status */ protected final void setStatus(PluginStatus newStatus) { currentPluginStatus = newStatus; } /** * * @param aThat * @return */ @Override public boolean equals(Object aThat) { //check for self-comparison if (this == aThat) { return true; } //use instanceof instead of getClass here for two reasons //1. if need be, it can match any supertype, and not just one class; //2. it renders an explict check for "that == null" redundant, since //it does the check for null already - "null instanceof [type]" always //returns false. (See Effective Java by Joshua Bloch.) if (!(aThat instanceof Plugin)) { return false; } //Alternative to the above line : //if ( aThat == null || aThat.getClass() != this.getClass() ) return false; //cast to native object is now safe Plugin that = (Plugin) aThat; //now a proper field-by-field evaluation can be made return EqualsUtil.areEqual(this.getName().toLowerCase(), that.getName().toLowerCase()); } /** * Calculates an hash code. * * @return an hash code */ @Override public int hashCode() { int hash = 5; hash = (53 * hash) + ((this.pluginName != null) ? this.pluginName.hashCode() : 0); return hash; } private void init() { if (configuration == null) { //try to load it from file deserializeManifest(path); } description = configuration.getStringProperty("description", "Missing plugin manifest"); setDescription(description); category = configuration.getStringProperty("category", "undefined"); shortName = configuration.getStringProperty("short-name", "undefined"); listenOn = configuration.getStringProperty("listen-on", "undefined"); sendOn = configuration.getStringProperty("send-on", "undefined"); register(); } /** * Deserializes the xml configuration file. * * @param manifest the manifest configuration file */ private void deserializeManifest(File manifest) { try { configuration = ConfigPersistence.deserialize(manifest); } catch (IOException ex) { LOG.error("Missing manifest \"{}\" for plugin \"{}\"", new Object[]{manifest.toString(), getName()}); setDescription("Missing manifest file " + manifest.toString()); } } /** * * */ private void register() { listener = new BusMessagesListener(this, getBusService()); listener.consumeCommandFrom(getCommandsChannelToListen()); } /** * * * @return */ private String getCommandsChannelToListen() { String defaultQueue = ACTUATORS_QUEUE_DOMAIN + category + "." + shortName; String customizedQueue = ACTUATORS_QUEUE_DOMAIN + listenOn; if (getReadQueue().equalsIgnoreCase("undefined")) { listenOn = defaultQueue + ".in"; return listenOn; } else { return customizedQueue; } } /** * */ @Deprecated protected void onShowGui() { } /** * */ @Deprecated protected void onHideGui() { } /** * */ @Override public void start() { //do not add code here } /** * */ @Override public void stop() { //do not add code here } /** * * @return the plugin name */ @Override public String toString() { return getName(); } /** * */ public void loadPermissionsFromManifest() { getApi().getAuth().setPluginPrivileges(this, configuration.getStringProperty("permissions", getApi().getAuth().getPluginDefaultPermission())); } public BusService getBusService() { return busService; } @Override public void onMessage(ObjectMessage message) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } /** * Destroyies the messaging channel. * */ @Override public void destroy() { // Destroy the messaging channel listener.destroy(); stop(); } }