add caching to graph inventory client 50/115150/1
authorBenjamin, Max <max.benjamin@att.com>
Thu, 19 Nov 2020 23:12:50 +0000 (18:12 -0500)
committerBenjamin, Max (mb388a) <mb388a@att.com>
Thu, 19 Nov 2020 23:12:51 +0000 (18:12 -0500)
add caching to graph inventory client
updated properties files to read cache properties

Issue-ID: SO-3398
Signed-off-by: Benjamin, Max (mb388a) <mb388a@att.com>
Change-Id: Ib3e67ae014b6668c9b004aae1e8b5d49b9ce6b06

20 files changed:
adapters/mso-openstack-adapters/src/main/java/org/onap/so/adapters/openstack/AaiClientPropertiesImpl.java
asdc-controller/src/main/java/org/onap/so/asdc/tenantIsolation/AaiClientPropertiesImpl.java
bpmn/MSOCommonBPMN/src/main/java/org/onap/so/client/restproperties/AAIPropertiesImpl.java
common/pom.xml
common/src/main/java/org/onap/so/client/AddCacheHeaders.java [new file with mode: 0644]
common/src/main/java/org/onap/so/client/CacheFactory.java [new file with mode: 0644]
common/src/main/java/org/onap/so/client/CacheProperties.java [new file with mode: 0644]
common/src/main/java/org/onap/so/client/RestClient.java
common/src/main/java/org/onap/so/client/RestClientSSL.java
common/src/main/java/org/onap/so/client/RestProperties.java
common/src/test/java/org/onap/so/client/RestClientTest.java
common/src/test/resources/logback-test.xml
graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/CacheControlFeature.java [new file with mode: 0644]
graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/CacheLogger.java [new file with mode: 0644]
graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/FlushCache.java [new file with mode: 0644]
graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/aai/AAIProperties.java
graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/graphinventory/GraphInventoryRestClient.java
graph-inventory/aai-client/src/test/java/org/onap/aaiclient/client/aai/AAIRestClientTest.java
mso-api-handlers/mso-api-handler-infra/src/main/java/org/onap/so/apihandlerinfra/tenantisolation/AaiClientPropertiesImpl.java
so-etsi-nfvo/so-etsi-nfvo-ns-lcm/so-etsi-nfvo-ns-lcm-bpmn-flows/src/main/java/org/onap/so/etsi/nfvo/ns/lcm/bpmn/flows/extclients/aai/AaiPropertiesImpl.java

index b7e214f..cd32cc2 100644 (file)
@@ -24,6 +24,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import org.onap.aaiclient.client.aai.AAIProperties;
 import org.onap.aaiclient.client.aai.AAIVersion;
+import org.onap.so.client.CacheProperties;
 import org.onap.so.spring.SpringContextHelper;
 import org.springframework.context.ApplicationContext;
 
