de.fraunhofer.iosb.ilt.frostserver.plugin.modelloader.PluginModelLoader Maven / Gradle / Ivy
/*
* Copyright (C) 2024 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131
* Karlsruhe, Germany.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package de.fraunhofer.iosb.ilt.frostserver.plugin.modelloader;
import static de.fraunhofer.iosb.ilt.frostserver.model.ext.TypeReferencesHelper.TYPE_REFERENCE_MAP;
import static de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.LiquibaseHelper.CHANGE_SET_NAME;
import static de.fraunhofer.iosb.ilt.frostserver.plugin.modelloader.ModelLoaderSettings.PLUGIN_NAME;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.fraunhofer.iosb.ilt.frostserver.model.ModelRegistry;
import de.fraunhofer.iosb.ilt.frostserver.model.loader.DefEntityProperty;
import de.fraunhofer.iosb.ilt.frostserver.model.loader.DefEntityType;
import de.fraunhofer.iosb.ilt.frostserver.model.loader.DefModel;
import de.fraunhofer.iosb.ilt.frostserver.persistence.PersistenceManager;
import de.fraunhofer.iosb.ilt.frostserver.persistence.PersistenceManagerFactory;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.JooqPersistenceManager;
import de.fraunhofer.iosb.ilt.frostserver.plugin.coremodel.CoreModelSettings;
import de.fraunhofer.iosb.ilt.frostserver.plugin.coremodel.PluginCoreModel;
import de.fraunhofer.iosb.ilt.frostserver.service.InitResult;
import de.fraunhofer.iosb.ilt.frostserver.service.PluginModel;
import de.fraunhofer.iosb.ilt.frostserver.service.PluginRootDocument;
import de.fraunhofer.iosb.ilt.frostserver.service.Service;
import de.fraunhofer.iosb.ilt.frostserver.service.ServiceRequest;
import de.fraunhofer.iosb.ilt.frostserver.settings.CoreSettings;
import de.fraunhofer.iosb.ilt.frostserver.settings.Settings;
import de.fraunhofer.iosb.ilt.frostserver.util.LiquibaseUser;
import de.fraunhofer.iosb.ilt.frostserver.util.SecurityModel;
import de.fraunhofer.iosb.ilt.frostserver.util.StringHelper;
import de.fraunhofer.iosb.ilt.frostserver.util.exception.UpgradeFailedException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author scf
*/
public class PluginModelLoader implements PluginRootDocument, PluginModel, LiquibaseUser {
private static final Logger LOGGER = LoggerFactory.getLogger(PluginModelLoader.class.getName());
private CoreSettings settings;
private boolean enabled;
private boolean fullyInitialised;
private String idTypeDefault;
private List modelDefinitions = new ArrayList<>();
private final Map primaryKeys = new HashMap<>();
private String modelPath;
private final List modelFiles = new ArrayList<>();
private String liquibasePath;
private final List liquibaseFiles = new ArrayList<>();
private String securityPath;
private final List securityFiles = new ArrayList<>();
private String metadataPath;
private String metadataStringData;
private final List metadataFiles = new ArrayList<>();
private Map metadataExtra;
private final List conformance = new ArrayList<>();
@Override
public InitResult init(CoreSettings settings) {
this.settings = settings;
Settings pluginSettings = settings.getPluginSettings();
enabled = pluginSettings.getBoolean(ModelLoaderSettings.TAG_ENABLE_MODELLOADER, ModelLoaderSettings.class);
if (enabled) {
idTypeDefault = pluginSettings.get(CoreModelSettings.TAG_ID_TYPE_DEFAULT, CoreModelSettings.class).toUpperCase();
settings.getPluginManager().registerPlugin(this);
liquibasePath = pluginSettings.get(ModelLoaderSettings.TAG_LIQUIBASE_PATH, ModelLoaderSettings.class);
String liquibaseString = pluginSettings.get(ModelLoaderSettings.TAG_LIQUIBASE_FILES, ModelLoaderSettings.class);
liquibaseFiles.addAll(Arrays.asList(StringUtils.split(liquibaseString.trim(), ", ")));
modelPath = pluginSettings.get(ModelLoaderSettings.TAG_MODEL_PATH, ModelLoaderSettings.class);
String modelFilesString = pluginSettings.get(ModelLoaderSettings.TAG_MODEL_FILES, ModelLoaderSettings.class);
for (var modelFile : StringUtils.split(modelFilesString.trim(), ", ")) {
addModelFileWithPath(modelFile);
}
securityPath = pluginSettings.get(ModelLoaderSettings.TAG_SECURITY_PATH, ModelLoaderSettings.class);
String securityFilesString = pluginSettings.get(ModelLoaderSettings.TAG_SECURITY_FILES, ModelLoaderSettings.class);
securityFiles.addAll(Arrays.asList(StringUtils.split(securityFilesString.trim(), ", ")));
metadataStringData = pluginSettings.get(ModelLoaderSettings.TAG_METADATA_DATA, ModelLoaderSettings.class);
metadataPath = pluginSettings.get(ModelLoaderSettings.TAG_METADATA_PATH, ModelLoaderSettings.class);
String metadataFilesString = pluginSettings.get(ModelLoaderSettings.TAG_METADATA_FILES, ModelLoaderSettings.class);
metadataFiles.addAll(Arrays.asList(StringUtils.split(metadataFilesString.trim(), ", ")));
}
return InitResult.INIT_OK;
}
private void loadModelFiles() {
for (String fileName : modelFiles) {
loadModelFile(fileName);
}
for (Entry entry : primaryKeys.entrySet()) {
String typeName = entry.getKey();
primaryKeys.get(typeName).setType(getTypeFor(settings, typeName));
}
}
private void addModelFileWithPath(String fileName) {
final File fullFile;
if (StringHelper.isNullOrEmpty(modelPath)) {
fullFile = new File(fileName);
} else {
fullFile = new File(modelPath, fileName);
}
modelFiles.add(fullFile.toString());
}
public void addModelFile(String filename) {
modelFiles.add(filename);
}
public void addSecurityFile(String filename) {
securityFiles.add(filename);
}
public void addLiquibaseFile(String filename) {
liquibaseFiles.add(filename);
}
public void loadModelFile(String fullPathString) {
File fullFile = new File(fullPathString);
LOGGER.info("Loading model definition from {}", fullFile.toString());
String data;
try {
ObjectMapper objectMapper = new ObjectMapper();
DefModel modelDefinition;
if (fullFile.exists()) {
data = FileUtils.readFileToString(fullFile, StandardCharsets.UTF_8);
modelDefinition = objectMapper.readValue(data, DefModel.class);
} else {
InputStream stream = getClass().getClassLoader().getResourceAsStream(fullFile.toString());
modelDefinition = objectMapper.readValue(stream, DefModel.class);
}
modelDefinition.init();
modelDefinitions.add(modelDefinition);
for (DefEntityType type : modelDefinition.getEntityTypes()) {
final DefEntityProperty primaryKey = type.getPrimaryKey();
if (primaryKey != null) {
primaryKeys.put(type.getName(), primaryKey);
}
}
conformance.addAll(modelDefinition.getConformance());
} catch (IOException ex) {
LOGGER.error("Failed to load model definition", ex);
}
}
@Override
public void installSecurityDefinitions(PersistenceManager pm) {
if (pm instanceof JooqPersistenceManager) {
for (String fileName : securityFiles) {
SecurityModel secModel = loadSecurityFile(fileName);
if (secModel == null) {
continue;
}
for (SecurityModel.SecurityEntry secEntry : secModel.getEntries()) {
pm.addSecurityDefinition(secEntry);
}
}
}
}
private SecurityModel loadSecurityFile(String fileName) {
final File fullFile = new File(securityPath, fileName);
LOGGER.info("Loading security definition from {}", fullFile.toString());
String data;
try {
ObjectMapper objectMapper = new ObjectMapper();
SecurityModel securityModel;
if (fullFile.exists()) {
data = FileUtils.readFileToString(fullFile, StandardCharsets.UTF_8);
securityModel = objectMapper.readValue(data, SecurityModel.class);
} else {
InputStream stream = getClass().getClassLoader().getResourceAsStream(fullFile.toString());
if (stream == null) {
LOGGER.info(" Not found: {}", fullFile);
return null;
}
securityModel = objectMapper.readValue(stream, SecurityModel.class);
}
return securityModel;
} catch (IOException ex) {
LOGGER.error("Failed to load model definition", ex);
}
return null;
}
@Override
public void modifyServiceDocument(ServiceRequest request, Map result) {
Map serverSettings = (Map) result.get(Service.KEY_SERVER_SETTINGS);
if (serverSettings == null) {
// Nothing to add to.
return;
}
if (metadataExtra == null) {
metadataExtra = loadExtraMetadata();
}
Set extensionList = (Set) serverSettings.get(Service.KEY_CONFORMANCE_LIST);
extensionList.addAll(conformance);
mergeJson(serverSettings, metadataExtra);
}
private Map loadExtraMetadata() {
final ObjectMapper objectMapper = new ObjectMapper();
final Map extraMetadata = new LinkedHashMap<>();
// local copy for thread safety
final String localMetadataStringData = metadataStringData;
if (!StringHelper.isNullOrEmpty(localMetadataStringData)) {
try {
mergeJson(extraMetadata, objectMapper.readValue(localMetadataStringData, TYPE_REFERENCE_MAP));
} catch (JsonProcessingException ex) {
LOGGER.error("Failed to parse extra metadata.", ex);
}
// Free global String data.
metadataStringData = null;
}
for (String fileName : metadataFiles) {
mergeJson(extraMetadata, loadExtraMetadataFile(fileName));
}
return extraMetadata;
}
private void mergeJson(Map target, Map toMerge) {
if (toMerge == null) {
return;
}
for (var entry : toMerge.entrySet()) {
String name = entry.getKey();
Object valueToMerge = entry.getValue();
Object valueTarget = target.get(name);
if (valueTarget == null) {
target.put(name, valueToMerge);
} else if (valueTarget instanceof Map toMap && valueToMerge instanceof Map mergeMap) {
mergeJson(toMap, mergeMap);
} else if (valueTarget instanceof List toList && valueToMerge instanceof List mergeList) {
toList.addAll(mergeList);
} else if (valueTarget instanceof Set toSet && valueToMerge instanceof List mergeList) {
toSet.addAll(mergeList);
} else {
LOGGER.warn("Keeping value for {}, target already contains a value: {}; Ignoring {}", name, valueTarget, valueToMerge);
}
}
}
private Map loadExtraMetadataFile(String fileName) {
final File fullFile = new File(metadataPath, fileName);
LOGGER.info("Loading extra landing page meta data from {}", fullFile.toString());
String data;
try {
ObjectMapper objectMapper = new ObjectMapper();
if (fullFile.exists()) {
data = FileUtils.readFileToString(fullFile, StandardCharsets.UTF_8);
return objectMapper.readValue(data, TYPE_REFERENCE_MAP);
} else {
InputStream stream = getClass().getClassLoader().getResourceAsStream(fullFile.toString());
if (stream == null) {
LOGGER.info(" Not found: {}", fullFile);
} else {
return objectMapper.readValue(stream, TYPE_REFERENCE_MAP);
}
}
} catch (IOException ex) {
LOGGER.error("Failed to load extra metadata", ex);
}
return Collections.emptyMap();
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public boolean isFullyInitialised() {
return fullyInitialised;
}
@Override
public void registerEntityTypes() {
loadModelFiles();
ModelRegistry modelRegistry = settings.getModelRegistry();
for (DefModel modelDefinition : modelDefinitions) {
modelDefinition.registerEntityTypes(modelRegistry);
}
}
@Override
public boolean linkEntityTypes(PersistenceManager pm) {
LOGGER.info("Initialising Model Types...");
ModelRegistry modelRegistry = settings.getModelRegistry();
for (DefModel modelDefinition : modelDefinitions) {
modelDefinition.linkEntityTypes(modelRegistry);
pm.addModelMapping(modelDefinition);
}
// Done, release the model definition.
modelDefinitions = null;
fullyInitialised = true;
return true;
}
public String getTypeFor(CoreSettings settings, String entityTypeName) {
Settings pluginSettings = settings.getPluginSettings();
return pluginSettings.get(PLUGIN_NAME + ".idType." + entityTypeName, idTypeDefault).toUpperCase();
}
public Map createLiqibaseParams(JooqPersistenceManager jpm, Map target) {
if (target == null) {
target = new LinkedHashMap<>();
}
PluginCoreModel pCoreModel = settings.getPluginManager().getPlugin(PluginCoreModel.class);
if (pCoreModel != null) {
pCoreModel.createLiqibaseParams(jpm, target);
}
for (Entry entry : primaryKeys.entrySet()) {
String typeName = entry.getKey();
jpm.generateLiquibaseVariables(target, typeName, getTypeFor(settings, typeName));
}
return target;
}
@Override
public String checkForUpgrades() {
if (!isFullyInitialised()) {
return "ModelLoader not fully initialised yet.";
}
try (PersistenceManager pm = PersistenceManagerFactory.getInstance(settings).create()) {
if (pm instanceof JooqPersistenceManager jpm) {
StringBuilder result = new StringBuilder();
for (String file : liquibaseFiles) {
final Map liquibaseParams = createLiqibaseParams(jpm, null);
liquibaseParams.put(CHANGE_SET_NAME, file);
liquibaseParams.put("searchPath", liquibasePath);
result.append(jpm.checkForUpgrades(file, liquibaseParams));
}
return result.toString();
} else {
return "Unknown persistence manager class";
}
}
}
@Override
public boolean doUpgrades(Writer out) throws UpgradeFailedException, IOException {
if (!isFullyInitialised()) {
out.append("ModelLoader not fully initialised yet.");
return false;
}
try (PersistenceManager pm = PersistenceManagerFactory.getInstance(settings).create()) {
if (pm instanceof JooqPersistenceManager jpm) {
for (String file : liquibaseFiles) {
final Map liquibaseParams = createLiqibaseParams(jpm, null);
liquibaseParams.put("searchPath", liquibasePath);
if (!jpm.doUpgrades(file, liquibaseParams, out)) {
return false;
}
}
return true;
}
out.append("Unknown persistence manager class");
return false;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy