
org.openremote.manager.app.ConfigurationService Maven / Gradle / Ivy
/*
* Copyright 2022, OpenRemote Inc.
*
* See the CONTRIBUTORS.txt file in the distribution for a
* full listing of individual contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package org.openremote.manager.app;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.openremote.container.persistence.PersistenceService;
import org.openremote.container.timer.TimerService;
import org.openremote.container.util.CodecUtil;
import org.openremote.manager.security.ManagerIdentityService;
import org.openremote.manager.web.ManagerWebService;
import org.openremote.model.Container;
import org.openremote.model.ContainerService;
import org.openremote.model.file.FileInfo;
import org.openremote.model.manager.ManagerAppConfig;
import org.openremote.model.manager.ManagerAppRealmConfig;
import org.openremote.model.util.TextUtil;
import org.openremote.model.util.ValueUtil;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import static org.openremote.container.util.MapAccess.getString;
import static org.openremote.manager.web.ManagerWebService.OR_CUSTOM_APP_DOCROOT;
import static org.openremote.manager.web.ManagerWebService.OR_CUSTOM_APP_DOCROOT_DEFAULT;
public class ConfigurationService implements ContainerService {
public static final String OR_MAP_SETTINGS_PATH = "OR_MAP_SETTINGS_PATH";
public static final String OR_MAP_SETTINGS_PATH_DEFAULT = "manager/src/map/mapsettings.json";
public static final String OR_MAP_TILES_PATH = "OR_MAP_TILES_PATH";
public static final String OR_MAP_TILES_PATH_DEFAULT = "manager/src/map/mapdata.mbtiles";
protected ManagerIdentityService identityService;
protected PersistenceService persistenceService;
protected Path pathPublicRoot;
private static final Logger LOG = Logger.getLogger(ConfigurationService.class.getName());
protected Path mapTilesPath;
protected Path mapSettingsPath;
protected Path managerConfigPath;
protected AtomicReference managerAppConfig;
@Override
public void init(Container container) throws Exception {
identityService = container.getService(ManagerIdentityService.class);
persistenceService = container.getService(PersistenceService.class);
pathPublicRoot = Paths.get(getString(container.getConfig(), OR_CUSTOM_APP_DOCROOT, OR_CUSTOM_APP_DOCROOT_DEFAULT));
container.getService(ManagerWebService.class).addApiSingleton(
new ConfigurationResourceImpl(
container.getService(TimerService.class),
identityService, this)
);
// Retrieve default configuration files; Try to find all possible default locations of each file, and then
// return the first one. Since the stream maintains ordering, we use the first available one, since they're placed
// below in order of most importance. Throw an Exception if no default file could be found.
mapSettingsPath = Stream.of(getPersistedMapConfigPath().toString(), getString(container.getConfig(), OR_MAP_SETTINGS_PATH, OR_MAP_SETTINGS_PATH_DEFAULT), "/opt/map/mapsettings.json", OR_MAP_SETTINGS_PATH_DEFAULT)
.map(Path::of)
.map(Path::toAbsolutePath)
.filter(Files::isRegularFile)
.findFirst().orElse(null);
mapTilesPath = Stream.of(getString(container.getConfig(), OR_MAP_TILES_PATH, OR_MAP_TILES_PATH_DEFAULT), "/deployment/map/mapdata.mbtiles", "/opt/map/mapdata.mbtiles", OR_MAP_TILES_PATH_DEFAULT)
.map(Path::of)
.map(Path::toAbsolutePath)
.filter(Files::isRegularFile)
.findFirst().orElse(null);
managerConfigPath = Stream.of(getPersistedManagerConfigPath(), pathPublicRoot.resolve("manager_config.json"))
.map(Path::toAbsolutePath)
.filter(Files::isRegularFile)
.findFirst().orElse(null);
if (mapSettingsPath == null) {
LOG.warning("Could not find map settings");
return;
}
LOG.info("Configuration Service Used files:");
LOG.info("\t- manager_config.json: " + managerConfigPath);
LOG.info("\t- mapsettings.json: " + mapSettingsPath);
LOG.info("\t- mapdata.mbtiles: " + mapTilesPath);
}
@Override
public void start(Container container) throws Exception {
}
@Override
public void stop(Container container) throws Exception {
/* code not overridden yet */
}
@Override
public String toString() {
return "ConfigurationService{" +
"mapTilesPath=" + mapTilesPath +
", mapSettingsPath=" + mapSettingsPath +
", managerConfigPath=" + managerConfigPath +
'}';
}
public ObjectNode getMapConfig() {
if (mapSettingsPath == null) {
return null;
}
try {
return (ObjectNode) ValueUtil.JSON.readTree(mapSettingsPath.toFile());
} catch (IOException e) {
LOG.severe("Could not read map_settings.json from " + mapSettingsPath);
}
return null;
}
public void saveMapConfig(ObjectNode mapConfiguration) throws RuntimeException {
LOG.log(Level.INFO, "Saving map_settings.json to: " + getPersistedMapConfigPath());
try {
Path p = getPersistedMapConfigPath();
File file = p.toAbsolutePath().toFile();
if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
Files.writeString(p, ValueUtil.JSON.writeValueAsString(mapConfiguration), StandardCharsets.UTF_8);
// Ensure mapSettingsPath is now pointing to persistence path
mapSettingsPath = p;
} catch (Exception exception) {
String msg = "Error saving map_settings.json: msg=" + exception.getMessage();
LOG.log(Level.WARNING, msg);
throw new IllegalStateException(msg);
}
}
public Path getMapTilesPath() {
return mapTilesPath;
}
public ManagerAppConfig getManagerConfig() {
if (managerAppConfig != null) {
return managerAppConfig.get();
}
if (managerConfigPath == null) {
return null;
}
try {
String managerConfigStr = Files.readString(managerConfigPath, StandardCharsets.UTF_8);
return ValueUtil.parse(managerConfigStr, ManagerAppConfig.class).orElse(null);
} catch (Exception e) {
LOG.severe("Could not read manager_config.json from " + managerConfigPath);
return null;
}
}
public void saveManagerConfig(ManagerAppConfig managerAppConfig) throws Exception {
LOG.log(Level.INFO, "Saving manager_config.json to: " + getPersistedManagerConfigPath());
try {
// Check references to images
managerAppConfig = checkAndFixImageReferences(managerAppConfig);
Path p = getPersistedManagerConfigPath();
File file = p.toAbsolutePath().toFile();
if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
Files.writeString(p, ValueUtil.JSON.writeValueAsString(managerAppConfig), StandardCharsets.UTF_8);
// Ensure managerConfigPath is now pointing to persistence path
managerConfigPath = p;
if (this.managerAppConfig == null) {
this.managerAppConfig = new AtomicReference<>(managerAppConfig);
} else {
this.managerAppConfig.set(managerAppConfig);
}
} catch (Exception exception) {
String msg = "Error saving manager_config.json: msg=" + exception.getMessage();
LOG.log(Level.WARNING, msg);
throw new Exception(msg);
}
}
public void saveManagerConfigImage(String path, FileInfo fileInfo) throws Exception {
LOG.log(Level.INFO, "Saving image in manager_config.json: " + fileInfo);
path = path.replace("/images/", "");
path = path.charAt(0) == '/' ? path.substring(1) : path;
Path resolvedPath = Path.of(path);
resolvedPath = getPersistedManagerConfigImagePath().resolve(resolvedPath);
Path filePath = getPersistedManagerConfigImagePath().resolve(path);
File file = filePath.toAbsolutePath().toFile();
try {
boolean isValid = resolvedPath.toFile().getCanonicalPath().contains(getPersistedManagerConfigImagePath().toFile().getCanonicalPath() + File.separator);
if (!isValid) {
String msg = "Failed to save manager config image path outside permitted directory: " + resolvedPath;
LOG.warning(msg);
throw new Exception("Failed to save manager config image path outside permitted directory: " + resolvedPath);
}
if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
Files.write(filePath, CodecUtil.decodeBase64(fileInfo.getContents()));
} catch (Exception exception) {
String msg = "Error saving image in manager_config.json '" + filePath + "': msg=" + exception.getMessage();
LOG.log(Level.WARNING, msg);
throw new Exception(msg);
}
}
public Optional getManagerConfigImage(String filename) {
File file = getPersistedManagerConfigImagePath().resolve(filename).toFile();
String checkPath;
try {
if (file.isFile()) {
checkPath = getPersistedManagerConfigImagePath().toFile().getCanonicalPath();
} else {
// fallback to OR_CUSTOM_APP_DOCROOT
file = pathPublicRoot.resolve("images").resolve(filename).toFile();
checkPath = pathPublicRoot.toFile().getCanonicalPath();
}
//Check if the file retrieved is somewhere within the allowed directory.
boolean isValid = file.getCanonicalPath().contains(checkPath + File.separator);
if (!isValid) return Optional.empty();
} catch (IOException e) {
return Optional.empty();
}
return file.isFile() ? Optional.of(file) : Optional.empty();
}
protected Path getPersistedManagerConfigPath() {
return persistenceService.getStorageDir().resolve("manager").resolve("manager_config.json");
}
protected Path getPersistedMapConfigPath() {
return persistenceService.getStorageDir().resolve("manager").resolve("mapsettings.json");
}
protected Path getPersistedManagerConfigImagePath() {
return this.persistenceService.resolvePath("manager").resolve("images");
}
protected ManagerAppConfig checkAndFixImageReferences(ManagerAppConfig managerAppConfig) {
if (!getPersistedManagerConfigImagePath().toFile().exists()) getPersistedManagerConfigImagePath().toFile().mkdirs();
if (managerAppConfig.getRealms() != null && !managerAppConfig.getRealms().isEmpty()) {
managerAppConfig.getRealms().values().forEach(managerAppRealmConfig -> {
if (!TextUtil.isNullOrEmpty(managerAppRealmConfig.getLogo())) {
managerAppRealmConfig.setLogo(fixImageRef(managerAppRealmConfig.getLogo()));
}
if (!TextUtil.isNullOrEmpty(managerAppRealmConfig.getLogoMobile())) {
managerAppRealmConfig.setLogo(fixImageRef(managerAppRealmConfig.getLogoMobile()));
}
if (!TextUtil.isNullOrEmpty(managerAppRealmConfig.getFavicon())) {
managerAppRealmConfig.setLogo(fixImageRef(managerAppRealmConfig.getFavicon()));
}
});
}
return managerAppConfig;
}
protected String fixImageRef(String image) {
if (!image.isBlank()) {
image = image.replace("/api/master/configuration/manager/image/", "");
image = image.replace("images/", "");
image = image.charAt(0) == '/' ? image.substring(1) : image;
Path imagePath = Path.of(image);
Path persistedImagePath = getPersistedManagerConfigImagePath().resolve(imagePath).toAbsolutePath();
Path path = pathPublicRoot.resolve("images").resolve(imagePath).toAbsolutePath();
if (!Files.isRegularFile(persistedImagePath)) {
if (Files.isRegularFile(path)) {
try {
// Image doesn't exist in persisted path but does exist in doc root so copy it
Files.copy(path, persistedImagePath);
// Change the reference in the config to the typical API-like reference:
image = "/api/master/configuration/manager/image/" + imagePath;
} catch (Exception e) {
LOG.warning("Error occurred whilst copying manager config image to persisted path: " + imagePath);
}
} else {
LOG.warning("manager_config.json image reference doesn't exist: " + imagePath);
}
}
}
return image;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy