/**
*
* 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
*/
public class ReactionRepositoryImpl implements ReactionRepository {
private static final Logger LOG = LoggerFactory.getLogger(ReactionRepositoryImpl.class.getName());
//for persistence purposes. ELEMENTS CANNOT BE MODIFIED OUTSIDE THIS CLASS
private static final List<Reaction> list = new ArrayList<>();
private static final String REACTION_FILE_EXTENSION = ".xrea";
private final DataUpgradeService dataUpgradeService;
@Inject
public ReactionRepositoryImpl(DataUpgradeService dataUpgradeService) {
this.dataUpgradeService = dataUpgradeService;
}
/**
* Saves all .xrea reaction files to a given folder.
*
* @param folder the folder where to save the files
*/
public void saveReactions(File folder) {
if (list.isEmpty()) {
LOG.warn("There are no reactions to persist, folder \"{}\" will not be altered.", folder.getAbsolutePath());
return;
}
if (!folder.isDirectory()) {
LOG.warn("\"{}\" is not a valid reaction folder. Skipped", folder.getAbsoluteFile());
return;
}
deleteReactionFiles(folder);
try {
LOG.info("Saving reactions to file into \"{}\"", folder.getAbsolutePath());
StringBuilder summaryContent = new StringBuilder();
for (Reaction reaction : list) {
String uuid = reaction.getUuid();
if ((uuid == null) || uuid.isEmpty()) {
reaction.setUuid(UUID.randomUUID().toString());
}
String fileName = reaction.getUuid() + REACTION_FILE_EXTENSION;
File file = new File(folder + "/" + fileName);
FreedomXStream.toXML(reaction, file);
summaryContent.append(reaction.getUuid()).append("\t\t\t").append(reaction.toString()).append("\t\t\t")
.append(reaction.getDescription()).append("\n");
}
writeSummaryFile(new File(folder, "index.txt"), "#Filename \t\t #Reaction \t\t\t #Description\n", summaryContent.toString());
} catch (Exception e) {
LOG.error("Error while saving reations", e);
}
}
/**
* Deletes all .xrea reaction files from a given folder.
*
* @param folder the folder to save the files from
*/
private void deleteReactionFiles(File folder) {
File[] files;
// This filter only returns object files
FileFilter objectFileFileter
= new FileFilter() {
@Override
public boolean accept(File file) {
return file.isFile() && file.getName().endsWith(REACTION_FILE_EXTENSION);
}
};
files = folder.listFiles(objectFileFileter);
for (File file : files) {
file.delete();
}
}
/**
* Loads all .xrea reaction files from a given folder.
*
* @param folder the folder to load reaction files from
*/
public synchronized void loadReactions(File folder) {
XStream xstream = FreedomXStream.getXstream();
// This filter only returns object files
FileFilter objectFileFileter
= new FileFilter() {
public boolean accept(File file) {
if (file.isFile() && file.getName().endsWith(REACTION_FILE_EXTENSION)) {
return true;
} else {
return false;
}
}
};
File[] files = folder.listFiles(objectFileFileter);
try {
if (files != null) {
for (File file : files) {
Reaction reaction = null;
String xml;
//validate the object against a predefined DTD
try {
xml = XmlPreprocessor.validate(file, Info.PATHS.PATH_CONFIG_FOLDER + "/validator/reaction.dtd");
} catch (IOException ex) {
throw new RepositoryException(ex.getMessage(), ex);
}
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(Reaction.class, xml, fromVersion);
reaction = (Reaction) xstream.fromXML(xml);
} catch (DataUpgradeException dataUpgradeException) {
throw new RepositoryException("Cannot upgrade reaction file " + file.getAbsolutePath(), dataUpgradeException);
} catch (XStreamException e) {
throw new RepositoryException("XML parsing error. Readed XML is \n" + xml, e);
}
if (reaction.getTrigger() != null && reaction.getTrigger().getName() != null) {
add(reaction);
} else {
LOG.error("Cannot add reaction \"{}\": it has an empty trigger", file.getName());
continue;
}
if (reaction.getCommands().isEmpty()) {
LOG.warn("Reaction \"{}\" has no valid commands. Maybe related objects are missing or not configured properly", reaction.toString());
}
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("No reactions to load from the folder \"{}\"", folder.toString());
}
}
} catch (Exception e) {
LOG.error("Exception while loading reactions from \"{}\"", new Object[]{folder.getAbsolutePath()}, e);
}
}
/**
*
* @param r
*/
@Deprecated
public void add(Reaction r) {
if (!exists(r)) { //if not already loaded
//if it's a new reaction validate it's commands
if (r.getCommands() != null && !r.getCommands().isEmpty()) {
try {
r.getTrigger().register(); //trigger starts to listen on its channel
} catch (Exception e) {
LOG.warn("Cannot register trigger");
}
list.add(r);
r.setChanged();
if (LOG.isDebugEnabled()) {
LOG.debug("Added new reaction \"{}\"", r.getDescription());
}
}
} else {
// Exists but has no commands
if (r.getCommands().isEmpty()) {
LOG.info("The reaction \"{}\" has no associated commands and will be unloaded.", r.getDescription());
remove(r);
}
LOG.info("The reaction \"{}\" is already loaded so it is skipped.", r.getDescription());
}
}
/**
*
* @param input
*/
@Deprecated
public void remove(Reaction input) {
if (input != null) {
boolean removed = list.remove(input);
LOG.info("Removed reaction \"{}\"", input.getDescription());
try {
input.getTrigger().unregister();
} catch (Exception e) {
LOG.warn("Cannot unregister trigger");
}
if ((!removed) && (list.contains(input))) {
LOG.warn("Error while removing Reaction \"{}\" from the list", input.getDescription());
}
}
}
/**
*
* @return
*/
@Deprecated
public Iterator<Reaction> iterator() {
return list.iterator();
}
/**
*
* @return
*/
@Deprecated
public List<Reaction> getReactions() {
return Collections.unmodifiableList(list);
}
/**
*
* @param uuid
* @return
*/
@Deprecated
public Reaction getReaction(String uuid) {
for (Reaction r : list) {
if (r.getUuid().equalsIgnoreCase(uuid)) {
return r;
}
}
return null;
}
/**
* Returns the number of Reactions.
*
* @return the number of reactions
*/
public int size() {
return list.size();
}
/**
* Checks if a reaction exists.
*
* @param input the reaction to check
* @return true if the reaction exists, false otherwise
*/
public boolean exists(Reaction input) {
if (input != null) {
for (Iterator<Reaction> it = list.iterator(); it.hasNext();) {
Reaction reaction = it.next();
if (input.equals(reaction)) {
return true;
}
}
}
return false;
}
@Override
public List<Reaction> findAll() {
Collections.sort(list, new ReactionNameComparator());
return Collections.unmodifiableList(list);
}
@Override
public List<Reaction> findByName(String name) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public Reaction findOne(String uuid) {
for (Reaction r : list) {
if (r.getUuid().equalsIgnoreCase(uuid)) {
return r;
}
}
return null;
}
@Override
public boolean create(Reaction item) {
try {
add(item);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public boolean delete(Reaction item) {
try {
remove(item);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public boolean delete(String uuid) {
return delete(findOne(uuid));
}
@Override
public Reaction modify(String uuid, Reaction data) {
try {
delete(uuid);
data.setUuid(uuid);
create(data);
return data;
} catch (Exception e) {
LOG.error("Cannot modify reaction", e);
return null;
}
}
@Override
public Reaction copy(Reaction rea) {
try {
Reaction r = findOne(rea.getUuid());
Reaction newOne = (Reaction) r.clone();
create(newOne);
return newOne;
} catch (Exception e) {
LOG.error("Cannot copy reaction", e);
return null;
}
}
@Override
public void deleteAll() {
try {
for (Reaction r : findAll()) {
delete(r);
}
} catch (Exception e) {
} finally {
list.clear();
}
}
/**
* This class compares two reactions given their names.
*
*/
class ReactionNameComparator implements Comparator<Reaction> {
@Override
public int compare(Reaction r1, Reaction r2) {
return r1.getTrigger().getName().compareTo(r2.getTrigger().getName());
}
}
}