/**
*
* 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.reactions;
import com.freedomotic.app.Freedomotic;
import com.freedomotic.exceptions.DataUpgradeException;
import com.freedomotic.exceptions.RepositoryException;
import com.freedomotic.persistence.DataUpgradeService;
import com.freedomotic.persistence.FreedomXStream;
import com.freedomotic.persistence.XmlPreprocessor;
import com.freedomotic.settings.Info;
import com.google.inject.Inject;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
import static com.freedomotic.util.FileOperations.writeSummaryFile;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Enrico Nicoletti
*/
class TriggerRepositoryImpl implements TriggerRepository {
private static final Logger LOG = LoggerFactory.getLogger(TriggerRepositoryImpl.class.getName());
private static ArrayList<Trigger> list = new ArrayList<>();
private final DataUpgradeService dataUpgradeService;
private static final String TRIGGER_FILE_EXTENSION = ".xtrg";
@Inject
public TriggerRepositoryImpl(DataUpgradeService dataUpgradeService) {
this.dataUpgradeService = dataUpgradeService;
}
/**
* Saves the triggers into the specified folder.
*
* @param folder the folder where to save the triggers
*/
@Override
public void saveTriggers(File folder) {
if (list.isEmpty()) {
LOG.warn("There are no triggers to persist, folder \"{}\" will not be altered", folder.getAbsolutePath());
return;
}
if (!folder.isDirectory()) {
LOG.warn("\"{}\" is not a valid trigger folder. Skipped", folder.getAbsoluteFile());
return;
}
deleteTriggerFiles(folder);
try {
LOG.info("Saving triggers to file into \"{}\"", folder.getAbsolutePath());
StringBuilder summaryContent = new StringBuilder();
for (Trigger trigger : list) {
if (trigger.isToPersist()) {
String uuid = trigger.getUUID();
if ((uuid == null) || uuid.isEmpty()) {
trigger.setUUID(UUID.randomUUID().toString());
}
String fileName = trigger.getUUID() + TRIGGER_FILE_EXTENSION;
File file = new File(folder + "/" + fileName);
FreedomXStream.toXML(trigger, file);
}
summaryContent.append(trigger.getUUID()).append("\t\t").append(trigger.getName()).append("\t\t\t")
.append(trigger.getChannel()).append("\n");
}
writeSummaryFile(new File(folder, "index.txt"), "#Filename \t\t #TriggerName \t\t\t #ListenedChannel\n", summaryContent.toString());
} catch (Exception e) {
LOG.error("Error while saving triggers ", e);
}
}
/**
* Deletes all the triggers into the specified folder.
*
* @param folder the folder containing all the triggers to delete
*/
private static void deleteTriggerFiles(File folder) {
File[] files = folder.listFiles();
// this filter only returns triggers files
FileFilter objectFileFilter
= new FileFilter() {
public boolean accept(File file) {
if (file.isFile() && file.getName().endsWith(TRIGGER_FILE_EXTENSION)) {
return true;
} else {
return false;
}
}
};
files = folder.listFiles(objectFileFilter);
for (File file : files) {
file.delete();
}
}
/**
*
* @return
*/
@Deprecated
public static List<Trigger> getTriggers() {
return list;
}
/**
* Loads triggers from a specified folder.
*
* @param folder the folder to load triggers from
*/
@Override
public void loadTriggers(File folder) {
XStream xstream = FreedomXStream.getXstream();
// this filter only returns triggers files
FileFilter objectFileFileter
= new FileFilter() {
@Override
public boolean accept(File file) {
return file.isFile() && file.getName().endsWith(TRIGGER_FILE_EXTENSION);
}
};
File[] files = folder.listFiles(objectFileFileter);
try {
if (files != null) {
for (File file : files) {
Trigger trigger = null;
String xml;
try {
//validate the object against a predefined DTD
xml = XmlPreprocessor.validate(file, Info.PATHS.PATH_CONFIG_FOLDER + "/validator/trigger.dtd");
} catch (IOException e) {
LOG.error(Freedomotic.getStackTraceInfo(e));
continue;
}
try {
Properties dataProperties = new Properties();
String fromVersion;
try {
dataProperties.load(new FileInputStream(new File(Info.PATHS.PATH_DATA_FOLDER + "/data.properties")));
fromVersion = dataProperties.getProperty("data.version");
} catch (IOException iOException) {
LOG.error(Freedomotic.getStackTraceInfo(iOException));
// fallback to a default version for older version without that properties file
fromVersion = "5.5.0";
}
xml = (String) dataUpgradeService.upgrade(Trigger.class, xml, fromVersion);
trigger = (Trigger) xstream.fromXML(xml);
} catch (DataUpgradeException dataUpgradeException) {
throw new RepositoryException("Cannot upgrade trigger file " + file.getAbsolutePath(), dataUpgradeException);
} catch (XStreamException e) {
throw new RepositoryException("XML parsing error. Readed XML is \n" + xml, e);
}
//addAndRegister trigger to the list if it is not a duplicate
if (!list.contains(trigger)) {
if (trigger.isHardwareLevel()) {
trigger.setPersistence(false); //it has not to me stored in root/data folder
addAndRegister(trigger); //in the list and start listening
} else {
if (folder.getAbsolutePath().startsWith(Info.PATHS.PATH_PLUGINS_FOLDER.getAbsolutePath())) {
trigger.setPersistence(false);
} else {
trigger.setPersistence(true); //not hardware trigger and not plugin related
}
list.add(trigger); //only in the list not registred. I will be registred only if used in mapping
}
} else {
LOG.warn("Trigger \"{}\" is already in the list", trigger.getName());
}
}
} else {
LOG.info("No triggers to load from the folder \"{}\"", folder.toString());
}
} catch (Exception e) {
LOG.error("Exception while loading this trigger ", e);
}
}
/**
* Adds and registers a trigger.
*
* @param t the trigger to add and register
*/
public static synchronized void addAndRegister(Trigger t) {
int preSize = TriggerRepositoryImpl.size();
if (!list.contains(t)) {
list.add(t);
t.register();
int postSize = TriggerRepositoryImpl.size();
if (postSize != (preSize + 1)) {
LOG.error("Error while adding and registering trigger \"{}\"", t.getName());
}
} else {
//this trigger is already in the list
int old = list.indexOf(t);
list.get(old).unregister();
list.set(old, t);
t.register();
}
}
/**
* Adds a trigger.
*
* @param t the trigger to add
*/
public static synchronized void add(Trigger t) {
if (t != null) {
int preSize = TriggerRepositoryImpl.size();
if (!list.contains(t)) {
list.add(t);
} else {
//this trigger is already in the list
int old = list.indexOf(t);
list.get(old).unregister();
list.set(old, t);
}
int postSize = TriggerRepositoryImpl.size();
if (postSize != (preSize + 1)) {
LOG.error("Error while adding trigger \"{}\"", t.getName());
}
}
}
/**
* Removes a trigger.
*
* @param t the trigger to remove
*/
public static synchronized void remove(Trigger t) {
int preSize = TriggerRepositoryImpl.size();
try {
t.unregister();
list.remove(t);
int postSize = TriggerRepositoryImpl.size();
if (postSize != (preSize - 1)) {
LOG.error("Error while removing trigger \"{}\"", t.getName());
}
} catch (Exception e) {
LOG.error("Error while unregistering trigger \"{}\"", t.getName(), e);
}
}
/**
* Gets a trigger by its name.
*
* @param name
* @return a trigger with the name (ignore-case) as the String in input
*/
@Deprecated
public static Trigger getTrigger(String name) {
if ((name == null) || (name.trim().isEmpty())) {
return null;
}
for (Trigger trigger : list) {
if (trigger.getName().equalsIgnoreCase(name.trim())) {
return trigger;
}
}
LOG.warn("Searching for a trigger named \"{}\" but it doesn't exist", name);
return null;
}
/**
*
* @param input
* @return
*/
@Deprecated
public static Trigger getTrigger(Trigger input) {
if (input != null) {
for (Iterator it = list.iterator(); it.hasNext();) {
Trigger trigger = (Trigger) it.next();
if (trigger.equals(input)) {
return trigger;
}
}
}
return null;
}
/**
*
* @param i
* @return
*/
@Deprecated
public static Trigger getTrigger(int i) {
return list.get(i);
}
/**
*
* @param uuid
* @return
*/
@Deprecated
public static Trigger getTriggerByUUID(String uuid) {
for (Trigger t : list) {
if (t.getUUID().equalsIgnoreCase(uuid)) {
return t;
}
}
return null;
}
/**
*
* @return
*/
@Deprecated
public static Iterator<Trigger> iterator() {
return list.iterator();
}
/**
* Returns the triggers number.
*
* @return number of triggers
*/
public static int size() {
return list.size();
}
@Override
public List<Trigger> findAll() {
Collections.sort(list, new TriggerNameComparator());
return list;
}
/**
* Finds a trigger given its name.
*
* @param name the name of the trigger to find
*/
@Override
public List<Trigger> findByName(String name) {
List<Trigger> tl = new ArrayList<>();
for (Trigger t : findAll()) {
if (t.getName().equalsIgnoreCase(name)) {
tl.add(t);
}
}
return tl;
}
@Override
public Trigger findOne(String uuid) {
return getTriggerByUUID(uuid);
}
@Override
public boolean create(Trigger item) {
try {
add(item);
return true;
} catch (Exception e) {
LOG.error("Cannot add trigger \"{}\"" + item.getName(), e);
return false;
}
}
@Override
public boolean delete(Trigger item) {
try {
remove(item);
return true;
} catch (Exception e) {
return false;
}
}
/**
* Deletes a trigger given its uuid.
*
* @param uuid the uuid of the trigger to delete
*/
@Override
public boolean delete(String uuid) {
return delete(findOne(uuid));
}
@Override
public Trigger modify(String uuid, Trigger data) {
try {
if (uuid == null || uuid.isEmpty() || data == null) {
LOG.warn("Cannot even start modifying trigger, basic data missing");
return null;
} else {
delete(uuid);
data.setUUID(uuid);
create(data);
try {
data.register();
} catch (Exception f) {
LOG.warn("Cannot register trigger \"{}\"", data.getName(), f);
}
return data;
}
} catch (Exception e) {
LOG.error("Error while modifying trigger \"{}\"" + data.getName(), e);
return null;
}
}
/**
* Creates a copy of a given trigger.
*
* @param trg the trigger to copy
*/
@Override
public Trigger copy(Trigger trg) {
try {
Trigger t = findOne(trg.getUUID()).clone();
t.setName("Copy of trigger " + t.getName());
create(t);
return t;
} catch (Exception e) {
LOG.error(Freedomotic.getStackTraceInfo(e));
return null;
}
}
/**
* Deletes all triggers.
*
*/
@Override
public void deleteAll() {
try {
for (Trigger t : findAll()) {
delete(t);
}
} catch (Exception e) {
} finally {
list.clear();
}
}
/**
* This class compares two triggers given their names.
*
*/
class TriggerNameComparator implements Comparator<Trigger> {
@Override
public int compare(Trigger t1, Trigger t2) {
return t1.getName().compareTo(t2.getName());
}
}
}