/** * * 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.plugins.impl; import com.freedomotic.api.Client; import com.freedomotic.api.Plugin; import com.freedomotic.bus.BusService; import com.freedomotic.events.PluginHasChanged; import com.freedomotic.events.PluginHasChanged.PluginActions; import com.freedomotic.exceptions.RepositoryException; import com.freedomotic.model.ds.Config; import com.freedomotic.things.ThingRepository; import com.freedomotic.plugins.ClientStorage; import com.freedomotic.plugins.ObjectPluginPlaceholder; import com.freedomotic.settings.Info; import com.google.inject.Inject; import com.google.inject.Injector; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A storage of added plugins and connected clients */ class ClientStorageInMemory implements ClientStorage { private static final Logger LOG = LoggerFactory.getLogger(ClientStorageInMemory.class.getName()); private static final List<Client> clients = new ArrayList<>(); //It works because this class is created by guice and the injector is automatically available in this case @Inject private Injector injector; @Inject private ThingRepository thingsRepository; @Inject private BusService busService; ClientStorageInMemory() { //just injected } /** * * @param c */ @Override public void add(Client c) { if (!clients.contains(c)) { if (isCompatible(c)) { clients.add(c); } else { Client client = createPluginPlaceholder(c.getName(), "Plugin", "Not compatible with this framework version v" + Info.getVersion()); clients.add(client); LOG.warn("Plugin \"{}\" is not compatible with this framework version v{}", new Object[]{c.getName(), Info.getVersion()}); } PluginHasChanged event = new PluginHasChanged(ClientStorageInMemory.class, c.getName(), PluginActions.ENQUEUE); busService.send(event); LOG.info("Extension \"{}\" added to plugins list.", c.getName()); } } /** * Unloads a plugin from memory and destroys all bus listeners associated to * it. * * @param c */ @Override public void remove(Client c) { if (clients.contains(c)) { // Stops and unsubscribes channels c.destroy(); clients.remove(c); PluginHasChanged event = new PluginHasChanged(ClientStorageInMemory.class, c.getName(), PluginActions.DEQUEUE); busService.send(event); } } /** * * @return */ @Override public List<Client> getClients() { Collections.sort(clients, new ClientNameComparator()); return Collections.unmodifiableList(clients); } /** * * @param name * @return */ @Override public Client get(String name) { for (Client client : clients) { if (client.getName().equalsIgnoreCase(name)) { return client; } } return null; } /** * * @param filterType * @return */ @Override public List<Client> getClients(String filterType) { List<Client> tmp = new ArrayList<>(); for (Client client : clients) { if (client.getType().equalsIgnoreCase(filterType)) { tmp.add(client); } } Collections.sort(tmp, new ClientNameComparator()); return Collections.unmodifiableList(tmp); } /** * * @param protocol * @return */ @Override public Client getClientByProtocol(String protocol) { for (Client client : clients) { if (client.getConfiguration().getStringProperty("protocol.name", "").equals(protocol)) { return client; } } return null; } /** * * @param input * @return */ @Override public boolean isLoaded(Client input) { if (input == null) { throw new IllegalArgumentException(); } return clients.contains(input); } /* * Checks if a plugin is already installed, if is an obsolete or newer * version */ /** * * @param name * @param version * @return */ @Override public int compareVersions(String name, String version) { Client client = get(name); if ((client != null) && client instanceof Plugin) { //already installed //now check for version Plugin plugin = (Plugin) client; if (plugin.getVersion() == null) { return -1; } return getOldestVersion(plugin.getVersion(), version); } else { //not installed return -1; } } /** * * @param client * @return */ protected boolean isCompatible(Client client) { //seach for a file called PACKAGE Properties config = client.getConfiguration().getProperties(); int requiredMajor = getVersionProperty(config, "framework.required.major"); int requiredMinor = getVersionProperty(config, "framework.required.minor"); int requiredBuild = getVersionProperty(config, "framework.required.build"); //checking framework version compatibility //required version must be older (or equal) then current version if (requiredMajor == Info.getMajor()) { if ((getOldestVersion(requiredMajor + "." + requiredMinor + "." + requiredBuild, Info.getVersion()) <= 0)) { return true; } } return false; } private int getVersionProperty(Properties properties, String key) { //if property is not specified returns Integer.MAX_VALUE so it never match //if is a string returns 0 to match any value with "x" try { int value; if (properties.getProperty(key).equalsIgnoreCase("x")) { value = 0; } else { value = Integer.parseInt(properties.getProperty( key, new Integer(Integer.MAX_VALUE).toString())); } return value; } catch (NumberFormatException numberFormatException) { throw new IllegalArgumentException(); } } /** * Calculates the oldest version between two version string * MAJOR.MINOR.BUILD (eg: 5.3.1) * * @param str1 first version string 5.3.0 * @param str2 second version string 5.3.1 * @return -1 if str1 is older then str2, 0 if str1 equals str2, 1 if str2 * is older then str1 */ private int getOldestVersion(String str1, String str2) { String MAX = Integer.toString(Integer.MAX_VALUE).toString(); str1 = str1.replaceAll("x", MAX); str2 = str2.replaceAll("x", MAX); String[] vals1 = str1.split("\\."); String[] vals2 = str2.split("\\."); int i = 0; while ((i < vals1.length) && (i < vals2.length) && vals1[i].equals(vals2[i])) { i++; } if ((i < vals1.length) && (i < vals2.length)) { int diff = new Integer(vals1[i]).compareTo(new Integer(vals2[i])); return (diff < 0) ? (-1) : ((diff == 0) ? 0 : 1); } int result = (vals1.length < vals2.length) ? (-1) : ((vals1.length == vals2.length) ? 0 : 1); return result; } /** * Creates a placeholder plugin and adds it to the list of added plugins. * This plugin is just a mock object to inform the user that an object with * complete features is expected here. It can be used for example to list a * fake plugin that informs the user the real plugin cannot be added. * * @param simpleName * @param type * @param description * @return */ @Override public Plugin createPluginPlaceholder(final String simpleName, final String type, final String description) { final Plugin placeholder = new Plugin(simpleName) { @Override public String getDescription() { if (description == null) { return "Plugin Unavailable. Error on loading"; } else { return description; } } @Override public String getName() { return "Cannot start \"" + simpleName + "\""; } @Override public String getType() { return type; } @Override public void start() { } @Override public void stop() { } @Override public boolean isRunning() { return false; } @Override public void showGui() { } @Override public void hideGui() { } }; placeholder.setDescription(description); placeholder.configuration = new Config(); return placeholder; } /** * * @param template * @return * @throws RepositoryException */ @Override public Client createObjectPlaceholder(final File template) throws RepositoryException { return new ObjectPluginPlaceholder(thingsRepository, template); } class ClientNameComparator implements Comparator<Client> { @Override public int compare(Client m1, Client m2) { //possibly check for nulls to avoid NullPointerException return m1.getName().compareTo(m2.getName()); } } }