/**
*
* Copyright (c) 2009-2013 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 it.freedomotic.environment;
import com.google.inject.Inject;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
import it.freedomotic.api.Client;
import it.freedomotic.app.Freedomotic;
import it.freedomotic.exceptions.DaoLayerException;
import it.freedomotic.model.environment.Environment;
import it.freedomotic.model.environment.Zone;
import it.freedomotic.model.object.Behavior;
import it.freedomotic.objects.EnvObjectLogic;
import it.freedomotic.objects.EnvObjectPersistence;
import it.freedomotic.persistence.FreedomXStream;
import it.freedomotic.plugins.ClientStorage;
import it.freedomotic.plugins.ObjectPluginPlaceholder;
import it.freedomotic.reactions.CommandPersistence;
import it.freedomotic.reactions.TriggerPersistence;
import it.freedomotic.security.Auth;
import it.freedomotic.util.DOMValidateDTD;
import it.freedomotic.util.Info;
import it.freedomotic.util.SerialClone;
import it.freedomotic.util.UidGenerator;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.shiro.authz.annotation.RequiresPermissions;
/**
*
* @author Enrico
*/
public final class EnvironmentPersistence {
private static List<EnvironmentLogic> environments = new ArrayList<EnvironmentLogic>();
private ClientStorage clientStorage;
@Inject
public EnvironmentPersistence(ClientStorage clientStorage) {
//disable instance creation
this.clientStorage = clientStorage;
}
@RequiresPermissions("environments:save")
public static void saveEnvironmentsToFolder(File folder) throws DaoLayerException {
if (environments.isEmpty()) {
LOG.warning("There is no environment to persist, " + folder.getAbsolutePath() + " will not be altered.");
return;
}
if (folder.exists() && !folder.isDirectory()) {
throw new DaoLayerException(folder.getAbsoluteFile() + " is not a valid environment folder. Skipped");
}
createFolderStructure(folder);
deleteEnvFiles(folder);
try {
// Create file
StringBuilder summary = new StringBuilder();
//print an header for the index.txt file
summary.append("#Filename \t\t #EnvName").append("\n");
for (EnvironmentLogic environment : environments) {
String uuid = environment.getPojo().getUUID();
if ((uuid == null) || uuid.isEmpty()) {
environment.getPojo().setUUID(UUID.randomUUID().toString());
}
String fileName = environment.getPojo().getUUID() + ".xenv";
save(environment,
new File(folder + "/" + fileName));
summary.append(fileName).append("\t").append(environment.getPojo().getName()).append("\n");
}
//writing a summary .txt file with the list of commands in this folder
FileWriter fstream = new FileWriter(folder + "/index.txt");
BufferedWriter indexfile = new BufferedWriter(fstream);
indexfile.write(summary.toString());
//Close the output stream
indexfile.close();
} catch (IOException e) {
throw new DaoLayerException(e.getCause());
}
}
private static void deleteEnvFiles(File folder)
throws DaoLayerException {
if ((folder == null) || !folder.isDirectory()) {
throw new IllegalArgumentException("Unable to delete environment files in a null or not valid folder");
}
// This filter only returns object files
FileFilter objectFileFileter =
new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isFile() && file.getName().endsWith(".xenv")) {
return true;
} else {
return false;
}
}
};
File[] files = folder.listFiles(objectFileFileter);
for (File file : files) {
boolean deleted = file.delete();
if (!deleted) {
throw new DaoLayerException("Unable to delete file " + file.getAbsoluteFile());
}
}
}
/**
* Loads all objects file filesystem folder and adds the objects to the list
*
* @param folder
* @param makeUnique
* @deprecated
*/
@Deprecated
public synchronized static boolean loadEnvironmentsFromDir(File folder, boolean makeUnique)
throws DaoLayerException {
if (folder == null) {
throw new DaoLayerException("Cannot");
}
environments.clear();
boolean check = true;
// This filter only returns env files
FileFilter envFileFilter =
new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isFile() && file.getName().endsWith(".xenv")) {
return true;
} else {
return false;
}
}
};
File[] files = folder.listFiles(envFileFilter);
for (File file : files) {
loadEnvironmentFromFile(file);
}
if (check) {
EnvObjectPersistence.loadObjects(EnvironmentPersistence.getEnvironments().get(0).getObjectFolder(),
false);
}
return check;
}
/**
* Add an environment. You can use EnvObjectPersistnce.MAKE_UNIQUE to create
* an object that will surely be unique. Beware this means it is created
* with defensive copy of the object in input and name, protocol, address
* and UUID are reset to a default value.
*
* @param obj the environment to add
* @param MAKE_UNIQUE can be true or false. Creates a defensive copy
* reference to the object in input.
* @return A pointer to the newly created environment object
*/
@RequiresPermissions("environments:create")
public static EnvironmentLogic add(final EnvironmentLogic obj, boolean MAKE_UNIQUE) {
if ((obj == null)
|| (obj.getPojo() == null)
|| (obj.getPojo().getName() == null)
|| obj.getPojo().getName().isEmpty()) {
throw new IllegalArgumentException("This is not a valid environment");
}
EnvironmentLogic envLogic = obj;
if (MAKE_UNIQUE) {
envLogic = Freedomotic.INJECTOR.getInstance(EnvironmentLogic.class);
//defensive copy to not affect the passed object with the changes
Environment pojoCopy = SerialClone.clone(obj.getPojo());
pojoCopy.setName(obj.getPojo().getName() + "-" + UidGenerator.getNextStringUid());
pojoCopy.setUUID(""); // force to assign a new random and unique UUID
//should be the last called after using setters on envLogic.getPojo()
envLogic.setPojo(pojoCopy);
}
envLogic.init();
// if (!envList.contains(envLogic)) {
environments.add(envLogic);
//envLogic.setChanged(true);
// } else {
// throw new RuntimeException("Cannot add the same environment more than one time");
// }
return envLogic;
}
public EnvObjectLogic join(String clazz, String name, String protocol, String address) {
EnvObjectLogic loaded = null;
ObjectPluginPlaceholder objectPlugin = (ObjectPluginPlaceholder) clientStorage.get(clazz);
if (objectPlugin == null) {
LOG.warning("Doesn't exist an object class called " + clazz);
return null;
}
File templateFile = objectPlugin.getTemplate();
try {
loaded = EnvObjectPersistence.loadObject(templateFile);
} catch (DaoLayerException ex) {
LOG.severe("Cannot join an object taken from template file " + templateFile);
}
//changing the name and other properties invalidates related trigger and commands
//call init() again after this changes
if ((name != null) && !name.isEmpty()) {
loaded.getPojo().setName(name);
} else {
loaded.getPojo().setName(protocol);
}
loaded = EnvObjectPersistence.add(loaded, EnvObjectPersistence.MAKE_UNIQUE);
loaded.getPojo().setProtocol(protocol);
loaded.getPojo().setPhisicalAddress(address);
loaded.setRandomLocation();
//set the PREFERRED MAPPING of the protocol plugin (if any is defined in its manifest)
Client addon = clientStorage.getClientByProtocol(protocol);
if (addon != null) {
for (int i = 0; i < addon.getConfiguration().getTuples().size(); i++) {
Map tuple = addon.getConfiguration().getTuples().getTuple(i);
String regex = (String) tuple.get("object.class");
if ((regex != null) && clazz.matches(regex)) {
//map object behaviors to hardware triggers
for (Behavior behavior : loaded.getPojo().getBehaviors()) {
String triggerName = (String) tuple.get(behavior.getName());
if (triggerName != null) {
loaded.addTriggerMapping(TriggerPersistence.getTrigger(triggerName),
behavior.getName());
}
}
for (String action : loaded.getPojo().getActions().stringPropertyNames()) {
String commandName = (String) tuple.get(action);
if (commandName != null) {
loaded.setAction(action,
CommandPersistence.getHardwareCommand(commandName));
}
}
}
}
}
return loaded;
}
public static void remove(EnvironmentLogic input) {
for (EnvObjectLogic obj : EnvObjectPersistence.getObjectByEnvironment(input.getPojo().getUUID())) {
EnvObjectPersistence.remove(obj);
}
environments.remove(input);
input.clear();
}
@RequiresPermissions("environments:delete")
public static void clear() {
try {
environments.clear();
} catch (Exception e) {
}
}
public static int size() {
return environments.size();
}
private static void createFolderStructure(File folder) {
if (!folder.exists()) {
folder.mkdir();
new File(folder + "/data").mkdir();
new File(folder + "/data/obj").mkdir();
new File(folder + "/data/rea").mkdir();
new File(folder + "/data/trg").mkdir();
new File(folder + "/data/cmd").mkdir();
new File(folder + "/data/resources").mkdir();
}
}
private static void save(EnvironmentLogic env, File file)
throws IOException {
XStream xstream = FreedomXStream.getEnviromentXstream();
for (Zone zone : env.getPojo().getZones()) {
zone.setObjects(null);
}
String xml = xstream.toXML(env.getPojo());
FileWriter fstream;
BufferedWriter out = null;
try {
LOG.info("Serializing environment to " + file);
fstream = new FileWriter(file);
out = new BufferedWriter(fstream);
out.write(xml);
//Close the output stream
LOG.info("Application environment " + env.getPojo().getName()
+ " succesfully serialized");
} catch (IOException ex) {
Logger.getLogger(Environment.class.getName()).log(Level.SEVERE, null, ex);
} finally {
out.close();
}
}
@RequiresPermissions("environments:save")
public static void saveAs(EnvironmentLogic env, File folder) throws IOException {
LOG.config("Serializing new environment to " + folder);
createFolderStructure(folder);
save(env,
new File(folder + "/" + env.getPojo().getUUID() + ".xenv"));
//TODO: Freedomotic.environment.getPojo().setObjectsFolder()
// EnvObjectPersistence.saveObjects(new File(folder + "/objects"));
}
/**
*
* @param file
* @throws DaoLayerException
* @deprecated
*/
@Deprecated
public static void loadEnvironmentFromFile(final File file)
throws DaoLayerException {
XStream xstream = FreedomXStream.getXstream();
//validate the object against a predefined DTD
String xml;
try {
xml = DOMValidateDTD.validate(file, Info.getApplicationPath() + "/config/validator/environment.dtd");
} catch (IOException ex) {
throw new DaoLayerException(ex.getMessage(), ex);
}
Environment pojo = null;
try {
pojo = (Environment) xstream.fromXML(xml);
} catch (XStreamException e) {
throw new DaoLayerException("XML parsing error. Readed XML is \n" + xml, e);
}
EnvironmentLogic envLogic = Freedomotic.INJECTOR.getInstance(EnvironmentLogic.class);
if (pojo == null) {
throw new IllegalStateException("Object data cannot be null at this stage");
}
envLogic.setPojo(pojo);
envLogic.setSource(file);
// next line is commented as the method init() is called in the add()
//envLogic.init();
add(envLogic, false);
}
@RequiresPermissions("environments:read")
public static List<EnvironmentLogic> getEnvironments() {
return environments;
}
@RequiresPermissions("environments:read")
public static EnvironmentLogic getEnvByUUID(String UUID) {
// if (auth.isPermitted("environments:read:" + UUID)) {
for (EnvironmentLogic env : environments) {
if (env.getPojo().getUUID().equals(UUID)) {
return env;
}
}
// }
return null;
}
private static final Logger LOG = Logger.getLogger(EnvironmentPersistence.class.getName());
}