nu.nethome.home.impl.HomeServer.java Source code

Java tutorial

Introduction

Here is the source code for nu.nethome.home.impl.HomeServer.java

Source

/**
 * Copyright (C) 2005-2013, Stefan Strmberg <stefangs@nethome.nu>
 *
 * This file is part of OpenNetHome  (http://www.nethome.nu)
 *
 * OpenNetHome 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 3 of the License, or
 * (at your option) any later version.
 *
 * OpenNetHome 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package nu.nethome.home.impl;

import nu.nethome.home.item.*;
import nu.nethome.home.items.UPnPScanner;
import nu.nethome.home.items.UsbScanner;
import nu.nethome.home.system.*;
import nu.nethome.util.plugin.PluginProvider;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.prefs.Preferences;

import org.apache.commons.lang3.StringUtils;

import static java.lang.Class.forName;

/**
 * This is the main class of the NetHomeServer. It will start and manage all other HomeItem instances.
 *
 * @author Stefan Stromberg
 */
@SuppressWarnings("UnusedDeclaration")
public class HomeServer implements HomeItem, HomeService, ServiceState, ServiceConfiguration, ValueItem {

    private static final String MODEL;

    static {
        MODEL = ("<?xml version = \"1.0\"?> \n" + "<HomeItem Class=\"HomeServer\"  Category=\"Ports\">"
                + "  <Attribute Name=\"Version\" Type=\"String\" Get=\"getVersion\" Default=\"true\" />"
                + "  <Attribute Name=\"SentEventCount\" Type=\"String\" Get=\"getSentEventCount\" />"
                + "  <Attribute Name=\"EventsPerMinute\" Type=\"String\" Get=\"getEventsPerMinute\" />"
                + "  <Attribute Name=\"FileName\" Type=\"String\" Get=\"getFileName\" Set=\"setFileName\" />"
                + "  <Attribute Name=\"UpgradeCommand\" Type=\"String\" Get=\"getUpgradeCommand\" Set=\"setUpgradeCommand\" />"
                + "  <Attribute Name=\"LogFile\" Type=\"String\" Get=\"getLogFile\"    Set=\"setLogFile\" />"
                + "  <Attribute Name=\"PythonScriptFile\" Type=\"String\" Get=\"getPythonFile\"    Set=\"setPythonFile\" />"
                + "  <Attribute Name=\"GlobalLogger\" Type=\"String\" Get=\"getGlobalLogger\"    Set=\"setGlobalLogger\" />"
                + "  <Attribute Name=\"WarningAction\" Type=\"Command\" Get=\"getWarningAction\"    Set=\"setWarningAction\" />"
                + "  <Attribute Name=\"ErrorAction\" Type=\"Command\" Get=\"getErrorAction\"    Set=\"setErrorAction\" />"
                + "  <Attribute Name=\"UpTime\" Type=\"String\" Get=\"getUpTime\" />"
                + "  <Attribute Name=\"MaxDistributionTime\" Type=\"String\" Get=\"getMaxDistributionTime\" Unit=\"ms\" />"
                + "  <Attribute Name=\"AverageDistributionTime\" Type=\"String\" Get=\"getAverageDistributionTime\"  Unit=\"ms\" />"
                + "  <Attribute Name=\"MaxItemTime\" Type=\"String\" Get=\"getMaxItemTime\"  Unit=\"ms\" />"
                + "  <Attribute Name=\"MaxItemName\" Type=\"String\" Get=\"getMaxItemName\" />"
                + "  <Attribute Name=\"AlarmCount\" Type=\"String\" Get=\"getCurrentAlarmCountString\" />"
                + "  <Attribute Name=\"TotalLogRows\" Type=\"String\" Get=\"getTotalLogRecordCountString\" />"
                + "  <Action Name=\"LoadItems\" Method=\"loadItems\" />"
                + "  <Action Name=\"SaveItems\" Method=\"saveItems\" />"
                + "  <Action Name=\"StopServer\" Method=\"stopServer\" />"
                + "  <Action Name=\"UpgradeServer\" Method=\"upgradeServer\" />"
                + "  <Action Name=\"ResetStatistics\" Method=\"resetStatistics\" />"
                + "  <Action Name=\"ClearLog\" Method=\"clearLog\" />"
                + "  <Action Name=\"Update Global Logger\" Method=\"updateGlobalLogger\" />" + "</HomeItem> ");
    }