@@ -33,6 +34,8 @@ public class AaiClientPropertiesImpl implements AAIProperties {
     private String auth;
     private String key;
     private Long readTimeout;
+    private boolean enableCaching;
+    private Long cacheMaxAge;
     private static final String SYSTEM_NAME = "MSO";
 
     public AaiClientPropertiesImpl() {
@@ -41,6 +44,8 @@ public class AaiClientPropertiesImpl implements AAIProperties {
         this.auth = context.getEnvironment().getProperty("aai.auth");
         this.key = context.getEnvironment().getProperty("mso.msoKey");
         this.readTimeout = context.getEnvironment().getProperty("aai.readTimeout", Long.class, new Long(60000));
+        this.enableCaching = context.getEnvironment().getProperty("aai.caching.enabled", Boolean.class, false);
+        this.cacheMaxAge = context.getEnvironment().getProperty("aai.caching.maxAge", Long.class, 60000L);
     }
 
     @Override
@@ -72,4 +77,19 @@ public class AaiClientPropertiesImpl implements AAIProperties {
     public Long getReadTimeout() {
         return this.readTimeout;
     }
+
+    @Override
+    public boolean isCachingEnabled() {
+        return this.enableCaching;
+    }
+
+    @Override
+    public CacheProperties getCacheProperties() {
+        return new AAICacheProperties() {
+            @Override
+            public Long getMaxAge() {
+                return cacheMaxAge;
+            }
+        };
+    }
 }
index 7b89af0..ace0ff1 100644 (file)
@@ -24,6 +24,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import org.onap.aaiclient.client.aai.AAIProperties;
 import org.onap.aaiclient.client.aai.AAIVersion;
+import org.onap.so.client.CacheProperties;
 import org.onap.so.spring.SpringContextHelper;
 import org.springframework.context.ApplicationContext;
 
@@ -33,6 +34,8 @@ public class AaiClientPropertiesImpl implements AAIProperties {
     private String auth;
     private String key;
     private Long readTimeout;
+    private boolean enableCaching;
+    private Long cacheMaxAge;
     private static final String SYSTEM_NAME = "MSO";
 
     public AaiClientPropertiesImpl() {
@@ -41,6 +44,8 @@ public class AaiClientPropertiesImpl implements AAIProperties {
         this.auth = context.getEnvironment().getProperty("aai.auth");
         this.key = context.getEnvironment().getProperty("mso.msoKey");
         this.readTimeout = context.getEnvironment().getProperty("aai.readTimeout", Long.class, new Long(60000));
+        this.enableCaching = context.getEnvironment().getProperty("aai.caching.enabled", Boolean.class, false);
+        this.cacheMaxAge = context.getEnvironment().getProperty("aai.caching.maxAge", Long.class, 60000L);
     }
 
     @Override
@@ -74,4 +79,19 @@ public class AaiClientPropertiesImpl implements AAIProperties {
     public Long getReadTimeout() {
         return this.readTimeout;
     }
+
+    @Override
+    public boolean isCachingEnabled() {
+        return this.enableCaching;
+    }
+
+    @Override
+    public CacheProperties getCacheProperties() {
+        return new AAICacheProperties() {
+            @Override
+            public Long getMaxAge() {
+                return cacheMaxAge;
+            }
+        };
+    }
 }
index f67af20..98a14fc 100644 (file)
@@ -25,6 +25,7 @@ import java.net.URL;
 import org.onap.aaiclient.client.aai.AAIProperties;
 import org.onap.aaiclient.client.aai.AAIVersion;
 import org.onap.so.bpmn.core.UrnPropertiesReader;
+import org.onap.so.client.CacheProperties;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -34,6 +35,9 @@ public class AAIPropertiesImpl implements AAIProperties {
     public static final String AAI_AUTH = "aai.auth";
     public static final String AAI_ENDPOINT = "aai.endpoint";
     public static final String AAI_READ_TIMEOUT = "aai.readTimeout";
+    public static final String AAI_ENABLE_CACHING = "aai.caching.enable";
+    public static final String AAI_CACHE_MAX_AGE = "aai.caching.maxAge";
+
     private UrnPropertiesReader reader;
 
     @Override
@@ -66,4 +70,19 @@ public class AAIPropertiesImpl implements AAIProperties {
         return Long.valueOf(reader.getVariable(AAI_READ_TIMEOUT, "60000"));
     }
 
+    @Override
+    public boolean isCachingEnabled() {
+        return Boolean.parseBoolean(reader.getVariable(AAI_ENABLE_CACHING, "false"));
+    }
+
+    @Override
+    public CacheProperties getCacheProperties() {
+        return new AAICacheProperties() {
+            @Override
+            public Long getMaxAge() {
+                return Long.valueOf(reader.getVariable(AAI_CACHE_MAX_AGE, "60000"));
+            }
+        };
+    }
+
 }
index 74e5180..6e26592 100644 (file)
       <artifactId>jaxb-impl</artifactId>
       <version>2.3.0</version>
     </dependency>
+    <dependency>
+      <groupId>javax.cache</groupId>
+      <artifactId>cache-api</artifactId>
+      <version>1.0.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.ehcache</groupId>
+      <artifactId>ehcache</artifactId>
+      <version>3.8.1</version>
+    </dependency>
   </dependencies>
   <dependencyManagement>
     <dependencies>
       </plugin>
     </plugins>
   </build>
-</project>
\ No newline at end of file
+</project>
diff --git a/common/src/main/java/org/onap/so/client/AddCacheHeaders.java b/common/src/main/java/org/onap/so/client/AddCacheHeaders.java
new file mode 100644 (file)
index 0000000..1a41be1
--- /dev/null
@@ -0,0 +1,28 @@
+package org.onap.so.client;
+
+import java.io.IOException;
+import java.util.Collections;
+import javax.annotation.Priority;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+import javax.ws.rs.ext.Provider;
+
+@Provider
+@Priority(1)
+public class AddCacheHeaders implements ClientResponseFilter {
+
+    private final CacheProperties props;
+
+    public AddCacheHeaders(CacheProperties props) {
+        this.props = props;
+    }
+
+    public void filter(ClientRequestContext request, ClientResponseContext response) throws IOException {
+        if (request.getMethod().equalsIgnoreCase("GET")) {
+            response.getHeaders().putIfAbsent("Cache-Control",
+                    Collections.singletonList("public, max-age=" + (props.getMaxAge() / 1000)));
+        }
+
+    }
+}
diff --git a/common/src/main/java/org/onap/so/client/CacheFactory.java b/common/src/main/java/org/onap/so/client/CacheFactory.java
new file mode 100644 (file)
index 0000000..6bc4858
--- /dev/null
@@ -0,0 +1,25 @@
+package org.onap.so.client;
+
+
+import java.util.concurrent.TimeUnit;
+import javax.cache.configuration.Factory;
+import javax.cache.expiry.Duration;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.expiry.TouchedExpiryPolicy;
+
+public class CacheFactory implements Factory<ExpiryPolicy> {
+
+    private static final long serialVersionUID = 8948728679233836929L;
+
+    private final CacheProperties props;
+
+    public CacheFactory(CacheProperties props) {
+        this.props = props;
+    }
+
+    @Override
+    public ExpiryPolicy create() {
+        return TouchedExpiryPolicy.factoryOf(new Duration(TimeUnit.MILLISECONDS, props.getMaxAge())).create();
+    }
+
+}
diff --git a/common/src/main/java/org/onap/so/client/CacheProperties.java b/common/src/main/java/org/onap/so/client/CacheProperties.java
new file mode 100644 (file)
index 0000000..4fb2a87
--- /dev/null
@@ -0,0 +1,13 @@
+package org.onap.so.client;
+
+public interface CacheProperties {
+
+
+    default Long getMaxAge() {
+        return 60000L;
+    }
+
+    default String getCacheName() {
+        return "default-http-cache";
+    }
+}
index 9fce328..be0a0f3 100644 (file)
@@ -188,8 +188,20 @@ public abstract class RestClient {
         return APPLICATION_MERGE_PATCH_JSON;
     }
 
+    protected ClientBuilder getClientBuilder() {
+        ClientBuilder builder = ClientBuilder.newBuilder();
+        if (props.isCachingEnabled()) {
+            enableCaching(builder);
+        }
+        return builder.readTimeout(props.getReadTimeout(), TimeUnit.MILLISECONDS);
+    }
+
+    protected ClientBuilder enableCaching(ClientBuilder builder) {
+        return builder;
+    }
+
     protected Client getClient() {
-        return ClientBuilder.newBuilder().readTimeout(props.getReadTimeout(), TimeUnit.MILLISECONDS).build();
+        return getClientBuilder().build();
     }
 
     protected abstract ONAPComponentsList getTargetEntity();
index 8956e20..c6252e4 100644 (file)
@@ -24,7 +24,6 @@ import java.net.URI;
 import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
 import java.util.Optional;
-import java.util.concurrent.TimeUnit;
 import javax.net.ssl.SSLContext;
 import javax.ws.rs.client.Client;
 import javax.ws.rs.client.ClientBuilder;
@@ -57,8 +56,7 @@ public abstract class RestClientSSL extends RestClient {
                 }
             }
             // Use default SSL context
-            client = ClientBuilder.newBuilder().sslContext(SSLContext.getDefault())
-                    .readTimeout(props.getReadTimeout(), TimeUnit.MILLISECONDS).build();
+            client = getClientBuilder().sslContext(SSLContext.getDefault()).build();
             logger.info("RestClientSSL using default SSL context!");
         } catch (NoSuchAlgorithmException e) {
             throw new RuntimeException(e);
index 36da424..a7a0ef6 100644 (file)
@@ -49,4 +49,12 @@ public interface RestProperties {
     public default Long getReadTimeout() {
         return Long.valueOf(60000);
     }
+
+    public default boolean isCachingEnabled() {
+        return false;
+    }
+
+    public default CacheProperties getCacheProperties() {
+        return new CacheProperties() {};
+    }
 }
index c6e282c..d40576b 100644 (file)
@@ -49,6 +49,7 @@ import org.mockito.junit.MockitoJUnitRunner;
 import org.onap.logging.filter.base.ONAPComponents;
 import org.onap.logging.filter.base.ONAPComponentsList;
 import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
+import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
 import com.github.tomakehurst.wiremock.junit.WireMockRule;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -61,7 +62,8 @@ public class RestClientTest {
     public ExpectedException thrown = ExpectedException.none();
 
     @Rule
-    public WireMockRule wireMockRule = new WireMockRule(WireMockConfiguration.options().dynamicPort());
+    public WireMockRule wireMockRule = new WireMockRule(
+            WireMockConfiguration.options().dynamicPort().extensions(new ResponseTemplateTransformer(false)));
 
     @Test
     public void retries() throws Exception {
index b52e6be..3c5f259 100644 (file)
   -->
 
 <configuration>
-       <property name="p_tim" value="%d{&quot;yyyy-MM-dd'T'HH:mm:ss.SSSXXX&quot;, UTC}"/>
-    <property name="p_lvl" value="%level"/>
-    <property name="p_log" value="%logger"/>
-    <property name="p_mdc" value="%replace(%replace(%mdc){'\t','\\\\t'}){'\n', '\\\\n'}"/>
-    <property name="p_msg" value="%replace(%replace(%msg){'\t', '\\\\t'}){'\n','\\\\n'}"/>
-    <property name="p_exc" value="%replace(%replace(%rootException){'\t', '\\\\t'}){'\n','\\\\n'}"/>
-    <property name="p_mak" value="%replace(%replace(%marker){'\t', '\\\\t'}){'\n','\\\\n'}"/>
-    <property name="p_thr" value="%thread"/>
-    <property name="pattern" value="%nopexception${p_tim}\t${p_thr}\t${p_lvl}\t${p_log}\t${p_mdc}\t${p_msg}\t${p_exc}\t${p_mak}\t%n"/>
+  <property name="p_tim" value="%d{&quot;yyyy-MM-dd'T'HH:mm:ss.SSSXXX&quot;, UTC}" />
+  <property name="p_lvl" value="%level" />
+  <property name="p_log" value="%logger" />
+  <property name="p_mdc" value="%replace(%replace(%mdc){'\t','\\\\t'}){'\n', '\\\\n'}" />
+  <property name="p_msg" value="%replace(%replace(%msg){'\t', '\\\\t'}){'\n','\\\\n'}" />
+  <property name="p_exc" value="%replace(%replace(%rootException){'\t', '\\\\t'}){'\n','\\\\n'}" />
+  <property name="p_mak" value="%replace(%replace(%marker){'\t', '\\\\t'}){'\n','\\\\n'}" />
+  <property name="p_thr" value="%thread" />
+  <property name="pattern"
+    value="%nopexception${p_tim}\t${p_thr}\t${p_lvl}\t${p_log}\t${p_mdc}\t${p_msg}\t${p_exc}\t${p_mak}\t%n" />
 
 
-       <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
-               <encoder>
-                       <pattern>${pattern}</pattern>
-               </encoder>
-       </appender>
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>${pattern}</pattern>
+    </encoder>
+  </appender>
 
-       <appender name="test"
-               class="org.onap.so.utils.TestAppender" />
+  <appender name="test" class="org.onap.so.utils.TestAppender" />
 
-       <logger name="com.att.ecomp.audit" level="info" additivity="false">
-               <appender-ref ref="STDOUT" />
-       </logger>
+  <logger name="com.att.ecomp.audit" level="info" additivity="false">
+    <appender-ref ref="STDOUT" />
+  </logger>
 
-       <logger name="com.att.eelf.metrics" level="info" additivity="false">
-               <appender-ref ref="STDOUT" />
-       </logger>
+  <logger name="com.att.eelf.metrics" level="info" additivity="false">
+    <appender-ref ref="STDOUT" />
+  </logger>
 
-       <logger name="com.att.eelf.error" level="WARN" additivity="false">
-               <appender-ref ref="STDOUT" />
-       </logger>
+  <logger name="com.att.eelf.error" level="WARN" additivity="false">
+    <appender-ref ref="STDOUT" />
+  </logger>
 
-       <logger name="org.onap" level="${so.log.level:-DEBUG}" additivity="false">
-               <appender-ref ref="STDOUT" />
-               <appender-ref ref="test" />
-       </logger>
-       
-       <logger name="org.flywaydb" level="DEBUG" additivity="false">
-        <appender-ref ref="STDOUT" />
-    </logger>
-       
+  <logger name="org.onap" level="${so.log.level:-DEBUG}" additivity="false">
+    <appender-ref ref="STDOUT" />
+    <appender-ref ref="test" />
+  </logger>
 
-       <logger name="ch.vorburger" level="WARN" additivity="false">
-               <appender-ref ref="STDOUT" />
-       </logger>
-       
-       <logger name="org.reflections" level="ERROR" additivity="false">
-               <appender-ref ref="STDOUT" />
-       </logger>
-       
+  <logger name="org.flywaydb" level="DEBUG" additivity="false">
+    <appender-ref ref="STDOUT" />
+  </logger>
 
-       <root level="WARN">
-               <appender-ref ref="STDOUT" />
-               <appender-ref ref="test" />
-       </root>
+
+  <logger name="ch.vorburger" level="WARN" additivity="false">
+    <appender-ref ref="STDOUT" />
+  </logger>
+
+  <logger name="org.reflections" level="ERROR" additivity="false">
+    <appender-ref ref="STDOUT" />
+  </logger>
+
+  <root level="WARN">
+    <appender-ref ref="STDOUT" />
+    <appender-ref ref="test" />
+  </root>
 
 
 </configuration>
\ No newline at end of file
diff --git a/graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/CacheControlFeature.java b/graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/CacheControlFeature.java
new file mode 100644 (file)
index 0000000..1e7c3e7
--- /dev/null
@@ -0,0 +1,137 @@
+package org.onap.aaiclient.client;
+
+import java.io.Closeable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Properties;
+import javax.annotation.PreDestroy;
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.Factory;
+import javax.cache.configuration.FactoryBuilder;
+import javax.cache.configuration.MutableCacheEntryListenerConfiguration;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.integration.CacheLoader;
+import javax.cache.integration.CacheWriter;
+import javax.cache.spi.CachingProvider;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+import javax.ws.rs.ext.Provider;
+import org.apache.cxf.jaxrs.client.cache.CacheControlClientReaderInterceptor;
+import org.apache.cxf.jaxrs.client.cache.CacheControlClientRequestFilter;
+import org.apache.cxf.jaxrs.client.cache.Entry;
+import org.apache.cxf.jaxrs.client.cache.Key;
+
+
+@Provider
+public class CacheControlFeature implements Feature, Closeable {
+    private CachingProvider provider;
+    private CacheManager manager;
+    private Cache<Key, Entry> cache;
+    private boolean cacheResponseInputStream;
+    private Factory<ExpiryPolicy> expiryPolicy;
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        // TODO: read context properties to exclude some patterns?
+        final Cache<Key, Entry> entryCache = createCache(context.getConfiguration().getProperties());
+        context.register(new CacheControlClientRequestFilter(entryCache));
+        CacheControlClientReaderInterceptor reader = new CacheControlClientReaderInterceptor(entryCache);
+        reader.setCacheResponseInputStream(cacheResponseInputStream);
+        context.register(reader);
+        return true;
+    }
+
+    @PreDestroy // TODO: check it is called
+    public void close() {
+        for (final Closeable c : Arrays.asList(cache, manager, provider)) {
+            try {
+                if (c != null) {
+                    c.close();
+                }
+            } catch (final Exception e) {
+                // no-op
+            }
+        }
+    }
+
+    private Cache<Key, Entry> createCache(final Map<String, Object> properties) {
+        final Properties props = new Properties();
+        props.putAll(properties);
+
+        final String prefix = this.getClass().getName() + ".";
+        final String uri = props.getProperty(prefix + "config-uri");
+        final String name = props.getProperty(prefix + "name", this.getClass().getName());
+
+        final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+
+        provider = Caching.getCachingProvider();
+        try {
+            synchronized (contextClassLoader) {
+                manager = provider.getCacheManager(uri == null ? provider.getDefaultURI() : new URI(uri),
+                        contextClassLoader, props);
+                if (manager.getCache(name) == null) {
+                    final MutableConfiguration<Key, Entry> configuration = new MutableConfiguration<Key, Entry>()
+                            .setReadThrough("true".equalsIgnoreCase(props.getProperty(prefix + "readThrough", "false")))
+                            .setWriteThrough(
+                                    "true".equalsIgnoreCase(props.getProperty(prefix + "writeThrough", "false")))
+                            .setManagementEnabled(
+                                    "true".equalsIgnoreCase(props.getProperty(prefix + "managementEnabled", "false")))
+                            .setStatisticsEnabled(
+                                    "true".equalsIgnoreCase(props.getProperty(prefix + "statisticsEnabled", "false")))
+                            .setStoreByValue(
+                                    "true".equalsIgnoreCase(props.getProperty(prefix + "storeByValue", "false")));
+
+                    final String loader = props.getProperty(prefix + "loaderFactory");
+                    if (loader != null) {
+                        @SuppressWarnings("unchecked")
+                        Factory<? extends CacheLoader<Key, Entry>> f =
+                                newInstance(contextClassLoader, loader, Factory.class);
+                        configuration.setCacheLoaderFactory(f);
+                    }
+                    final String writer = props.getProperty(prefix + "writerFactory");
+                    if (writer != null) {
+                        @SuppressWarnings("unchecked")
+                        Factory<? extends CacheWriter<Key, Entry>> f =
+                                newInstance(contextClassLoader, writer, Factory.class);
+                        configuration.setCacheWriterFactory(f);
+                    }
+                    if (expiryPolicy != null) {
+
+                        configuration.setExpiryPolicyFactory(expiryPolicy);
+                    }
+                    configuration.addCacheEntryListenerConfiguration(new MutableCacheEntryListenerConfiguration(
+                            FactoryBuilder.factoryOf(new CacheLogger()), null, true, true));
+
+                    cache = manager.createCache(name, configuration);
+                } else {
+                    cache = manager.getCache(name);
+                }
+                return cache;
+            }
+        } catch (final URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T newInstance(final ClassLoader contextClassLoader, final String clazz, final Class<T> cast) {
+        try {
+            return (T) contextClassLoader.loadClass(clazz).newInstance();
+        } catch (final Exception e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    public void setCacheResponseInputStream(boolean cacheStream) {
+        this.cacheResponseInputStream = cacheStream;
+    }
+
+    public void setExpiryPolicyFactory(Factory<ExpiryPolicy> f) {
+        this.expiryPolicy = f;
+    }
+}
diff --git a/graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/CacheLogger.java b/graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/CacheLogger.java
new file mode 100644 (file)
index 0000000..f3dc610
--- /dev/null
@@ -0,0 +1,53 @@
+package org.onap.aaiclient.client;
+
+import java.io.Serializable;
+import javax.cache.event.CacheEntryCreatedListener;
+import javax.cache.event.CacheEntryEvent;
+import javax.cache.event.CacheEntryExpiredListener;
+import javax.cache.event.CacheEntryListenerException;
+import javax.cache.event.CacheEntryRemovedListener;
+import javax.cache.event.CacheEntryUpdatedListener;
+import org.apache.cxf.jaxrs.client.cache.Entry;
+import org.apache.cxf.jaxrs.client.cache.Key;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CacheLogger implements CacheEntryExpiredListener<Key, Entry>, CacheEntryCreatedListener<Key, Entry>,
+        CacheEntryUpdatedListener<Key, Entry>, CacheEntryRemovedListener<Key, Entry>, Serializable {
+
+    private static final long serialVersionUID = 1L;
+    private static final Logger logger = LoggerFactory.getLogger(CacheLogger.class);
+
+    @Override
+    public void onExpired(Iterable<CacheEntryEvent<? extends Key, ? extends Entry>> events)
+            throws CacheEntryListenerException {
+        for (CacheEntryEvent<? extends Key, ? extends Entry> event : events) {
+            logger.debug("{} expired key: {}", event.getSource().getName(), event.getKey().getUri());
+        }
+    }
+
+    @Override
+    public void onRemoved(Iterable<CacheEntryEvent<? extends Key, ? extends Entry>> events)
+            throws CacheEntryListenerException {
+        for (CacheEntryEvent<? extends Key, ? extends Entry> event : events) {
+            logger.debug("{} removed key: {}", event.getSource().getName(), event.getKey().getUri());
+        }
+    }
+
+    @Override
+    public void onUpdated(Iterable<CacheEntryEvent<? extends Key, ? extends Entry>> events)
+            throws CacheEntryListenerException {
+        for (CacheEntryEvent<? extends Key, ? extends Entry> event : events) {
+            logger.debug("{} updated key: {}", event.getSource().getName(), event.getKey().getUri());
+        }
+    }
+
+    @Override
+    public void onCreated(Iterable<CacheEntryEvent<? extends Key, ? extends Entry>> events)
+            throws CacheEntryListenerException {
+        for (CacheEntryEvent<? extends Key, ? extends Entry> event : events) {
+            logger.debug("{} created key: {}", event.getSource().getName(), event.getKey().getUri());
+        }
+    }
+
+}
diff --git a/graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/FlushCache.java b/graph-inventory/aai-client/src/main/java/org/onap/aaiclient/client/FlushCache.java
new file mode 100644 (file)
index 0000000..0f290ff
--- /dev/null
@@ -0,0 +1,41 @@
+package org.onap.aaiclient.client;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+import org.apache.cxf.jaxrs.client.cache.Key;
+import org.onap.so.client.CacheProperties;
+
+public class FlushCache implements ClientResponseFilter {
+
+    private static final Set<String> modifyMethods =
+            new HashSet<>(Arrays.asList(HttpMethod.DELETE, HttpMethod.PATCH, HttpMethod.PUT, HttpMethod.POST));
+
+    private final CacheProperties props;
+
+    public FlushCache(CacheProperties props) {
+        this.props = props;
+    }
+
+    @Override
+    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
+
+        if (responseContext.getStatus() >= 200 && responseContext.getStatus() <= 299) {
+            if (FlushCache.modifyMethods.contains(requestContext.getMethod())) {
+
+                CacheManager cacheManager = Caching.getCachingProvider().getCacheManager(
+                        Caching.getCachingProvider().getDefaultURI(), Thread.currentThread().getContextClassLoader());
+                cacheManager.getCache(props.getCacheName()).remove(
+                        new Key(requestContext.getUri(), requestContext.getAcceptableMediaTypes().get(0).toString()));
+            }
+        }
+    }
+
+}
index ac8a6e6..9c7798f 100644 (file)
@@ -20,6 +20,7 @@
 
 package org.onap.aaiclient.client.aai;
 
+import org.onap.so.client.CacheProperties;
 import org.onap.so.client.RestProperties;
 
 public interface AAIProperties extends RestProperties {
@@ -34,4 +35,15 @@ public interface AAIProperties extends RestProperties {
     public default boolean mapNotFoundToEmpty() {
         return true;
     }
+
+    default CacheProperties getCacheProperties() {
+        return new AAICacheProperties() {};
+    }
+
+    public interface AAICacheProperties extends CacheProperties {
+
+        default String getCacheName() {
+            return "aai-http-cache";
+        }
+    }
 }
index c242208..c22f2f5 100644 (file)
@@ -23,8 +23,13 @@ package org.onap.aaiclient.client.graphinventory;
 import java.net.URI;
 import java.util.Map;
 import java.util.Optional;
+import javax.ws.rs.client.ClientBuilder;
 import javax.ws.rs.core.Response;
+import org.onap.aaiclient.client.CacheControlFeature;
+import org.onap.aaiclient.client.FlushCache;
 import org.onap.logging.filter.base.ONAPComponentsList;
+import org.onap.so.client.AddCacheHeaders;
+import org.onap.so.client.CacheFactory;
 import org.onap.so.client.ResponseExceptionMapper;
 import org.onap.so.client.RestClientSSL;
 import org.onap.so.client.RestProperties;
@@ -41,6 +46,21 @@ public abstract class GraphInventoryRestClient extends RestClientSSL {
         super(props, Optional.of(uri));
     }
 
+
+    protected ClientBuilder enableCaching(ClientBuilder builder) {
+        builder.register(new AddCacheHeaders(props.getCacheProperties()));
+        builder.register(new FlushCache(props.getCacheProperties()));
+        CacheControlFeature cacheControlFeature = new CacheControlFeature();
+        cacheControlFeature.setCacheResponseInputStream(true);
+        cacheControlFeature.setExpiryPolicyFactory(new CacheFactory(props.getCacheProperties()));
+        builder.property("org.onap.aaiclient.client.CacheControlFeature.name",
+                props.getCacheProperties().getCacheName());
+
+        builder.register(cacheControlFeature);
+
+        return builder;
+    }
+
     @Override
     public abstract ONAPComponentsList getTargetEntity();
 
index b73454f..d0f7847 100644 (file)
@@ -25,7 +25,11 @@ import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
 import static com.github.tomakehurst.wiremock.client.WireMock.get;
 import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
 import static com.github.tomakehurst.wiremock.client.WireMock.matching;
+import static com.github.tomakehurst.wiremock.client.WireMock.put;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
 import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
+import static com.github.tomakehurst.wiremock.client.WireMock.verify;
 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.mockito.ArgumentMatchers.any;
@@ -35,8 +39,10 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.URL;
 import java.util.HashMap;
 import javax.ws.rs.core.Response;
 import org.junit.Rule;
@@ -48,6 +54,7 @@ import org.mockito.junit.MockitoJUnitRunner;
 import org.onap.aaiclient.client.defaultproperties.DefaultAAIPropertiesImpl;
 import org.onap.aaiclient.client.graphinventory.GraphInventoryPatchConverter;
 import org.onap.aaiclient.client.graphinventory.exceptions.GraphInventoryPatchDepthExceededException;
+import org.onap.so.client.RestClient;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.github.tomakehurst.wiremock.junit.WireMockRule;
 import com.google.common.collect.ImmutableMap;
@@ -96,4 +103,113 @@ public class AAIRestClientTest {
         wireMockRule.verify(getRequestedFor(urlPathEqualTo("/test")).withHeader("X-FromAppId", equalTo("MSO"))
                 .withHeader("X-TransactionId", matching(".*")).withHeader("test", equalTo("value")));
     }
+
+
+    @Test
+    public void cacheGetTest() throws URISyntaxException, InterruptedException {
+
+        wireMockRule.stubFor(get(urlPathMatching("/cached"))
+                .willReturn(aResponse().withStatus(200).withHeader("Content-Type", "text/plain").withBody("value")));
+
+        AAIProperties props = new AAIProperties() {
+
+            @Override
+            public URL getEndpoint() throws MalformedURLException {
+                return new URL(String.format("http://localhost:%s", wireMockRule.port()));
+            }
+
+            @Override
+            public String getSystemName() {
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public boolean isCachingEnabled() {
+                return true;
+            }
+
+            @Override
+            public AAIVersion getDefaultVersion() {
+                return AAIVersion.LATEST;
+            }
+
+            @Override
+            public String getAuth() {
+                return null;
+            }
+
+            @Override
+            public String getKey() {
+                return null;
+            }
+
+        };
+        RestClient client = new AAIRestClient(props, new URI("/cached"), new HashMap<String, String>());
+
+        Response response = client.get();
+
+        response.readEntity(String.class);
+        response = client.get();
+        response.readEntity(String.class);
+        verify(1, getRequestedFor(urlEqualTo("/cached")));
+
+    }
+
+    @Test
+    public void cachePutTest() throws URISyntaxException, InterruptedException {
+
+        wireMockRule.stubFor(put(urlPathMatching("/cached/1")).willReturn(aResponse().withStatus(200)));
+
+        wireMockRule.stubFor(get(urlPathMatching("/cached/1"))
+                .willReturn(aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody("{}")));
+
+        AAIProperties props = new AAIProperties() {
+
+            @Override
+            public URL getEndpoint() throws MalformedURLException {
+                return new URL(String.format("http://localhost:%s", wireMockRule.port()));
+            }
+
+            @Override
+            public String getSystemName() {
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public boolean isCachingEnabled() {
+                return true;
+            }
+
+            @Override
+            public AAIVersion getDefaultVersion() {
+                return AAIVersion.LATEST;
+            }
+
+            @Override
+            public String getAuth() {
+                return null;
+            }
+
+            @Override
+            public String getKey() {
+                return null;
+            }
+
+        };
+
+        RestClient client = new AAIRestClient(props, new URI("/cached/1"), new HashMap<String, String>());
+
+
+        Response response = client.get();
+
+        response.readEntity(String.class);
+        client.put("wow");
+
+        client.get();
+        response.readEntity(String.class);
+        verify(2, getRequestedFor(urlEqualTo("/cached/1")));
+
+    }
 }
index 6e6a9d2..1492baf 100644 (file)
@@ -24,6 +24,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import org.onap.aaiclient.client.aai.AAIProperties;
 import org.onap.aaiclient.client.aai.AAIVersion;
+import org.onap.so.client.CacheProperties;
 import org.onap.so.spring.SpringContextHelper;
 import org.springframework.context.ApplicationContext;
 
@@ -33,6 +34,8 @@ public class AaiClientPropertiesImpl implements AAIProperties {
     private String auth;
     private String key;
     private Long readTimeout;
+    private boolean enableCaching;
+    private Long cacheMaxAge;
 
     public AaiClientPropertiesImpl() {
 
@@ -41,6 +44,8 @@ public class AaiClientPropertiesImpl implements AAIProperties {
         this.auth = context.getEnvironment().getProperty("aai.auth");
         this.key = context.getEnvironment().getProperty("mso.msoKey");
         this.readTimeout = context.getEnvironment().getProperty("aai.readTimeout", Long.class, new Long(60000));
+        this.enableCaching = context.getEnvironment().getProperty("aai.caching.enabled", Boolean.class, false);
+        this.cacheMaxAge = context.getEnvironment().getProperty("aai.caching.maxAge", Long.class, 60000L);
     }
 
     @Override
@@ -72,4 +77,19 @@ public class AaiClientPropertiesImpl implements AAIProperties {
     public Long getReadTimeout() {
         return this.readTimeout;
     }
+
+    @Override
+    public boolean isCachingEnabled() {
+        return this.enableCaching;
+    }
+
+    @Override
+    public CacheProperties getCacheProperties() {
+        return new AAICacheProperties() {
+            @Override
+            public Long getMaxAge() {
+                return cacheMaxAge;
+            }
+        };
+    }
 }
index 910d5fa..0aa14c7 100644 (file)
@@ -22,6 +22,7 @@ package org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.extclients.aai;
 
 import org.onap.aaiclient.client.aai.AAIProperties;
 import org.onap.aaiclient.client.aai.AAIVersion;
+import org.onap.so.client.CacheProperties;
 import org.onap.so.spring.SpringContextHelper;
 import org.springframework.context.ApplicationContext;
 import java.net.MalformedURLException;
@@ -34,6 +35,8 @@ public class AaiPropertiesImpl implements AAIProperties {
     private final String encryptionKey;
     private final String aaiVersion;
     private final Long readTimeout;
+    private final boolean enableCaching;
+    private final Long cacheMaxAge;
 
     public AaiPropertiesImpl() {
         final ApplicationContext context = SpringContextHelper.getAppContext();
@@ -42,6 +45,8 @@ public class AaiPropertiesImpl implements AAIProperties {
         this.encryptionKey = context.getEnvironment().getProperty("mso.key");
         this.aaiVersion = context.getEnvironment().getProperty("aai.version");
         this.readTimeout = context.getEnvironment().getProperty("aai.readTimeout", Long.class, new Long(60000));
+        this.enableCaching = context.getEnvironment().getProperty("aai.caching.enabled", Boolean.class, false);
+        this.cacheMaxAge = context.getEnvironment().getProperty("aai.caching.maxAge", Long.class, 60000L);
     }
 
     @Override
@@ -79,4 +84,19 @@ public class AaiPropertiesImpl implements AAIProperties {
     public Long getReadTimeout() {
         return this.readTimeout;
     }
+
+    @Override
+    public boolean isCachingEnabled() {
+        return this.enableCaching;
+    }
+
+    @Override
+    public CacheProperties getCacheProperties() {
+        return new AAICacheProperties() {
+            @Override
+            public Long getMaxAge() {
+                return cacheMaxAge;
+            }
+        };
+    }
 }