/** * * 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.environment.impl; import com.freedomotic.app.Freedomotic; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.UUID; import org.apache.commons.io.FileUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.freedomotic.environment.EnvironmentLogic; import com.freedomotic.environment.EnvironmentRepository; import com.freedomotic.exceptions.RepositoryException; import com.freedomotic.model.environment.Environment; import com.freedomotic.model.environment.Zone; import com.freedomotic.persistence.FreedomXStream; import com.freedomotic.persistence.XmlPreprocessor; import com.freedomotic.settings.AppConfig; import com.freedomotic.settings.Info; import com.freedomotic.things.EnvObjectLogic; import com.freedomotic.things.ThingRepository; import com.freedomotic.util.SerialClone; import com.freedomotic.util.UidGenerator; import com.google.inject.Inject; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.XStreamException; /** * Repository to manage the {@link Environment} loaded from filesystem * * @author Enrico Nicoletti */ class EnvironmentRepositoryImpl implements EnvironmentRepository { private static final Logger LOG = LoggerFactory.getLogger(EnvironmentRepositoryImpl.class.getName()); // Dependencies private final AppConfig appConfig; private final EnvironmentPersistenceFactory environmentPersistenceFactory; private final ThingRepository thingsRepository; private File defaultEnvironmentFile; private static final String ENVIRONMENT_FILE_EXTENSION = ".xenv"; // The Environments cache private static final List<EnvironmentLogic> environments = new ArrayList<>(); /** * Creates a new repository * * @param appConfig needed to get the default environment * @param environmentPersistenceFactory creates the right environment loader * to manage the environment persistence * @throws com.freedomotic.exceptions.RepositoryException */ @Inject EnvironmentRepositoryImpl( AppConfig appConfig, ThingRepository thingsRepository, EnvironmentPersistenceFactory environmentPersistenceFactory) throws RepositoryException { this.appConfig = appConfig; this.thingsRepository = thingsRepository; this.environmentPersistenceFactory = environmentPersistenceFactory; } @Override public void initFromDefaultFolder() throws RepositoryException { String envFilePath = appConfig.getProperty("KEY_ROOM_XML_PATH"); this.defaultEnvironmentFile = new File(Info.PATHS.PATH_ENVIRONMENTS_FOLDER + File.separator + envFilePath); File defaultEnvironmentFolder = getDefaultEnvironmentFolder(); this.init(defaultEnvironmentFolder); } /** * Reads environment data from filesystem using the * {@link EnvironmentPersistence} class * * @throws RepositoryException */ @Override public synchronized void init(File folder) throws RepositoryException { EnvironmentPersistence environmentPersistence = environmentPersistenceFactory.create(folder); Collection<Environment> loadedPojo = environmentPersistence.loadAll(); this.deleteAll(); for (Environment env : loadedPojo) { EnvironmentLogic envLogic = new EnvironmentLogic(); envLogic.setPojo(env); envLogic.setSource(new File(folder + "/" + env.getUUID() + ENVIRONMENT_FILE_EXTENSION)); // activate this environment this.create(envLogic); } List<EnvObjectLogic> loadedThings = thingsRepository.loadAll(findAll().get(0).getObjectFolder()); for (EnvObjectLogic thing : loadedThings) { // stores the thing in repository. Important, otherwise it will be not visible in the environment thingsRepository.create(thing); } } /** * * * @return @throws RepositoryException */ private File getDefaultEnvironmentFolder() throws RepositoryException { File folder = this.defaultEnvironmentFile.getParentFile(); if (folder == null) { throw new RepositoryException("Application configuration does not specify the default environment to load." + " A 'KEY_ROOM_XML_PATH' property is expected"); } if (!folder.exists()) { throw new RepositoryException( "Folder \"" + folder + "\" doesn't exist. Cannot load default " + "environment from \"" + this.defaultEnvironmentFile.getAbsolutePath() + "\""); } else if (!folder.isDirectory()) { throw new RepositoryException( "Environment folder \"" + folder.getAbsolutePath() + "\" is supposed to be a directory"); } return folder; } /** * * @param folder * @throws RepositoryException */ @RequiresPermissions("environments:save") @Override public void saveEnvironmentsToFolder(File folder) throws RepositoryException { if (environments.isEmpty()) { LOG.warn("There is no environment to persist. Folder \"{}\" will not be altered", folder.getAbsolutePath()); return; } if (folder.exists() && !folder.isDirectory()) { throw new RepositoryException("\"" + folder.getAbsoluteFile() + "\" is not a valid environment folder. Skipped"); } try { 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() + ENVIRONMENT_FILE_EXTENSION; save(environment, new File(folder + "/" + fileName)); } } catch (IOException e) { throw new RepositoryException(e.getCause()); } // save environment's things if (appConfig.getBooleanProperty("KEY_OVERRIDE_OBJECTS_ON_EXIT", false)) { try { thingsRepository.saveAll(findAll().get(0).getObjectFolder()); } catch (RepositoryException ex) { LOG.error("Cannot save objects into \"{}\"", findAll().get(0).getObjectFolder().getAbsolutePath(), Freedomotic.getStackTraceInfo(ex)); } } } /** * * * @param folder * @throws RepositoryException */ private static void deleteEnvFiles(File folder) throws RepositoryException { if ((folder == null) || !folder.isDirectory()) { throw new IllegalArgumentException("Unable to delete environment files in a null or not valid folder"); } // this filter only returns thing files FileFilter objectFileFileter = new FileFilter() { @Override public boolean accept(File file) { if (file.isFile() && file.getName().endsWith(ENVIRONMENT_FILE_EXTENSION)) { return true; } else { return false; } } }; File[] files = folder.listFiles(objectFileFileter); for (File file : files) { boolean deleted = file.delete(); if (!deleted) { throw new RepositoryException("Unable to delete file " + file.getAbsoluteFile()); } } } /** * Loads all objects file filesystem folder and adds the objects to the * list. * * @param folder * @param makeUnique * @return * @throws com.freedomotic.exceptions.RepositoryException * @deprecated */ @Deprecated private boolean loadEnvironmentsFromDir(File folder, boolean makeUnique) throws RepositoryException { if (folder == null) { throw new RepositoryException("Cannot load environments from a null folder"); } environments.clear(); // This filter only returns env files FileFilter envFileFilter = new FileFilter() { @Override public boolean accept(File file) { return file.isFile() && file.getName().endsWith(ENVIRONMENT_FILE_EXTENSION); } }; File[] files = folder.listFiles(envFileFilter); for (File file : files) { try { EnvironmentLogic envLogic = loadEnvironmentFromFile(file); if (envLogic != null) { add(envLogic, false); } } catch (RepositoryException re) { LOG.error("Cannot add environment from file \"{}\"", file.getAbsolutePath(), Freedomotic.getStackTraceInfo(re)); } } // Load all objects in this environment thingsRepository.loadAll(EnvironmentRepositoryImpl.getEnvironments().get(0).getObjectFolder()); // TODO: this return value makes no sense return true; } /** * Adds an environment. You can use EnvObjectPersistance.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") @Deprecated private 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 = new EnvironmentLogic(); //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 // force to assign a new random and unique UUID to every zone for (Zone z : pojoCopy.getZones()) { z.setUuid(UUID.randomUUID().toString()); } //should be the last called after using setters on envLogic.getPojo() envLogic.setPojo(pojoCopy); } envLogic.init(); environments.add(envLogic); return envLogic; } /** * * @param input */ @RequiresPermissions("environments:delete") @Deprecated private void remove(EnvironmentLogic input) { for (EnvObjectLogic obj : thingsRepository.findByEnvironment(input)) { thingsRepository.delete(obj); } environments.remove(input); this.removeEnvironmentFile(input.getSource()); input.clear(); } private boolean removeEnvironmentFile(File environmentFile) { if (environmentFile == null) { LOG.warn("No file defined in this environment, so no deletion occurs."); return false; } if (environmentFile.exists()) { try { FileUtils.forceDelete(environmentFile); return true; } catch (IOException e) { LOG.warn("Error while removing file \"{}\", please try manually.", environmentFile.getAbsolutePath(), Freedomotic.getStackTraceInfo(e)); return false; } } else { LOG.warn("File \"{}\" does not exist", environmentFile.getAbsolutePath()); return false; } } /** * */ @RequiresPermissions("environments:delete") @Override public void deleteAll() { try { for (EnvironmentLogic el : environments) { delete(el); } } catch (Exception e) { LOG.error(Freedomotic.getStackTraceInfo(e)); } finally { environments.clear(); } } /** * Saves an environment to file. * * @param env environment to save * @param file file to save the environment * @throws IOException */ private void save(EnvironmentLogic env, File file) throws IOException { try { EnvironmentPersistence environmentPersistence = environmentPersistenceFactory.create(file.getParentFile()); environmentPersistence.persist(env.getPojo()); } catch (RepositoryException ex) { LOG.error(Freedomotic.getStackTraceInfo(ex)); } } /** * * @param env * @param folder * @throws IOException */ @RequiresPermissions("environments:save") @Override public void saveAs(EnvironmentLogic env, File folder) throws IOException { LOG.info("Serializing new environment to \"{}\"", folder); save(env, new File(folder + "/" + env.getPojo().getUUID() + ENVIRONMENT_FILE_EXTENSION)); } /** * * @param file * @throws RepositoryException * @deprecated */ @Deprecated @Override public EnvironmentLogic loadEnvironmentFromFile(final File file) throws RepositoryException { LOG.info("Loading environment from file \"{}\"", file.getAbsolutePath()); XStream xstream = FreedomXStream.getXstream(); //validate the object against a predefined DTD String xml; try { xml = XmlPreprocessor.validate(file, Info.PATHS.PATH_CONFIG_FOLDER + "/validator/environment.dtd"); } catch (IOException ex) { throw new RepositoryException(ex.getMessage(), ex); } Environment pojo = null; try { pojo = (Environment) xstream.fromXML(xml); } catch (XStreamException e) { throw new RepositoryException("XML parsing error. Readed XML is \n" + xml, e); } EnvironmentLogic envLogic = new EnvironmentLogic(); if (pojo == null) { throw new IllegalStateException("Object data cannot be null at this stage"); } envLogic.setPojo(pojo); envLogic.setSource(file); LOG.info("Environment \"" + envLogic.getPojo().getName() + "\" loaded"); return envLogic; } /** * * @return */ @Deprecated @RequiresPermissions("environments:read") private static List<EnvironmentLogic> getEnvironments() { return environments; } /** * * @param UUID * @return */ @RequiresPermissions("environments:read") @Deprecated private 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; } @Override @RequiresPermissions("environments:read") public List<EnvironmentLogic> findAll() { return getEnvironments(); } @Override @RequiresPermissions("environments:read") public List<EnvironmentLogic> findByName(String name) { List<EnvironmentLogic> el = new ArrayList<>(); for (EnvironmentLogic e : findAll()) { if (e.getPojo().getName().equalsIgnoreCase(name)) { el.add(e); } } return el; } @Override @RequiresPermissions("environments:read") public EnvironmentLogic findOne(String uuid) { return getEnvByUUID(uuid); } @Override @RequiresPermissions("environments:create") public boolean create(EnvironmentLogic item) { try { add(item, false); return true; } catch (Exception e) { LOG.error(Freedomotic.getStackTraceInfo(e)); return false; } } @Override @RequiresPermissions("environments:delete") public boolean delete(EnvironmentLogic item) { if (environments.isEmpty()) { LOG.warn("There are no environments to delete"); return false; } if (item.getSource() != null && environments.contains(item) && this.defaultEnvironmentFile.getAbsolutePath().equalsIgnoreCase(item.getSource().getAbsolutePath())) { LOG.info("Default environment cannot be deleted \"{}\"", item.getSource().getAbsolutePath()); return false; } try { remove(item); return true; } catch (Exception e) { LOG.error(Freedomotic.getStackTraceInfo(e)); return false; } } @Override @RequiresPermissions("environments:delete") public boolean delete(String uuid) { return delete(findOne(uuid)); } @Override @RequiresPermissions("environments:update") public EnvironmentLogic modify(String uuid, EnvironmentLogic data) { try { delete(uuid); data.getPojo().setUUID(uuid); create(data); return data; } catch (Exception e) { LOG.error(Freedomotic.getStackTraceInfo(e)); return null; } } @Override @RequiresPermissions("environments:create") public EnvironmentLogic copy(EnvironmentLogic env) { return add(env, true); } }