    private static final int MAX_QUEUE_SIZE = 20;
    private static final String QUIT_EVENT = "BrokerQuitEvent";
    public static final int LOG_RECORD_CAPACITY = 50;
    public static final int EVENT_COUNT_PERIOD = 15;

    private static Logger logger = Logger.getLogger(HomeServer.class.getName());
    private static final int MS_PER_MINUTE = (1000 * 60);
    private static final int MS_PER_HOUR = (MS_PER_MINUTE * 60);
    private static final int MS_PER_DAY = (MS_PER_HOUR * 24);
    private static final int UPGRADE_HOLDOFF_TIME = 500;
    private static final int MINUTES_PER_HOUR = 60;
    private String name;
    private long id = 1L;
    private boolean doUpgrade = false;
    private String upgradeCommand = "/usr/local/lib/home-manager/upgrade.sh";
    private Date startTime = new Date();
    private long maxID = 0;
    private LinkedBlockingQueue<Event> eventQueue;
    private EventDistributionStatistics statistics = new EventDistributionStatistics();
    private Timer minuteTimer = new Timer();
    private Event minuteEvent;
    private String fileName = "system.xml";
    private int sentEventCount = 0;
    private final ItemDirectory itemDirectory = new ItemDirectory();
    private HomeItemFactory factory;
    private HomeItemLoader homeItemLoader;
    private PluginProvider pluginProvider;
    private LinkedBlockingDeque<LogRecord> logRecords;
    private long totalLogRecordCount = 0;
    private int currentWarningCount = 0;
    private boolean activated = false;
    private List<FinalEventListener> finalEventListeners = new LinkedList<>();
    private ExtendedLoggerComponent eventCountlogger = new ExtendedLoggerComponent(this);
    private long eventsCount = 0;
    private long eventsCountPerPeriod = 0;
    private int minuteCounter;
    private int minutesBetweenItemSave = 60;
    private String logDirectory = "";
    private Python python;
    private String loggerComponentDescriptor = "";
    private CommandLineExecutor commandLineExecutor;
    private String warningAction = "";
    private String errorAction = "";

    public HomeServer() {
        eventQueue = new LinkedBlockingQueue<>(MAX_QUEUE_SIZE);
        logRecords = new LinkedBlockingDeque<>(LOG_RECORD_CAPACITY);
        setupLogger();
        eventCountlogger.activate(this);
        commandLineExecutor = new CommandLineExecutor(this, true);
        python = new Python();
    }

    private void setupLogger() {
        Logger.getLogger("").addHandler(new Handler() {
            @Override
            synchronized public void publish(LogRecord record) {
                newLogRecord(record);
            }

            @Override
            public void flush() {
                // Nothing to do
            }

            @Override
            public void close() throws SecurityException {
                logRecords.clear();
            }
        });
    }

