com.yahoo.elide.modelconfig.io.FileLoader Maven / Gradle / Ivy
/*
* Copyright 2021, Yahoo Inc.
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root for terms.
*/
package com.yahoo.elide.modelconfig.io;
import static com.yahoo.elide.core.dictionary.EntityDictionary.NO_VERSION;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.yahoo.elide.modelconfig.DynamicConfigHelpers;
import com.yahoo.elide.modelconfig.store.models.ConfigFile;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
/**
* Responsible for loading HJSON configuration either from the classpath or from the file system.
*/
@Slf4j
public class FileLoader {
private static final Pattern TABLE_FILE = Pattern.compile("models/tables/[^/]+\\.hjson");
private static final Pattern NAME_SPACE_FILE = Pattern.compile("models/namespaces/[^/]+\\.hjson");
private static final Pattern DB_FILE = Pattern.compile("db/sql/[^/]+\\.hjson");
private static final String CLASSPATH_PATTERN = "classpath*:";
private static final String FILEPATH_PATTERN = "file:";
private static final String RESOURCEPATH_PATTERN = "resource:";
private static final String RESOURCES = "resources";
private static final int RESOURCES_LENGTH = 9; //"resources".length()
private static final String HJSON_EXTN = "**/*.hjson";
private final PathMatchingResourcePatternResolver resolver;
private static final Function CONTENT_PROVIDER = (resource) -> {
try (InputStream inputStream = resource.getInputStream()) {
return new String(inputStream.readAllBytes(), UTF_8);
} catch (IOException e) {
log.error ("Error converting stream to String: {}", e.getMessage());
throw new IllegalStateException(e);
}
};
@Getter
private final String rootPath;
private final String rootURL;
@Getter
private boolean writeable;
/**
* Constructor.
* @param rootPath The file system (or classpath) root directory path for configuration.
*/
public FileLoader(String rootPath) {
this.resolver = new PathMatchingResourcePatternResolver(this.getClass().getClassLoader());
String pattern = CLASSPATH_PATTERN + DynamicConfigHelpers.formatFilePath(formatClassPath(rootPath));
boolean classPathExists = false;
try {
classPathExists = (resolver.getResources(pattern).length != 0);
} catch (IOException e) {
//NOOP
}
if (classPathExists) {
this.rootURL = pattern;
writeable = false;
} else {
File config = new File(rootPath);
if (!config.exists()) {
log.error ("Config path does not exist: {}", config);
throw new IllegalStateException(rootPath + " : config path does not exist");
}
writeable = Files.isWritable(config.toPath());
this.rootURL = FILEPATH_PATTERN + DynamicConfigHelpers.formatFilePath(config.getAbsolutePath());
}
this.rootPath = rootPath;
}
/**
* Load resources from the filesystem/classpath.
* @return A map from the path to the resource.
* @throws IOException If something goes boom.
*/
public Map loadResources() throws IOException {
Map resourceMap = new LinkedHashMap<>();
URI configDirURI = resolver.getResources(this.rootURL)[0].getURI();
String configDirURIString = configDirURI.toString();
int configDirURILength = configDirURIString.length();
if (configDirURIString.startsWith(FILEPATH_PATTERN) && !configDirURIString.startsWith(FILEPATH_PATTERN + "/")) {
// URI for configDir is missing slash the ie file:path and not file:/path and spring
// resolver will return with file:/path
// See https://github.com/spring-projects/spring-framework/issues/29275
configDirURILength += 1;
}
Resource[] hjsonResources = resolver.getResources(this.rootURL + HJSON_EXTN);
for (Resource resource : hjsonResources) {
if (! resource.exists()) {
log.error("Missing resource during HJSON configuration load: {}", resource.getURI());
continue;
}
String resourceUri = resource.getURI().toString();
String path = null;
if (resourceUri.startsWith(RESOURCEPATH_PATTERN)) {
// Native image
int rootLength = resourceUri.indexOf(this.rootPath) + this.rootPath.length() + 1;
path = resourceUri.substring(rootLength);
} else {
path = resourceUri.substring(configDirURILength);
}
resourceMap.put(path, ConfigFile.builder()
.type(toType(path))
.contentProvider(() -> CONTENT_PROVIDER.apply(resource))
.path(path)
.version(NO_VERSION)
.build());
}
return resourceMap;
}
/**
* Load a single resource from the filesystem/classpath.
* @return The file content.
* @throws IOException If something goes boom.
*/
public ConfigFile loadResource(String relativePath) throws IOException {
Resource[] hjsonResources = resolver.getResources(this.rootURL + relativePath);
if (hjsonResources.length == 0 || ! hjsonResources[0].exists()) {
return null;
}
return ConfigFile.builder()
.type(toType(relativePath))
.contentProvider(() -> CONTENT_PROVIDER.apply(hjsonResources[0]))
.path(relativePath)
.version(NO_VERSION)
.build();
}
/**
* Remove src/.../resources/ from class path for configs directory.
* @param filePath class path for configs directory.
* @return formatted class path for configs directory.
*/
static String formatClassPath(String filePath) {
if (filePath.indexOf(RESOURCES + "/") > -1) {
return filePath.substring(filePath.indexOf(RESOURCES + "/") + RESOURCES_LENGTH + 1);
} else if (filePath.indexOf(RESOURCES) > -1) {
return filePath.substring(filePath.indexOf(RESOURCES) + RESOURCES_LENGTH);
}
return filePath;
}
public static ConfigFile.ConfigFileType toType(String path) {
String lowerCasePath = path.toLowerCase(Locale.ROOT);
if (lowerCasePath.endsWith("db/variables.hjson")) {
return ConfigFile.ConfigFileType.VARIABLE;
} else if (lowerCasePath.endsWith("models/variables.hjson")) {
return ConfigFile.ConfigFileType.VARIABLE;
} else if (lowerCasePath.equals("models/security.hjson")) {
return ConfigFile.ConfigFileType.SECURITY;
} else if (DB_FILE.matcher(lowerCasePath).matches()) {
return ConfigFile.ConfigFileType.DATABASE;
} else if (TABLE_FILE.matcher(lowerCasePath).matches()) {
return ConfigFile.ConfigFileType.TABLE;
} else if (NAME_SPACE_FILE.matcher(lowerCasePath).matches()) {
return ConfigFile.ConfigFileType.NAMESPACE;
} else {
return ConfigFile.ConfigFileType.UNKNOWN;
}
}
}