Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON configuration persistence 2 #39

Merged
merged 1 commit into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.dataline.config.persistence;

public class ConfigNotFoundException extends Exception {
public ConfigNotFoundException(String message) {
super(message);
}

public ConfigNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import java.util.Set;

public interface ConfigPersistence {
<T> T getConfig(PersistenceConfigType persistenceConfigType, String configId, Class<T> clazz);
<T> T getConfig(PersistenceConfigType persistenceConfigType, String configId, Class<T> clazz)
throws ConfigNotFoundException, JsonValidationException;

<T> Set<T> getConfigs(PersistenceConfigType persistenceConfigType, Class<T> clazz);
<T> Set<T> getConfigs(PersistenceConfigType persistenceConfigType, Class<T> clazz)
throws JsonValidationException;

<T> void writeConfig(
PersistenceConfigType persistenceConfigType, String configId, T config, Class<T> clazz);
<T> void writeConfig(PersistenceConfigType persistenceConfigType, String configId, T config);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,91 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SchemaValidatorsConfig;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
import io.dataline.config.ConfigSchema;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

// we force all interaction with disk storage to be effectively single threaded.
public class ConfigPersistenceImpl implements ConfigPersistence {
private static final Object lock = new Object();
private static final String CONFIG_STORAGE_ROOT = "data/config/";
private static final String CONFIG_SCHEMA_ROOT = "dataline-config/src/main/resources/json/";

private final ObjectMapper objectMapper;
private final SchemaValidatorsConfig schemaValidatorsConfig;
private final JsonSchemaFactory jsonSchemaFactory;
final JsonSchemaValidation jsonSchemaValidation;

public ConfigPersistenceImpl() {
jsonSchemaValidation = JsonSchemaValidation.getInstance();
objectMapper = new ObjectMapper();
schemaValidatorsConfig = new SchemaValidatorsConfig();
jsonSchemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
}

@Override
public <T> T getConfig(
PersistenceConfigType persistenceConfigType, String configId, Class<T> clazz) {
PersistenceConfigType persistenceConfigType, String configId, Class<T> clazz)
throws ConfigNotFoundException, JsonValidationException {
synchronized (lock) {
return getConfigInternal(persistenceConfigType, configId, clazz);
}
}

private <T> T getConfigInternal(
PersistenceConfigType persistenceConfigType, String configId, Class<T> clazz)
throws ConfigNotFoundException, JsonValidationException {
// find file
File configFile = getFileOrThrow(persistenceConfigType, configId);

// validate file with schema
validateJson(configFile, persistenceConfigType, configId);
validateJson(configFile, persistenceConfigType);

// cast file to type
return fileToPojo(configFile, clazz);
}

@Override
public <T> Set<T> getConfigs(PersistenceConfigType persistenceConfigType, Class<T> clazz) {
return getConfigIds(persistenceConfigType).stream()
.map(configId -> getConfig(persistenceConfigType, configId, clazz))
.collect(Collectors.toSet());
public <T> Set<T> getConfigs(PersistenceConfigType persistenceConfigType, Class<T> clazz)
throws JsonValidationException {
synchronized (lock) {
final Set<T> configs = new HashSet<>();
for (String configId : getConfigIds(persistenceConfigType)) {
try {
configs.add(getConfig(persistenceConfigType, configId, clazz));
// this should not happen, because we just looked up these ids.
} catch (ConfigNotFoundException e) {
throw new RuntimeException(e);
}
}
return configs;
}
}

@Override
public <T> void writeConfig(
PersistenceConfigType persistenceConfigType, String configId, T config, Class<T> clazz) {
try {
objectMapper.writeValue(new File(getConfigPath(persistenceConfigType, configId)), config);
} catch (IOException e) {
throw new RuntimeException(e);
PersistenceConfigType persistenceConfigType, String configId, T config) {
synchronized (lock) {
try {
objectMapper.writeValue(new File(getConfigPath(persistenceConfigType, configId)), config);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

private JsonSchema getSchema(PersistenceConfigType persistenceConfigType) {
private JsonNode getSchema(PersistenceConfigType persistenceConfigType) {
String configSchemaFilename =
standardConfigTypeToConfigSchema(persistenceConfigType).getSchemaFilename();
File schemaFile = new File(String.format("%s/%s", CONFIG_SCHEMA_ROOT, configSchemaFilename));
JsonNode schema;
try {
schema = objectMapper.readTree(schemaFile);
return objectMapper.readTree(schemaFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
return jsonSchemaFactory.getSchema(schema, schemaValidatorsConfig);
}

private Set<Path> getFiles(PersistenceConfigType persistenceConfigType) {
Expand Down Expand Up @@ -148,38 +163,28 @@ private ConfigSchema standardConfigTypeToConfigSchema(
}
}

private void validateJson(
File configFile, PersistenceConfigType persistenceConfigType, String configId) {
private void validateJson(File configFile, PersistenceConfigType persistenceConfigType)
throws JsonValidationException {
JsonNode configJson;
try {
configJson = objectMapper.readTree(configFile);
} catch (IOException e) {
throw new RuntimeException(e);
}

JsonSchema schema = getSchema(persistenceConfigType);
Set<ValidationMessage> validationMessages = schema.validate(configJson);
if (validationMessages.size() > 0) {
throw new IllegalStateException(
String.format(
"json schema validation failed. type: %s id: %s \n errors: %s \n schema: \n%s \n object: \n%s",
persistenceConfigType,
configId,
validationMessages.stream()
.map(ValidationMessage::toString)
.collect(Collectors.joining(",")),
schema.getSchemaNode().toPrettyString(),
configJson.toPrettyString()));
}
JsonNode schema = getSchema(persistenceConfigType);
jsonSchemaValidation.validateThrow(schema, configJson);
}

private File getFileOrThrow(PersistenceConfigType persistenceConfigType, String configId) {
private File getFileOrThrow(PersistenceConfigType persistenceConfigType, String configId)
throws ConfigNotFoundException {
return getFile(persistenceConfigType, configId)
.map(Path::toFile)
.orElseThrow(
() ->
new RuntimeException(
String.format("config %s %s not found", persistenceConfigType, configId)));
new ConfigNotFoundException(
String.format(
"config type: %s id: %s not found", persistenceConfigType, configId)));
}

private <T> T fileToPojo(File file, Class<T> clazz) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.dataline.config.persistence;

import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.*;
import java.util.Set;
import java.util.stream.Collectors;

public class JsonSchemaValidation {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like a premature refactor here, but it get immediately used in a PR that i'm about to put up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proof: file: dataline-server/src/main/java/io/dataline/server/validation/IntegrationSchemaValidation.java in #42.

private final SchemaValidatorsConfig schemaValidatorsConfig;
private final JsonSchemaFactory jsonSchemaFactory;

private static final JsonSchemaValidation INSTANCE = new JsonSchemaValidation();

public static JsonSchemaValidation getInstance() {
return INSTANCE;
}

private JsonSchemaValidation() {
this.schemaValidatorsConfig = new SchemaValidatorsConfig();
this.jsonSchemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
}

public Set<ValidationMessage> validate(JsonNode schemaJson, JsonNode configJson) {
JsonSchema schema = jsonSchemaFactory.getSchema(schemaJson, schemaValidatorsConfig);

return schema.validate(configJson);
}

public void validateThrow(JsonNode schemaJson, JsonNode configJson)
throws JsonValidationException {
final Set<ValidationMessage> validationMessages = validate(schemaJson, configJson);
if (validationMessages.size() > 0) {
throw new JsonValidationException(
String.format(
"json schema validation failed. \nerrors: %s \nschema: \n%s \nobject: \n%s",
validationMessages.stream()
.map(ValidationMessage::toString)
.collect(Collectors.joining(",")),
schemaJson.toPrettyString(),
configJson.toPrettyString()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.dataline.config.persistence;

public class JsonValidationException extends Exception {
public JsonValidationException(String message) {
super(message);
}

public JsonValidationException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.dataline.config.persistence;

import java.util.UUID;

public class WorkspaceConstants {
// for MVP we only support one workspace per deployment and we hard code its id.
public static UUID DEFAULT_WORKSPACE_ID = UUID.fromString("5ae6b09b-fdec-41af-aaf7-7d94cfc33ef6");
}