summaryrefslogtreecommitdiffstats
path: root/infra/translate-utils/src
diff options
context:
space:
mode:
authorJan Srnicek <jsrnicek@cisco.com>2016-11-03 09:37:28 +0100
committerMaros Marsalek <mmarsale@cisco.com>2016-11-03 10:51:24 +0000
commit03c9a24ebf7bee3ae767236b1fd3ae0ce8fccec6 (patch)
tree7f1c89590046858eb457585d4cb8412dda3d4aab /infra/translate-utils/src
parent481fe32d90010b4570fd97239ca6c7940880d133 (diff)
HONEYCOMB-259 - CacheKeyFactory
Provides logic for creating scoped keys Change-Id: I126bcb9255b4f8a3f2585f50e6e718948581e7f0 Signed-off-by: Jan Srnicek <jsrnicek@cisco.com>
Diffstat (limited to 'infra/translate-utils/src')
-rw-r--r--infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/CacheKeyFactory.java31
-rw-r--r--infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/DumpCacheManager.java34
-rw-r--r--infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/IdentifierCacheKeyFactory.java122
-rw-r--r--infra/translate-utils/src/test/java/io/fd/honeycomb/translate/util/read/cache/IdentifierCacheKeyFactoryTest.java144
4 files changed, 323 insertions, 8 deletions
diff --git a/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/CacheKeyFactory.java b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/CacheKeyFactory.java
new file mode 100644
index 000000000..1b444ba3c
--- /dev/null
+++ b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/CacheKeyFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.translate.util.read.cache;
+
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Provides keys for provided {@code InstanceIdentifier}
+ */
+public interface CacheKeyFactory {
+
+ /**
+ * Construct key accordingly to provided {@code InstanceIdentifier<?>}
+ */
+ String createKey(@Nonnull final InstanceIdentifier<?> actualContextIdentifier);
+}
diff --git a/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/DumpCacheManager.java b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/DumpCacheManager.java
index f5895038e..f1b265dec 100644
--- a/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/DumpCacheManager.java
+++ b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/DumpCacheManager.java
@@ -30,6 +30,9 @@ import org.slf4j.LoggerFactory;
/**
* Manager responsible for returning Data object dumps<br> either from cache or by invoking specified {@link
* EntityDumpExecutor}
+ *
+ * @param <T> Type of data returned by {@code EntityDumpExecutor},and stored cache
+ * @param <U> Type of dumping params
*/
public final class DumpCacheManager<T, U> {
@@ -37,27 +40,29 @@ public final class DumpCacheManager<T, U> {
private final EntityDumpExecutor<T, U> dumpExecutor;
private final EntityDumpPostProcessingFunction<T> postProcessor;
+ private final CacheKeyFactory cacheKeyFactory;
private DumpCacheManager(DumpCacheManagerBuilder<T, U> builder) {
this.dumpExecutor = builder.dumpExecutor;
this.postProcessor = builder.postProcessingFunction;
+ this.cacheKeyFactory = builder.cacheKeyFactory;
}
/**
* Returns {@link Optional<T>} of dump
*
* @param identifier identifier for origin of dumping context
- * @param entityKey key that defines scope for caching
* @param cache modification cache of current transaction
* @param dumpParams parameters to configure dump request
* @throws ReadFailedException if execution of dumping request failed
- * @returns If present in cache ,returns cached instance, if not, tries to dump data using provided executor, otherwise
- * Optional.absent()
+ * @returns If present in cache ,returns cached instance, if not, tries to dump data using provided executor,
+ * otherwise Optional.absent()
*/
- public Optional<T> getDump(@Nonnull final InstanceIdentifier<?> identifier, @Nonnull String entityKey,
- @Nonnull ModificationCache cache, final U dumpParams)
+ public Optional<T> getDump(@Nonnull final InstanceIdentifier<?> identifier,
+ @Nonnull final ModificationCache cache, final U dumpParams)
throws ReadFailedException {
+ final String entityKey = this.cacheKeyFactory.createKey(identifier);
// this key binding to every log has its logic ,because every customizer have its own cache manager and if
// there is need for debugging/fixing some complex call with a lot of data,you can get lost in those logs
LOG.debug("Loading dump for KEY[{}]", entityKey);
@@ -81,29 +86,42 @@ public final class DumpCacheManager<T, U> {
public static final class DumpCacheManagerBuilder<T, U> {
+ private static final CacheKeyFactory DEFAULT_CACHE_KEY_FACTORY_INSTANCE = new IdentifierCacheKeyFactory();
+
private EntityDumpExecutor<T, U> dumpExecutor;
private EntityDumpPostProcessingFunction<T> postProcessingFunction;
+ private CacheKeyFactory cacheKeyFactory;
public DumpCacheManagerBuilder() {
// for cases when user does not set specific post-processor
postProcessingFunction = new NoopDumpPostProcessingFunction<T>();
+
+ //use no additional scopes version by default
+ cacheKeyFactory = DEFAULT_CACHE_KEY_FACTORY_INSTANCE;
}
- public DumpCacheManagerBuilder<T, U> withExecutor(@Nonnull EntityDumpExecutor<T, U> executor) {
+ public DumpCacheManagerBuilder<T, U> withExecutor(@Nonnull final EntityDumpExecutor<T, U> executor) {
this.dumpExecutor = executor;
return this;
}
public DumpCacheManagerBuilder<T, U> withPostProcessingFunction(
- EntityDumpPostProcessingFunction<T> postProcessingFunction) {
+ @Nonnull final EntityDumpPostProcessingFunction<T> postProcessingFunction) {
this.postProcessingFunction = postProcessingFunction;
return this;
}
+ public DumpCacheManagerBuilder<T, U> withCacheKeyFactory(@Nonnull final CacheKeyFactory cacheKeyFactory) {
+ this.cacheKeyFactory = cacheKeyFactory;
+ return this;
+ }
+
public DumpCacheManager<T, U> build() {
checkNotNull(dumpExecutor, "Dump executor cannot be null");
checkNotNull(postProcessingFunction,
- "Dump post-processor cannot be null cannot be null, default implementation is used if its not set");
+ "Dump post-processor cannot be null cannot be null, default implementation is used when not set explicitly");
+ checkNotNull(cacheKeyFactory,
+ "Cache key factory cannot be null, default non-extended implementation is used when not set explicitly");
return new DumpCacheManager<>(this);
}
diff --git a/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/IdentifierCacheKeyFactory.java b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/IdentifierCacheKeyFactory.java
new file mode 100644
index 000000000..51f47e137
--- /dev/null
+++ b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/read/cache/IdentifierCacheKeyFactory.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.translate.util.read.cache;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Factory providing cache keys to easier switching between scopes of caching
+ */
+public final class IdentifierCacheKeyFactory implements CacheKeyFactory {
+
+ private static final String KEY_PARTS_SEPARATOR = "|";
+
+ // should be Set<Class<? extends DataObject & Identificable<?>>>, but that's not possible for wildcards
+ private final Set<Class<? extends DataObject>> additionalKeyTypes;
+
+ /**
+ * Construct simple cache key factory
+ */
+ public IdentifierCacheKeyFactory() {
+ this(Collections.emptySet());
+ }
+
+ /**
+ * @param additionalKeyTypes Additional types from path of cached type, that are specifying scope
+ */
+ public IdentifierCacheKeyFactory(@Nonnull final Set<Class<? extends DataObject>> additionalKeyTypes) {
+ // verify that all are non-null and identifiable
+ this.additionalKeyTypes = checkNotNull(additionalKeyTypes, "Additional key types can't be null").stream()
+ .map(IdentifierCacheKeyFactory::verifyNotNull)
+ .map(IdentifierCacheKeyFactory::verifyIsIdentifiable)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public String createKey(@Nonnull final InstanceIdentifier<?> actualContextIdentifier) {
+
+ checkNotNull(actualContextIdentifier, "Cannot construct key for null InstanceIdentifier");
+
+ // easiest case when only simple key is needed
+ if (additionalKeyTypes.isEmpty()) {
+ return actualContextIdentifier.getTargetType().toString();
+ }
+
+ checkArgument(isUniqueKeyConstructable(actualContextIdentifier),
+ "Unable to construct unique key, required key types : %s, provided paths : %s", additionalKeyTypes,
+ actualContextIdentifier.getPathArguments());
+
+ return String
+ .join(KEY_PARTS_SEPARATOR, additionalKeys(actualContextIdentifier),
+ actualContextIdentifier.getTargetType().toString());
+ }
+
+ /**
+ * Verifies that all requested key parts have keys
+ */
+ private boolean isUniqueKeyConstructable(final InstanceIdentifier<?> actualContextIdentifier) {
+ return StreamSupport.stream(actualContextIdentifier.getPathArguments().spliterator(), false)
+ .filter(this::isAdditionalScope)
+ .filter(this::isIdentifiable)
+ .count() == additionalKeyTypes.size();
+ }
+
+ private boolean isAdditionalScope(final InstanceIdentifier.PathArgument pathArgument) {
+ return additionalKeyTypes.contains(pathArgument.getType());
+ }
+
+ private boolean isIdentifiable(final InstanceIdentifier.PathArgument pathArgument) {
+ return pathArgument instanceof IdentifiableItem;
+ }
+
+
+ private String additionalKeys(final InstanceIdentifier<?> actualContextIdentifier) {
+ return StreamSupport.stream(actualContextIdentifier.getPathArguments().spliterator(), false)
+ .filter(this::isAdditionalScope)
+ .filter(this::isIdentifiable)
+ .map(IdentifiableItem.class::cast)
+ .map(IdentifierCacheKeyFactory::bindKeyString)
+ .collect(Collectors.joining(KEY_PARTS_SEPARATOR));
+ }
+
+ private static String bindKeyString(IdentifiableItem identifiableItem) {
+ return String.format("%s[%s]", identifiableItem.getType().getTypeName(), identifiableItem.getKey());
+ }
+
+ private static Class<? extends DataObject> verifyNotNull(final Class<? extends DataObject> type) {
+ return checkNotNull(type, "Cannot use null as key");
+ }
+
+ /**
+ * Initial check if provided scope variables are identifiable aka. can be used to create unique cache key
+ */
+ private static Class<? extends DataObject> verifyIsIdentifiable(final Class<? extends DataObject> type) {
+ checkArgument(Identifiable.class.isAssignableFrom(type), "Type %s is not Identifiable", type);
+ return type;
+ }
+}
diff --git a/infra/translate-utils/src/test/java/io/fd/honeycomb/translate/util/read/cache/IdentifierCacheKeyFactoryTest.java b/infra/translate-utils/src/test/java/io/fd/honeycomb/translate/util/read/cache/IdentifierCacheKeyFactoryTest.java
new file mode 100644
index 000000000..e7eae552c
--- /dev/null
+++ b/infra/translate-utils/src/test/java/io/fd/honeycomb/translate/util/read/cache/IdentifierCacheKeyFactoryTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.translate.util.read.cache;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.binding.ChildOf;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
+import org.opendaylight.yangtools.yang.binding.Identifier;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class IdentifierCacheKeyFactoryTest {
+
+ private interface SuperDataObject extends DataObject {
+ }
+
+ private interface DataObjectParent extends DataObject, ChildOf<SuperDataObject>, Identifiable<DataObjectParentKey> {
+ }
+
+ private class DataObjectParentKey implements Identifier<DataObjectParent> {
+ }
+
+ private interface DataObjectChild extends DataObject, ChildOf<DataObjectParent>, Identifiable<DataObjectChildKey> {
+ }
+
+ private class DataObjectChildKey implements Identifier<DataObjectChild> {
+ }
+
+ private DataObjectParentKey parentKey;
+ private DataObjectChildKey childKey;
+ private InstanceIdentifier<DataObjectChild> identifierBothKeyed;
+ private InstanceIdentifier<DataObjectChild> identifierOneMissing;
+ private InstanceIdentifier<DataObjectChild> identifierNoneKeyed;
+
+ private IdentifierCacheKeyFactory simpleKeyFactory;
+ private IdentifierCacheKeyFactory complexKeyFactory;
+
+ @Before
+ public void init() {
+ parentKey = new DataObjectParentKey();
+ childKey = new DataObjectChildKey();
+ identifierBothKeyed = InstanceIdentifier.create(SuperDataObject.class).child(DataObjectParent.class, parentKey)
+ .child(DataObjectChild.class, childKey);
+ identifierOneMissing = InstanceIdentifier.create(DataObjectChild.class);
+ identifierNoneKeyed = InstanceIdentifier.create(SuperDataObject.class).child(DataObjectParent.class)
+ .child(DataObjectChild.class);
+
+ complexKeyFactory = new IdentifierCacheKeyFactory(ImmutableSet.of(DataObjectParent.class));
+ simpleKeyFactory = new IdentifierCacheKeyFactory();
+ }
+
+ @Test
+ public void createKeyBothKeyedComplex() {
+ final String key = complexKeyFactory.createKey(identifierBothKeyed);
+
+ /**
+ * Should pass because key constructed in this case should look like :
+ * additional_scope_type[additional_scope_type_key]|cached_type
+ * */
+ verifyComplexKey(key);
+ }
+
+ /**
+ * Should fail because provided identifier does'nt contain all requested key parts
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void createKeyOneMissingComplex() {
+ complexKeyFactory.createKey(identifierOneMissing);
+ }
+
+ /**
+ * Should fail because request paths are not keyed
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void createKeyNoneKeyedComplex() {
+ complexKeyFactory.createKey(identifierNoneKeyed);
+ }
+
+ @Test
+ public void createKeyBothKeyedSimple() {
+ final String key = simpleKeyFactory.createKey(identifierBothKeyed);
+
+ /**
+ * Should pass because key constructed in this case should look like : cached_type
+ * */
+ verifySimpleKey(key);
+ }
+
+ @Test
+ public void createKeyOneMissingSimple() {
+ final String key = simpleKeyFactory.createKey(identifierOneMissing);
+ /**
+ * Should pass because key constructed in this case should look like : cached_type
+ * */
+ verifySimpleKey(key);
+ }
+
+ /**
+ * Should fail because request paths are not keyed
+ */
+ @Test
+ public void createKeyNoneKeyedSimple() {
+ final String key = simpleKeyFactory.createKey(identifierNoneKeyed);
+ /**
+ * Should pass because key constructed in this case should look like : cached_type
+ * */
+ verifySimpleKey(key);
+ }
+
+ private void verifyComplexKey(final String key) {
+ assertTrue(key.contains(DataObjectParent.class.getTypeName()));
+ assertTrue(key.contains(parentKey.toString()));
+ assertTrue(key.contains(DataObjectChild.class.getTypeName()));
+ assertFalse(key.contains(childKey.toString()));
+ assertFalse(key.contains(SuperDataObject.class.getTypeName()));
+ }
+
+ private void verifySimpleKey(final String key) {
+ assertFalse(key.contains(DataObjectParent.class.getTypeName()));
+ assertFalse(key.contains(parentKey.toString()));
+ assertTrue(key.contains(DataObjectChild.class.getTypeName()));
+ assertFalse(key.contains(childKey.toString()));
+ assertFalse(key.contains(SuperDataObject.class.getTypeName()));
+ }
+} \ No newline at end of file