diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/JacksonHelper.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/JacksonHelper.java new file mode 100644 index 00000000..297be5ad --- /dev/null +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/JacksonHelper.java @@ -0,0 +1,155 @@ +/* + * Copyright 2020 interactive instruments GmbH + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package de.ii.xtraplatform.values.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.JacksonYAMLParseException; +import com.google.common.hash.Hashing; +import de.ii.xtraplatform.values.domain.Identifier; +import de.ii.xtraplatform.values.domain.ValueDecoderMiddleware; +import de.ii.xtraplatform.values.domain.ValueEncoding.FORMAT; +import io.dropwizard.util.DataSize; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.regex.Pattern; + +class JacksonHelper { + + private static final byte[] JSON_NULL = "null".getBytes(); + private static final byte[] YAML_NULL = "--- null\n".getBytes(); + private static final Pattern JSON_EMPTY = Pattern.compile("(\\s)*"); + private static final Pattern YAML_EMPTY = Pattern.compile("---(\\s)*"); + + private final List> decoderPreProcessor; + private final List> decoderMiddleware; + private final DataSize maxYamlFileSize; + private final Function mapperProvider; + + JacksonHelper( + List> decoderPreProcessor, + List> decoderMiddleware, + DataSize maxYamlFileSize, + Function mapperProvider) { + this.decoderPreProcessor = decoderPreProcessor; + this.decoderMiddleware = decoderMiddleware; + this.maxYamlFileSize = maxYamlFileSize; + this.mapperProvider = mapperProvider; + } + + // Serialization methods + byte[] serialize(Object data, FORMAT format) { + try { + return mapperProvider.apply(format).writeValueAsBytes(data); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unexpected serialization error", e); + } + } + + @SuppressWarnings("UnstableApiUsage") + String hash(Object data) { + byte[] bytes = serialize(data, FORMAT.SMILE); + return Hashing.murmur3_128().hashBytes(bytes).toString(); + } + + // Deserialization methods + + T deserialize(Identifier identifier, byte[] payload, FORMAT format, boolean ignoreCache) + throws IOException { + if (isNull(payload)) { + return null; + } + if (Objects.equals(format, FORMAT.NONE)) { + throw new IllegalStateException("No format given"); + } + + // Preprocess payload + byte[] rawData = payload; + ObjectMapper objectMapper = mapperProvider.apply(format); + for (ValueDecoderMiddleware preProcessor : decoderPreProcessor) { + rawData = preProcessor.process(identifier, rawData, objectMapper, null, ignoreCache); + } + + return processMiddleware(identifier, rawData, objectMapper, ignoreCache); + } + + boolean isEmpty(byte[] payload) { + if (isNull(payload)) { + return true; + } + String payloadString = new String(payload, StandardCharsets.UTF_8); + return JSON_EMPTY.matcher(payloadString).matches() + || YAML_EMPTY.matcher(payloadString).matches(); + } + + boolean isNull(byte[] payload) { + return Arrays.equals(payload, JSON_NULL) || Arrays.equals(payload, YAML_NULL); + } + + private T processMiddleware( + Identifier identifier, byte[] rawData, ObjectMapper objectMapper, boolean ignoreCache) + throws IOException { + T data = null; + + try { + for (ValueDecoderMiddleware middleware : decoderMiddleware) { + data = middleware.process(identifier, rawData, objectMapper, data, ignoreCache); + } + } catch (JacksonYAMLParseException e) { + if (Objects.nonNull(e.getMessage()) + && e.getMessage().contains("incoming YAML document exceeds the limit")) { + throw new IOException( + String.format( + "Maximum YAML file size of %s exceeded, increase 'store.maxYamlFileSize' to fix.", + maxYamlFileSize), + e); + } + data = tryRecovery(identifier, rawData, objectMapper, e); + } catch (Throwable e) { + data = tryRecovery(identifier, rawData, objectMapper, e); + } + + return data; + } + + private T tryRecovery( + Identifier identifier, byte[] rawData, ObjectMapper objectMapper, Throwable originalException) + throws IOException { + Optional> recovery = + decoderMiddleware.stream().filter(ValueDecoderMiddleware::canRecover).findFirst(); + + if (recovery.isPresent()) { + try { + return recovery.get().recover(identifier, rawData, objectMapper); + } catch (Throwable recoveryException) { + originalException.addSuppressed(recoveryException); + } + } + + rethrowOriginalException(originalException); + return null; // unreachable + } + + private void rethrowOriginalException(Throwable originalException) throws IOException { + if (originalException instanceof IOException) { + throw (IOException) originalException; + } + if (originalException instanceof RuntimeException) { + throw (RuntimeException) originalException; + } + if (originalException instanceof Error) { + throw (Error) originalException; + } + throw new IOException("Deserialization failed", originalException); + } +} diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueDecoderBase.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueDecoderBase.java index 123c84fc..444f6811 100644 --- a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueDecoderBase.java +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueDecoderBase.java @@ -28,7 +28,7 @@ public ValueDecoderBase(Function newInstanceSupplier, ValueCache< public T process( Identifier identifier, byte[] payload, ObjectMapper objectMapper, T data, boolean ignoreCache) throws IOException { - T data2 = null; + T data2; if (valueCache.has(identifier) && !ignoreCache) { data2 = valueCache.get(identifier); diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueDecoderPreHash.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueDecoderPreHash.java index 3bfa57a7..96898145 100644 --- a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueDecoderPreHash.java +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueDecoderPreHash.java @@ -14,17 +14,13 @@ import de.ii.xtraplatform.values.domain.ValueDecoderMiddleware; import java.io.IOException; import java.util.function.Function; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class ValueDecoderPreHash implements ValueDecoderMiddleware { - private static final Logger LOGGER = LoggerFactory.getLogger(ValueDecoderPreHash.class); - private final Function> newBuilderSupplier; private final Function hasher; - // TODO: shouldPreHash from Factory + // NOPMD - TODO: shouldPreHash from Factory public ValueDecoderPreHash( Function> newBuilderSupplier, Function hasher) { this.newBuilderSupplier = newBuilderSupplier; @@ -43,8 +39,6 @@ public T process( builder.stableHash(hash); - // LOGGER.debug("PROC {} {}", identifier, hash); - return builder.build(); } } diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueEncodingJackson.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueEncodingJackson.java index 04d37bed..eeefabc2 100644 --- a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueEncodingJackson.java +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/api/ValueEncodingJackson.java @@ -10,55 +10,41 @@ import static de.ii.xtraplatform.base.domain.util.JacksonModules.DESERIALIZE_IMMUTABLE_BUILDER_NESTED; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.smile.SmileFactory; -import com.fasterxml.jackson.dataformat.yaml.JacksonYAMLParseException; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature; import com.google.common.collect.ImmutableMap; -import com.google.common.hash.Hashing; import de.ii.xtraplatform.base.domain.Jackson; import de.ii.xtraplatform.values.domain.Identifier; import de.ii.xtraplatform.values.domain.ValueDecoderMiddleware; import de.ii.xtraplatform.values.domain.ValueEncoding; import io.dropwizard.util.DataSize; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.regex.Pattern; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.LoaderOptions; public class ValueEncodingJackson implements ValueEncoding { - private static final Logger LOGGER = LoggerFactory.getLogger(ValueEncodingJackson.class); - - public static final byte[] JSON_NULL = "null".getBytes(); public static final byte[] YAML_NULL = "--- null\n".getBytes(); - private static final Pattern JSON_EMPTY = Pattern.compile("(\\s)*"); - private static final Pattern YAML_EMPTY = Pattern.compile("---(\\s)*"); - private static final FORMAT DEFAULT_FORMAT = FORMAT.YML; private final Map mappers; private final List> decoderPreProcessor; private final List> decoderMiddleware; - private final DataSize maxYamlFileSize; + private final JacksonHelper jacksonHelper; public ValueEncodingJackson( Jackson jackson, DataSize maxYamlFileSize, boolean failOnUnknownProperties) { - this.maxYamlFileSize = Objects.requireNonNullElse(maxYamlFileSize, DataSize.megabytes(3)); + DataSize resolvedMaxYamlFileSize = + Objects.requireNonNullElse(maxYamlFileSize, DataSize.megabytes(3)); LoaderOptions loaderOptions = new LoaderOptions(); - loaderOptions.setCodePointLimit(Math.toIntExact(this.maxYamlFileSize.toBytes())); + loaderOptions.setCodePointLimit(Math.toIntExact(resolvedMaxYamlFileSize.toBytes())); ObjectMapper jsonMapper = jackson @@ -107,6 +93,9 @@ public ValueEncodingJackson( this.decoderMiddleware = new ArrayList<>(); this.decoderPreProcessor = new ArrayList<>(); + this.jacksonHelper = + new JacksonHelper<>( + decoderPreProcessor, decoderMiddleware, resolvedMaxYamlFileSize, this::getMapper); } public void addDecoderPreProcessor(ValueDecoderMiddleware preProcessor) { @@ -124,87 +113,19 @@ public final FORMAT getDefaultFormat() { @Override public final byte[] serialize(T data) { - try { - return getDefaultMapper().writeValueAsBytes(data); - } catch (JsonProcessingException e) { - // should never happen - throw new IllegalStateException("Unexpected serialization error", e); - } + return serialize(data, DEFAULT_FORMAT); } @Override public final byte[] serialize(T data, FORMAT format) { - try { - return getMapper(format).writeValueAsBytes(data); - } catch (JsonProcessingException e) { - // should never happen - throw new IllegalStateException("Unexpected serialization error", e); - } - } - - @Override - public byte[] serialize(Map data) { - try { - return getDefaultMapper().writeValueAsBytes(data); - } catch (JsonProcessingException e) { - // should never happen - throw new IllegalStateException("Unexpected serialization error", e); - } + return jacksonHelper.serialize(data, format); } @Override public final T deserialize( Identifier identifier, byte[] payload, FORMAT format, boolean ignoreCache) throws IOException { - // "null" as payload means delete - if (isNull(payload)) { - return null; - } - if (Objects.equals(format, FORMAT.NONE)) { - throw new IllegalStateException("No format given"); - } - - byte[] rawData = payload; - ObjectMapper objectMapper = getMapper(format); - T data = null; - - for (ValueDecoderMiddleware preProcessor : decoderPreProcessor) { - rawData = preProcessor.process(identifier, rawData, objectMapper, null, ignoreCache); - } - - try { - for (ValueDecoderMiddleware middleware : decoderMiddleware) { - data = middleware.process(identifier, rawData, objectMapper, data, ignoreCache); - } - - } catch (Throwable e) { - if (e instanceof JacksonYAMLParseException - && Objects.nonNull(e.getMessage()) - && e.getMessage().contains("incoming YAML document exceeds the limit")) { - throw new IOException( - String.format( - "Maximum YAML file size of %s exceeded, increase 'store.maxYamlFileSize' to fix.", - maxYamlFileSize)); - } - - Optional> recovery = - decoderMiddleware.stream().filter(ValueDecoderMiddleware::canRecover).findFirst(); - if (recovery.isPresent()) { - try { - data = recovery.get().recover(identifier, rawData, objectMapper); - } catch (Throwable e2) { - throw e; - } - } else { - throw e; - } - } - - return data; - } - - final ObjectMapper getDefaultMapper() { - return getMapper(DEFAULT_FORMAT); + return jacksonHelper.deserialize(identifier, payload, format, ignoreCache); } @Override @@ -212,21 +133,17 @@ public final ObjectMapper getMapper(FORMAT format) { return mappers.get(format); } - final boolean isNull(byte[] payload) { - return Arrays.equals(payload, JSON_NULL) || Arrays.equals(payload, YAML_NULL); + public final boolean isEmpty(byte[] payload) { + return jacksonHelper.isEmpty(payload); } - public final boolean isEmpty(byte[] payload) { - String payloadString = new String(payload, StandardCharsets.UTF_8); - return JSON_EMPTY.matcher(payloadString).matches() - || YAML_EMPTY.matcher(payloadString).matches(); + @Override + public byte[] serialize(Map data) { + return jacksonHelper.serialize(data, DEFAULT_FORMAT); } @Override - @SuppressWarnings("UnstableApiUsage") public final String hash(T data) { - byte[] bytes = serialize(data, FORMAT.SMILE); - - return Hashing.murmur3_128().hashBytes(bytes).toString(); + return jacksonHelper.hash(data); } } diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/app/ValueReloadTask.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/app/ValueReloadTask.java index 8e86128e..1eb545a0 100644 --- a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/app/ValueReloadTask.java +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/app/ValueReloadTask.java @@ -54,7 +54,9 @@ public void init(AppConfiguration configuration, Environment environment) { // codelists/foo,maplibre-styles/bar @Override public void execute(Map> parameters, PrintWriter output) throws Exception { - if (LOGGER.isTraceEnabled()) LOGGER.trace("Reload request: {}", parameters); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Reload request: {}", parameters); + } List includes = getPaths(parameters).stream().map(Path::of).collect(Collectors.toList()); diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/app/ValueStoreImpl.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/app/ValueStoreImpl.java index a9984494..965879e3 100644 --- a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/app/ValueStoreImpl.java +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/app/ValueStoreImpl.java @@ -57,6 +57,7 @@ @Singleton @AutoBind(interfaces = {ValueStore.class, AppLifeCycle.class}) +@SuppressWarnings({"PMD.TooManyMethods", "PMD.GodClass"}) public class ValueStoreImpl extends AbstractVolatile implements ValueStore, KeyValueStore, ValueCache, AppLifeCycle { @@ -116,77 +117,7 @@ public CompletionStage onStart(boolean isStartupAsync) { LOGGER.debug("Loading values"); - valueFactories - .getTypes() - .forEach( - valueType -> { - ValueFactory valueFactory = valueFactories.get(valueType); - Path typePath = Path.of(valueFactory.type()); - int count = 0; - - valueTypes.put( - valueFactory.valueClass(), - KeyValueStore.TYPE_SPLITTER.splitToList(valueFactory.type())); - - try (Stream paths = - blobStore.walk( - typePath, - 8, - (path, attributes) -> attributes.isValue() && !attributes.isHidden())) { - List files = paths.sorted().collect(Collectors.toList()); - - for (Path file : files) { - String extension = Files.getFileExtension(file.getFileName().toString()); - ValueEncoding.FORMAT payloadFormat = ValueEncoding.FORMAT.fromString(extension); - - if (payloadFormat == ValueEncoding.FORMAT.UNKNOWN - && valueFactory.formatAliases().containsKey(extension)) { - payloadFormat = valueFactory.formatAliases().get(extension); - } - - if (payloadFormat == ValueEncoding.FORMAT.UNKNOWN) { - return; - } - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Loading value: {} {} {}", valueType, file, payloadFormat); - } - - Path currentPath = typePath.resolve(file); - Path parent = Objects.requireNonNullElse(file.getParent(), Path.of("")); - Identifier identifier = - Identifier.from( - typePath - .resolve(parent) - .resolve( - Files.getNameWithoutExtension(file.getFileName().toString()))); - - try { - byte[] bytes = blobStore.content(currentPath).get().readAllBytes(); - long lm = blobStore.lastModified(currentPath); - - StoredValue value = - valueEncoding.deserialize(identifier, bytes, payloadFormat, true); - - this.memCache.put(identifier, value); - this.lastModified.put(identifier, lm == -1 ? Instant.now().toEpochMilli() : lm); - - count++; - } catch (IOException e) { - LogContext.error(LOGGER, e, "Could not load value from {}", currentPath); - } - } - - } catch (IOException e) { - LogContext.error(LOGGER, e, "Could not load values with type {}", valueType); - } - - if (count > 0) { - LOGGER.info("Loaded {} {}", count, valueType); - } else { - LOGGER.debug("Loaded {} {}", count, valueType); - } - }); + loadAllValues(); LOGGER.debug("Loaded values"); @@ -199,94 +130,128 @@ public CompletionStage onStart(boolean isStartupAsync) { void reload(List filter) { LOGGER.debug("Reloading values"); - valueFactories - .getTypes() - .forEach( - valueType -> { - ValueFactory valueFactory = valueFactories.get(valueType); - Path typePath = Path.of(valueFactory.type()); - int count = 0; - - valueTypes.put( - valueFactory.valueClass(), - KeyValueStore.TYPE_SPLITTER.splitToList(valueFactory.type())); - - try (Stream paths = - blobStore.walk( - typePath, - 8, - (path, attributes) -> attributes.isValue() && !attributes.isHidden())) { - List files = paths.sorted().collect(Collectors.toList()); - - for (Path file : files) { - String extension = Files.getFileExtension(file.getFileName().toString()); - ValueEncoding.FORMAT payloadFormat = ValueEncoding.FORMAT.fromString(extension); - - if (payloadFormat == ValueEncoding.FORMAT.UNKNOWN - && valueFactory.formatAliases().containsKey(extension)) { - payloadFormat = valueFactory.formatAliases().get(extension); - } - - if (payloadFormat == ValueEncoding.FORMAT.UNKNOWN) { - return; - } - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Loading value: {} {} {}", valueType, file, payloadFormat); - } - - Path currentPath = typePath.resolve(file); - Path parent = Objects.requireNonNullElse(file.getParent(), Path.of("")); - Path identifierPath = - typePath - .resolve(parent) - .resolve(Files.getNameWithoutExtension(file.getFileName().toString())); - Identifier identifier = Identifier.from(identifierPath); - - if (!filter.isEmpty() && filter.stream().noneMatch(identifierPath::startsWith)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping value, not included: {}", identifierPath); - } - continue; - } - - try { - byte[] bytes = blobStore.content(currentPath).get().readAllBytes(); - long lm = blobStore.lastModified(currentPath); - - StoredValue value = - valueEncoding.deserialize(identifier, bytes, payloadFormat, true); - - if (memCache.containsKey(identifier) - && !Objects.equals(memCache.get(identifier), value)) { - count++; - } else { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Not counting value, not changed: {}", identifierPath); - } - } - - this.memCache.put(identifier, value); - this.lastModified.put(identifier, lm == -1 ? Instant.now().toEpochMilli() : lm); - } catch (IOException e) { - LogContext.error(LOGGER, e, "Could not reload value from {}", currentPath); - } - } - - } catch (IOException e) { - LogContext.error(LOGGER, e, "Could not reload values with type {}", valueType); - } - - if (count > 0) { - LOGGER.info("Reloaded {} {}", count, valueType); - } else { - LOGGER.debug("Reloaded {} {}", count, valueType); - } - }); + loadValuesWithFilter(filter); LOGGER.debug("Reloaded values"); } + private void loadAllValues() { + valueFactories.getTypes().forEach(this::loadValuesForType); + } + + private void loadValuesWithFilter(List filter) { + valueFactories.getTypes().forEach(valueType -> loadValuesForType(valueType, filter)); + } + + private void loadValuesForType(String valueType) { + loadValuesForType(valueType, List.of()); + } + + private void loadValuesForType(String valueType, List filter) { + ValueFactory valueFactory = valueFactories.get(valueType); + Path typePath = Path.of(valueFactory.type()); + int count = 0; + + valueTypes.put( + valueFactory.valueClass(), KeyValueStore.TYPE_SPLITTER.splitToList(valueFactory.type())); + + try (Stream paths = + blobStore.walk( + typePath, 8, (path, attributes) -> attributes.isValue() && !attributes.isHidden())) { + List files = paths.sorted().collect(Collectors.toList()); + + for (Path file : files) { + if (processValueFile(valueType, valueFactory, typePath, file, filter)) { + count++; + } + } + + } catch (IOException e) { + LogContext.error(LOGGER, e, "Could not load values with type {}", valueType); + } + + logLoadingResult(count, valueType, !filter.isEmpty()); + } + + private boolean processValueFile( + String valueType, ValueFactory valueFactory, Path typePath, Path file, List filter) { + + String extension = Files.getFileExtension(file.getFileName().toString()); + ValueEncoding.FORMAT payloadFormat = ValueEncoding.FORMAT.fromString(extension); + + if (payloadFormat == ValueEncoding.FORMAT.UNKNOWN + && valueFactory.formatAliases().containsKey(extension)) { + payloadFormat = valueFactory.formatAliases().get(extension); + } + + if (payloadFormat == ValueEncoding.FORMAT.UNKNOWN) { + return false; + } + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Loading value: {} {} {}", valueType, file, payloadFormat); + } + + Path parent = Objects.requireNonNullElse(file.getParent(), Path.of("")); + Path identifierPath = + typePath + .resolve(parent) + .resolve(Files.getNameWithoutExtension(file.getFileName().toString())); + + // Check filter for reload operations + if (!filter.isEmpty() && filter.stream().noneMatch(identifierPath::startsWith)) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping value, not included: {}", identifierPath); + } + return false; + } + + Identifier identifier = Identifier.from(identifierPath); + Path currentPath = typePath.resolve(file); + return loadAndStoreValue(identifier, currentPath, payloadFormat, filter.isEmpty()); + } + + private boolean loadAndStoreValue( + Identifier identifier, + Path currentPath, + ValueEncoding.FORMAT payloadFormat, + boolean isInitialLoad) { + try { + byte[] bytes = blobStore.content(currentPath).get().readAllBytes(); + long lm = blobStore.lastModified(currentPath); + + StoredValue value = valueEncoding.deserialize(identifier, bytes, payloadFormat, true); + + boolean hasChanged = true; + if (!isInitialLoad) { + hasChanged = + memCache.containsKey(identifier) && !Objects.equals(memCache.get(identifier), value); + + if (!hasChanged && LOGGER.isTraceEnabled()) { + LOGGER.trace("Not counting value, not changed: {}", identifier.asPath()); + } + } + + this.memCache.put(identifier, value); + this.lastModified.put(identifier, lm == -1 ? Instant.now().toEpochMilli() : lm); + + return isInitialLoad || hasChanged; + } catch (IOException e) { + String operation = isInitialLoad ? "load" : "reload"; + LogContext.error(LOGGER, e, "Could not {} value from {}", operation, currentPath); + return false; + } + } + + private void logLoadingResult(int count, String valueType, boolean isReload) { + String operation = isReload ? "Reloaded" : "Loaded"; + if (count > 0) { + LOGGER.info("{} {} {}", operation, count, valueType); + } else { + LOGGER.debug("{} {} {}", operation, count, valueType); + } + } + protected ValueBuilder getBuilder(Identifier identifier) { return valueFactories.getTypes().stream() .filter(type -> KeyValueStore.valueTypeMatches(identifier, type)) @@ -308,7 +273,6 @@ public List identifiers(String... path) { .collect(Collectors.toList()); } - // TODO: change in KeyValueStore, might affect EntityDataStore @Override public List ids(String... path) { return identifiers(path).stream().map(Identifier::asPath).collect(Collectors.toList()); @@ -364,7 +328,7 @@ public CompletableFuture delete(Identifier identifier) { } } - StoredValue removed = null; + StoredValue removed; synchronized (this) { removed = memCache.remove(identifier); diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/Identifier.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/Identifier.java index 9c8f1297..9cabe3f9 100644 --- a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/Identifier.java +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/Identifier.java @@ -19,6 +19,8 @@ @JsonDeserialize(as = ImmutableIdentifier.class) public interface Identifier extends Comparable { + Joiner JOINER = Joiner.on('/').skipNulls(); + String id(); List path(); @@ -63,8 +65,6 @@ default int compareTo(Identifier identifier) { return id().compareTo(identifier.id()); } - Joiner JOINER = Joiner.on('/').skipNulls(); - @JsonIgnore @Value.Derived @Value.Auxiliary diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/KeyValueStore.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/KeyValueStore.java index 2fb54cdd..7bfb7c8c 100644 --- a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/KeyValueStore.java +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/KeyValueStore.java @@ -14,6 +14,8 @@ public interface KeyValueStore extends Values { + Splitter TYPE_SPLITTER = Splitter.on('/'); + static String valueType(Identifier identifier) { if (identifier.path().isEmpty()) { throw new IllegalArgumentException("Invalid path, no value type found."); @@ -21,8 +23,6 @@ static String valueType(Identifier identifier) { return identifier.path().get(0); } - Splitter TYPE_SPLITTER = Splitter.on('/'); - static boolean valueTypeMatches(Identifier identifier, String type) { List valueType = TYPE_SPLITTER.splitToList(type); diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/StoredValue.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/StoredValue.java index b9dcf253..77e6cc03 100644 --- a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/StoredValue.java +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/StoredValue.java @@ -12,7 +12,7 @@ public interface StoredValue { - // TODO: removing/emptying breaks builders, no from(Value) is generated + // NOPMD - TODO: removing/emptying breaks builders, no from(Value) is generated @JsonIgnore @org.immutables.value.Value.Default default long storageVersion() { diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/ValueEncoding.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/ValueEncoding.java index 51f42385..75e70a47 100644 --- a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/ValueEncoding.java +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/ValueEncoding.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -34,7 +35,7 @@ public static FORMAT fromString(String format) { } for (FORMAT f : values()) { - if (Objects.equals(f.name(), format.toUpperCase())) { + if (Objects.equals(f.name(), format.toUpperCase(Locale.ROOT))) { return f; } } @@ -48,7 +49,7 @@ public static List extensions(String... additional) { .filter(format -> format != NONE && format != UNKNOWN) .map(Enum::name), Arrays.stream(additional)) - .map(format -> "." + format.toLowerCase()) + .map(format -> "." + format.toLowerCase(Locale.ROOT)) .collect(Collectors.toList()); } @@ -56,7 +57,7 @@ public String apply(String path) { if (this == NONE || this == UNKNOWN) { return path; } - return path + "." + this.name().toLowerCase(); + return path + "." + this.name().toLowerCase(Locale.ROOT); } } diff --git a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/ValueStoreDecorator.java b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/ValueStoreDecorator.java index fb49c049..0be79375 100644 --- a/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/ValueStoreDecorator.java +++ b/xtraplatform-values/src/main/java/de/ii/xtraplatform/values/domain/ValueStoreDecorator.java @@ -20,6 +20,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +@SuppressWarnings("PMD.TooManyMethods") public interface ValueStoreDecorator extends KeyValueStore, Volatile2 { KeyValueStore getDecorated();