    private void newLogRecord(LogRecord record) {
        if (isLogRecordInBlacklist(record)) {
            return;
        }
        totalLogRecordCount++;
        if (logRecords.remainingCapacity() == 0) {
            if (logRecords.getLast().getLevel().intValue() >= Level.WARNING.intValue()) {
                currentWarningCount--;
            }
            logRecords.removeLast();
        }

        logRecords.offerFirst(record);
        if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
            currentWarningCount++;
            if (record.getLevel().intValue() < Level.SEVERE.intValue()) {
                commandLineExecutor.executeCommandLine(warningAction);
            } else {
                commandLineExecutor.executeCommandLine(errorAction);
            }
        }
    }

    private boolean isLogRecordInBlacklist(LogRecord record) {
        if (record.getMessage() == null) {
            return false;
        }
        return record.getMessage().startsWith("Prefs file removed in background")
                || record.getMessage().startsWith("Could not open/create prefs root node")
                || record.getMessage().startsWith("SAAJ0009");
    }

    public String clearLog() {
        logRecords.clear();
        currentWarningCount = 0;
        return "";
    }

    public void run(HomeItemFactory factory, HomeItemLoader loader, PluginProvider pluginProvider,
            BootWebServer bootWebServer) {
        this.factory = factory;
        this.homeItemLoader = loader;
        this.pluginProvider = pluginProvider;

        loadItems(bootWebServer);

        bootWebServer.setStatus("Startup complete");
        waitForEnd();

        // When we get this far, the application is closing down.
        // Stop all HomeItems and empty the instance list.
        stopAndRemoveItems();

        // Upgrade server
        handleUpgrade();
        logger.info("**Exiting HomeManager " + HomeManager.class.getPackage().getImplementationVersion() + "**");
    }

    private void startPython(BootWebServer bootWebServer) {
        if (getPythonFile() != null && !getPythonFile().isEmpty()) {
            bootWebServer.beginSection("Starting Python");
            python.run(this);
        }
    }

    private synchronized void waitForEnd() {
        try {
            wait();
        } catch (InterruptedException e) {
            logger.log(Level.WARNING, "Interrupted waiting for end of program", e);
        }
    }

    public void stopServer() {
        saveItems();
        internalStopServer();
    }

    private synchronized void internalStopServer() {
        notify();
    }

    public HomeService getService() {
        return this;
    }

    public ServiceConfiguration getConfiguration() {
        return this;
    }

    public void activate(HomeService server) {
        Thread eventThread = new Thread("EventDistributor") {
            @Override
            public void run() {
                eventDistributorTask();
            }
        };
        eventThread.start();
        minuteEvent = new InternalEvent(MINUTE_EVENT_TYPE);
        Calendar date = Calendar.getInstance();
        // Start at next next even minute
        boolean hourJump = (date.get(Calendar.MINUTE) == (MINUTES_PER_HOUR - 1));
        date.set(Calendar.HOUR, date.get(Calendar.HOUR) + (hourJump ? 1 : 0));
        date.set(Calendar.MINUTE, (hourJump ? 1 : (date.get(Calendar.MINUTE) + 2)));
        date.set(Calendar.SECOND, 0);
        date.set(Calendar.MILLISECOND, 0);
        // Schedule the job at m_Interval minutes interval
        minuteTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                send(minuteEvent);
            }
        }, date.getTime(), MS_PER_MINUTE);
        activated = true;
    }

    /*
     * (non-Javadoc)
     *
     * @see ssg.home.HomeItem#setName()
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the fileName
     */
    public String getFileName() {
        return fileName;
    }

    /**
     * @param fileName the fileName to set
     */
    public void setFileName(String fileName) {
        if (activated && !fileName.equals(this.fileName)) {
            // Kind of ugly. If the user changes the save file name, we also save this as a system property
            // so we can find the name (file) again when the server is started.
            Preferences prefs = Preferences.userNodeForPackage(HomeManager.class);
            prefs.put("SaveFileName", fileName);
        }
        this.fileName = fileName;
    }

    public List<DirectoryEntry> listInstances(String pattern) {
        return itemDirectory.listInstances(pattern);
    }

    public HomeItemProxy openInstance(String name) {
        HomeItem item = itemDirectory.findInstance(name);
        if (item != null) {
            try {
                return new LocalHomeItemProxy(item, this);
            } catch (ModelException e) {
                // return null
            }
        }
        return null;
    }

    public boolean renameInstance(String fromInstanceName, String toInstanceName) {
        return itemDirectory.renameInstance(fromInstanceName, toInstanceName);
    }

    public boolean removeInstance(String instanceName) {
        HomeItem item = itemDirectory.removeInstance(instanceName);
        if (item == null) {
            // Item does not exist
            return false;
        }

        // Stop the instance unless it is never started
        if (!item.getName().startsWith("#")) {
            item.stop();
        }
        return true;
    }

    public Event createEvent(String type, String value) {
        return new InternalEvent(type, value);
    }

    /*
     * (non-Javadoc)
     *
     * @see ssg.home.EventBroker#send(ssg.home.Event)
     *
     * This implementation iterates all HomeItems connected to the EventBroker
     * and sends the event to each of them.
     */
    public void send(Event event) {
        logger.fine(event.toString());
        if (!eventQueue.offer(event)) {
            handleEventDistributionFaliure(event);
        } else {
            sentEventCount++;
            eventsCount++;
        }
    }

    public boolean executePython(String pythonCode) {
        try {
            if (python.executePython(pythonCode)) {
                return true;
            } else {
                logger.warning("Error executing Python: " + pythonCode);
                return false;
            }
        } catch (IOException ex) {
            logger.warning("Error when calling script function:" + ex.toString());
            return false;
        }
    }

    private void handleEventDistributionFaliure(Event event) {
        if (statistics.isItemCurrentlyProcessingEvent()) {
            logger.severe("Event queue full. Current Item processing is \"" + statistics.getCurrentItemName()
                    + "\"  since " + getCurrentItemProcessingTime() + " ms");
        } else {
            logger.severe("Event queue full trying to distribute " + event.toString());
        }
    }

    public void eventDistributorTask() {
        while (true) {
            Event event;
            String itemName = "";
            try {
                // Take the next event from the queue, will wait if no events yet
                event = eventQueue.take();
                // Check if it was the quit event, quit in that case
                if (event.getAttribute(Event.EVENT_TYPE_ATTRIBUTE).equals(QUIT_EVENT)) {
                    return;
                }
                // Loop over all Items and offer the event
                statistics.startDistributionRound();
                boolean eventIsHandled = false;
                for (HomeItem home : itemDirectory.getHomeItems()) {
                    try {
                        itemName = home.getName();
                        logger.finest("Distributing event to " + itemName);
                        statistics.startItemDistribution(itemName);
                        boolean handled = home.receiveEvent(event);
                        eventIsHandled |= handled;
                    } catch (Exception e) {
                        logger.log(Level.WARNING,
                                "Failed to distribute event to \"" + itemName + "\" (" + event.toString() + ") ",
                                e);
                    }
                    statistics.endItemDistribution();
                }
                synchronized (finalEventListeners) {
                    for (FinalEventListener listener : finalEventListeners) {
                        listener.receiveFinalEvent(event, eventIsHandled);
                    }
                }
            } catch (InterruptedException e) {
                // Do Dinada
            } finally {
                statistics.endDistributionRound();
            }
        }
    }

    /**
     * Register a new HomeItem
     *
     * @param item Instance to register
     */
    public int registerInstance(HomeItem item) {
        return itemDirectory.registerInstance(item, false);
    }

    public String getModel() {
        return MODEL;
    }

    public boolean receiveEvent(Event e) {
        if (e.getAttribute(Event.EVENT_TYPE_ATTRIBUTE).equals(MINUTE_EVENT_TYPE)
                && ++minuteCounter >= minutesBetweenItemSave) {
            saveItems();
            minuteCounter = 0;
            return true;
        }
        return false;
    }

    public String getName() {
        return name;
    }

    public long getItemId() {
        return id;
    }

    public void setItemId(long id) {
        this.id = id;
    }

    /**
     * HomeItem method which stops all object activity for program termination
     */
    public void stop() {
        // Stop the event distribution thread by sending the quit event
        Event quitEvent = new InternalEvent(QUIT_EVENT);
        send(quitEvent);

        // Stop the minute timer
        minuteTimer.cancel();
    }

    public String getSentEventCount() {
        return String.valueOf(sentEventCount);
    }

    /**
     * Load HomeItems from a file and activate them. This is done in two steps: First the Items are
     * registered in the internal directory. After that the Items are activated in start order.
     * This means that at activation, all Items are reachable in the directory even those which are
     * not yet activated.
     */
    public void loadItems(BootWebServer bootWebServer) {
        String currentFileName = getFileName();
        bootWebServer.beginSection("Loading items from file " + currentFileName);
        List<HomeItem> loadedItems = homeItemLoader.loadItems(getFileName(), factory, this);

        bootWebServer.beginSection("Pre processing items");
        addSingletonItems(loadedItems);

        sortOnStartOrder(loadedItems);

        for (HomeItem item : loadedItems) {
            if (item.getItemId() > maxID) {
                maxID = item.getItemId();
            }
        }
        maxID += 1;

        // Loop through all created Items, and register them
        bootWebServer.beginSection("Registering items");
        for (HomeItem item : loadedItems) {

            // This is a backward compatibility check. If the Item has no valid ID, assign one
            if (item.getItemId() == 0) {
                item.setItemId(maxID);
                maxID += 1;
            }

            // Register the new instance
            int regResult = itemDirectory.registerInstance(item, true);
            if (regResult != 0) {
                // IF we fail to register the instance, mark it as bad by setting ID = 0
                HomeServer.logger.warning(
                        "Failed to register Item " + item.getName() + " Error " + Integer.toString(regResult));
                item.setItemId(0);
            }
        }

        startPython(bootWebServer);

        // Now loop through all Items again in start order and activate them
        bootWebServer.beginSection("Start activating Items");
        int itemCount = loadedItems.size();
        int activatedItemCount = 0;
        for (HomeItem item : loadedItems) {
            if (item.getClass().getSimpleName().equals("JettyWEB")) {
                bootWebServer.stop();
            } else {
                bootWebServer.setStatus("Activating item '" + item.getName() + "' (" + (activatedItemCount + 1)
                        + " of " + itemCount + ")");
            }
            if (!item.getName().startsWith("#") && (item.getItemId() != 0)) {
                try {
                    item.activate(this);
                    activatedItemCount++;
                } catch (Exception e) {
                    HomeServer.logger
                            .warning("Failed to activate Item " + item.getName() + " Error " + e.getMessage());
                }
            }
        }
        HomeServer.logger.info("Activated " + Integer.toString(activatedItemCount) + " of "
                + Integer.toString(itemCount) + " Items");
        setFileName(currentFileName);
    }

    private void addSingletonItems(List<HomeItem> loadedItems) {
        boolean hasUpnpScanner = false;
        boolean hasUsbScanner = false;
        for (HomeItem loadedItem : loadedItems) {
            if (loadedItem instanceof UPnPScanner) {
                hasUpnpScanner = true;
            }
            if (loadedItem instanceof UsbScanner) {
                hasUsbScanner = true;
            }
        }
        if (!hasUpnpScanner) {
            UPnPScanner uPnPScanner = new UPnPScanner();
            uPnPScanner.setName("UPnP_Scanner");
            loadedItems.add(uPnPScanner);
        }
        if (!hasUsbScanner) {
            UsbScanner usbScanner = new UsbScanner();
            usbScanner.setName("USB_Scanner");
            loadedItems.add(usbScanner);
        }
    }

    private void sortOnStartOrder(List<HomeItem> sortedItems) {
        Collections.sort(sortedItems, new Comparator<HomeItem>() {
            public int compare(HomeItem o1, HomeItem o2) {
                try {
                    HomeItemModel m1 = StaticHomeItemModel.getModel(o1);
                    HomeItemModel m2 = StaticHomeItemModel.getModel(o2);
                    if (m1.getStartOrder() == m2.getStartOrder()) {
                        return o1.getName().compareTo(o2.getName());
                    }
                    return m1.getStartOrder() > m2.getStartOrder() ? 1 : -1;
                } catch (ModelException e) {
                    // This should not happen...
                    return 0;
                }
            }
        });
    }

    public void saveItems() {
        List<HomeItem> items = new LinkedList<>();
        Iterator<HomeItem> i = itemDirectory.iterator();
        while (i.hasNext()) {
            items.add(i.next());
        }
        homeItemLoader.saveItems(items, fileName);
    }

    /**
     * Stop all HomeItems and empty the internal instance lists
     */
    public void stopAndRemoveItems() {
        Iterator<HomeItem> it = itemDirectory.iterator();
        logger.info("Closing down");
        while (it.hasNext()) {
            try {
                HomeItem item = it.next();
                logger.info("Stopping: " + item.getName());
                item.stop();
            } catch (Exception e) {
                logger.warning("Exception caught during stop of Item: " + e.getMessage());
            }
        }
        logger.info("Stopped Items");
        itemDirectory.clear();
        eventCountlogger.stop();
        logger.info("Stopped Internal services");
    }

    /**
     * Initiate the upgrade sequence
     */
    public void upgradeServer() {
        doUpgrade = true;
        stopServer();
    }

    public void handleUpgrade() {
        if (doUpgrade) {
            try {
                logger.info("Starting upgrade sequence");
                Runtime r = Runtime.getRuntime();
                // Run the upgrade command. If this is a Windows bat-file, you have to have one bat file which
                // does a "start" of the second real upgrade bat file.
                r.exec(upgradeCommand);
                try {
                    // For some reason we have to wait a while, otherwise it seems this program exits before
                    // the execution of the upgrade command is really started.
                    Thread.sleep(UPGRADE_HOLDOFF_TIME);
                } catch (InterruptedException i) {
                    // Do nothing
                }
            } catch (IOException e) {
                logger.warning("Could not auto upgrade:" + e.getMessage());
            }
        }
    }

    /**
     * @return the upgradeCommand
     */
    public String getUpgradeCommand() {
        return upgradeCommand;
    }

    /**
     * @param upgradeCommand the upgradeCommand to set
     */
    public void setUpgradeCommand(String upgradeCommand) {
        this.upgradeCommand = upgradeCommand;
    }

    public String getUpTime() {
        long currentTime = new Date().getTime();
        long uptime = currentTime - startTime.getTime();
        long days = uptime / MS_PER_DAY;
        uptime = uptime - days * MS_PER_DAY;
        long hours = uptime / MS_PER_HOUR;
        uptime = uptime - hours * MS_PER_HOUR;
        long minutes = uptime / MS_PER_MINUTE;
        String result = (days != 0) ? Long.toString(days) + " days " : "";
        result += (hours != 0) ? Long.toString(hours) + " hours " : "";
        return result + Long.toString(minutes) + " minutes";
    }

    public HomeItemProxy createInstance(String publicClassName, String instanceName) {
        // Check name
        if (itemDirectory.findInstance(instanceName) != null) {
            return null;
        }

        // Try to create an instance of the new class
        HomeItem newItem = factory.createInstance(publicClassName);
        if (newItem == null) {
            return null;
        }
        // Set the name, ID and register the new instance
        try {
            newItem.setName(instanceName);
        } catch (Exception e) {
            logger.log(Level.WARNING, "Failed setting name on HomeItem: " + instanceName, e);
        }
        maxID += 1;
        newItem.setItemId(maxID);
        registerInstance(newItem);
        try {
            return new LocalHomeItemProxy(newItem, this);
        } catch (ModelException e) {
            return null;
        }
    }

    public List<HomeItemInfo> listClasses() {
        return factory.listItemTypes();
    }

    public PluginProvider getPluginProvider() {
        return pluginProvider;
    }

    public ServiceState getState() {
        return this;
    }

    public String getMaxDistributionTime() {
        Double value = statistics.getMaxRoundTime();
        return String.format("%.2f", value);
    }

    public String getAverageDistributionTime() {
        Double value = statistics.getAvarageRoundTime();
        return String.format("%.2f", value);
    }

    public String getMaxItemTime() {
        Double value = statistics.getMaxItemTime();
        return String.format("%.2f", value);
    }

    public String getCurrentItemProcessingTime() {
        Double value = statistics.currentItemProcessingTime() / EventDistributionStatistics.NANO_PER_MILLI;
        return String.format("%.2f", value);
    }

    public String getMaxItemName() {
        return statistics.getMaxItemName();
    }

    public void resetStatistics() {
        statistics.resetStatistics();
    }

    public String getVersion() {
        String version = HomeManager.class.getPackage().getImplementationVersion();
        return (version == null) ? "Unknown" : version;
    }

    public Collection<LogRecord> getCurrentLogRecords() {
        return new ArrayList<>(logRecords);
    }

    public int getCurrentAlarmCount() {
        return currentWarningCount;
    }

    public long getTotalLogRecordCount() {
        return totalLogRecordCount;
    }

    public String getCurrentAlarmCountString() {
        return Integer.toString(currentWarningCount);
    }

    public String getTotalLogRecordCountString() {
        return Long.toString(totalLogRecordCount);
    }

    @Override
    public void registerFinalEventListener(FinalEventListener listener) {
        synchronized (finalEventListeners) {
            finalEventListeners.add(listener);
        }
    }

    @Override
    public void unregisterFinalEventListener(FinalEventListener listener) {
        synchronized (finalEventListeners) {
            finalEventListeners.remove(listener);
        }
    }

    @Override
    public String getValue() {
        eventsCountPerPeriod = eventsCount;
        eventsCount = 0;
        return getEventsPerMinute();
    }

    public String getEventsPerMinute() {
        return Long.toString(eventsCountPerPeriod / EVENT_COUNT_PERIOD);
    }

    public String getLogFile() {
        return eventCountlogger.getFileName();
    }

    public void setLogFile(String logfile) {
        eventCountlogger.setFileName(logfile);
    }

    @Override
    public String getLogDirectory() {
        return logDirectory;
    }

    public void setLogDirectory(String logFileDirectory) {
        this.logDirectory = logFileDirectory;
        if (!this.logDirectory.isEmpty() && !this.logDirectory.endsWith(File.separator)) {
            this.logDirectory += File.separator;
        }
    }

    public String getPythonFile() {
        return python.getScriptSourceFileName();
    }

    public void setPythonFile(String scriptFile) {
        String lastScriptFile = python.getScriptSourceFileName();
        python.setScriptSourceFileName(scriptFile);
        if (!python.isActivated() && activated && !scriptFile.isEmpty()) {
            logger.info("Starting python");
            python.run(this);
        }
    }

    /**
     * Gets the loggerComponentDescriptor. See {@link ValueItemLogger}.
     *
     * @return the loggerComponentDescriptor.
     */
    public String getGlobalLogger() {
        return loggerComponentDescriptor;
    }

    /**
     * Sets the loggerComponentDescriptor that defines the global logger. See
     * {@link ValueItemLogger}.
     *
     * @param loggerComponentDescriptor the loggerComponentDescriptor to set
     */
    public void setGlobalLogger(String loggerComponentDescriptor) {
        this.loggerComponentDescriptor = loggerComponentDescriptor;
    }

    /**
     * This is the action that updates a the global logger - if it has been
     * provided in the GlobalLogger field of the settings pane.
     * <p/>
     * It loops through all home items that are known. For every item that
     * inherits from ValueItem and has a LoggerComponent field, and a provided
     * LogFile, its content is imported into the global logger.
     *
     * @return
     */
    public String updateGlobalLogger() {

        if (StringUtils.isBlank(getGlobalLogger())) {
            logger.log(Level.INFO, "Action: updateGlobalLogger - No global logger provided.");
            return "";
        }

        String itemName;
        ValueItemLogger valueLogger = ValueItemLoggerFactory.createValueItemLogger(getGlobalLogger());

        for (HomeItem home : itemDirectory.getHomeItems()) {
            try {
                itemName = home.getName();
                // Check if HomeItem implements ValueItem
                if (!(home instanceof ValueItem)) {
                    continue;
                }

                HomeItemProxy proxy = new LocalHomeItemProxy(home, this);

                // Check if HomeItem has private ExtendedLoggerComponent
                ExtendedLoggerComponent extLogComp = getLoggerComponent(proxy);
                if (extLogComp == null) {
                    logger.log(Level.FINE, itemName + " does not have an extended logger component...");
                    continue;
                }

                // Read the HomeItem file LogFile field
                if (!StringUtils.isBlank(proxy.getAttributeValue("LogFile"))) {

                    String logFile = getCompletePathName(getLogDirectory(), proxy.getAttributeValue("LogFile"));
                    String homeItemId = Long.toString(home.getItemId());

                    logger.log(Level.INFO, itemName + " with id " + homeItemId + " has a private logfile ('"
                            + logFile + "') that will be merged with the global database.");

                    if (valueLogger.importCsvFile(logFile, getGlobalLogger(), homeItemId) == false) {
                        // Failed, let's get out of here!
                        return "Failed";
                    }
                }

            } catch (Exception e) {
                logger.log(Level.WARNING, "Failed updating the logger: ", e);
            }
        }

        return "";
    }

    public static String getCompletePathName(String path, String file) {
        String fileName = file == null ? "" : file;
        String pathName = path == null ? "" : path;
        if (fileName != null && !fileName.toLowerCase().startsWith(pathName.toLowerCase())) {
            fileName = Paths.get(pathName).resolve(fileName).toString();
        }
        return fileName;
    }

    /**
     * Gets an associated extended logger component of the HomeItem. Note that
     * it is up to the HomeItem implementation to actually use a component
     * logger or not. This method will try to find one in its private members by
     * using introspection.
     *
     * @param proxy the home item proxy
     * @return an ExtendedLoggerComponent object, or null if none is found.
     */
    private ExtendedLoggerComponent getLoggerComponent(HomeItemProxy proxy) {
        HomeItem item = (HomeItem) proxy.getInternalRepresentation();
        ExtendedLoggerComponent elc = null;
        Class<?> clz = item.getClass();
        do {
            for (Field field : clz.getDeclaredFields()) {
                field.setAccessible(true);
                Class<?> classType = field.getType();
                if (classType.getName().contains("ExtendedLoggerComponent")) {
                    try {
                        elc = (ExtendedLoggerComponent) field.get(item);
                    } catch (IllegalArgumentException | IllegalAccessException e) {
                        // Eat!
                    }
                    break;
                }
            }
            clz = clz.getSuperclass();
        } while (null != clz);

        return elc;
    }

    @Override
    public String getValueItemLoggerDescriptor() {
        return getGlobalLogger();
    }

    public String getWarningAction() {
        return warningAction;
    }

    public void setWarningAction(String warningAction) {
        this.warningAction = warningAction;
    }

    public String getErrorAction() {
        return errorAction;
    }

    public void setErrorAction(String errorAction) {
        this.errorAction = errorAction;
    }
}