Skip to content

Commit

Permalink
Fix hazelcast configuration and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
jameskleeh committed Oct 29, 2019
1 parent 31c8236 commit 5cd554b
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 61 deletions.
24 changes: 24 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Contributing a new cache implementation

How a cache implementation should be created depends on whether the caches can/should be declared ahead of time. For caches with predefined cache names, `SyncCache` beans should be created which will be ingested by the default cache manager. For caches that cannot or should not be defined ahead of time, a `DynamicCacheManager` bean should be registered to retrieve the cache.

Any other beans created are not used by Micronaut. The only beans referenced are `SyncCache` and `DynamicCacheManager`.

## Synchronous cache implementations

For caches that using blocking IO, the `getExecutorService()` method should be overridden for each `SyncCache` to allow the offloading of the operations to a thread pool. Unless there is a reason to do otherwise, the `TaskManagers.IO` named thread pool should be used.

## Asynchronous cache implementations

If the cache provider has an asynchronous API, the `async()` method of `SyncCache` should be overridden to supply an `AsyncCache` that uses the native asynchronous API. See the Hazelcast implementation for an example.

## Gradle build and setup

To ensure consistency across implementations, copy the build of an existing implementation and alter the dependencies as needed.

## Documentation

All cache implementations should be documented by modifying the `src/main/docs/guide/toc.yml` to include your documentation. Documentation should describe how to install and configure the implementation.



2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ subprojects { Project subproject ->
repositories {
//mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://oss.jfrog.org/oss-snapshot-local/" }
}

apply plugin:"groovy"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,14 @@

import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import io.micronaut.cache.DefaultCacheManager;
import io.micronaut.cache.DynamicCacheManager;
import io.micronaut.cache.SyncCache;
import io.micronaut.context.annotation.Primary;
import io.micronaut.context.annotation.Replaces;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.scheduling.TaskExecutors;

import javax.annotation.Nonnull;
import javax.inject.Named;
import java.io.Closeable;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.inject.Singleton;
import java.util.concurrent.ExecutorService;

/**
Expand All @@ -39,11 +33,9 @@
* @author Nirav Assar
* @since 1.0.0
*/
@Replaces(DefaultCacheManager.class)
@Primary
public class HazelcastCacheManager implements io.micronaut.cache.CacheManager<IMap<Object, Object>>, Closeable {
@Singleton
public class HazelcastCacheManager implements DynamicCacheManager<IMap<Object, Object>> {

private Map<String, HazelcastSyncCache> cacheMap;
private final ConversionService<?> conversionService;
private final ExecutorService executorService;
private final HazelcastInstance hazelcastClientInstance;
Expand All @@ -57,36 +49,17 @@ public class HazelcastCacheManager implements io.micronaut.cache.CacheManager<IM
*/
public HazelcastCacheManager(ConversionService<?> conversionService,
HazelcastInstance hazelcastClientInstance,
@Named("io") ExecutorService executorService) {
@Named(TaskExecutors.IO) ExecutorService executorService) {
this.conversionService = conversionService;
this.executorService = executorService;
this.cacheMap = new HashMap<>();
this.hazelcastClientInstance = hazelcastClientInstance;
}

@Nonnull
@Override
public Set<String> getCacheNames() {
Set<String> names = new HashSet<String>();
names.add(this.hazelcastClientInstance.getName());
return names;
}

@SuppressWarnings("unchecked")
@Nonnull
@Override
public SyncCache<IMap<Object, Object>> getCache(String name) {
HazelcastSyncCache syncCache = this.cacheMap.get(name);
if (syncCache == null) {
IMap<Object, Object> nativeCache = hazelcastClientInstance.getMap(name);
syncCache = new HazelcastSyncCache(conversionService, nativeCache, executorService);
this.cacheMap.put(name, syncCache);
}
return syncCache;
}

@Override
public void close() throws IOException {
hazelcastClientInstance.shutdown();
IMap<Object, Object> nativeCache = hazelcastClientInstance.getMap(name);
return new HazelcastSyncCache(conversionService, nativeCache, executorService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,41 @@

import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.client.config.ClientNetworkConfig;
import com.hazelcast.client.config.SocketOptions;
import com.hazelcast.config.ConfigPatternMatcher;
import com.hazelcast.config.GroupConfig;
import com.hazelcast.config.SocketInterceptorConfig;
import io.micronaut.context.annotation.ConfigurationBuilder;
import io.micronaut.context.annotation.ConfigurationProperties;

import javax.annotation.Nullable;

/**
* Configuration class for an Hazelcast as a client.
*
* @author Nirav Assar
* @since 1.0.0
*/
@ConfigurationProperties("hazelcast")
@ConfigurationProperties(value = "hazelcast.client", includes = {"properties", "executorPoolSize", "licenseKey", "instanceName",
"labels", "userContext"})
public class HazelcastClientConfiguration extends ClientConfig {

@ConfigurationBuilder("network")
@ConfigurationBuilder(value = "network", includes = {"smartRouting", "connectionAttemptPeriod", "connectionAttemptLimit",
"connectionTimeout", "addresses", "redoOperation", "outboundPortDefinitions", "outboundPorts"})
ClientNetworkConfig networkConfig = new ClientNetworkConfig();

@ConfigurationBuilder("network.socket")
SocketOptions socketOptions = new SocketOptions();

@ConfigurationBuilder("group")
GroupConfig groupConfig = new GroupConfig();

/**
* Default constructor.
*/
HazelcastClientConfiguration() {
networkConfig.setSocketOptions(socketOptions);
super.setNetworkConfig(networkConfig);
super.setGroupConfig(groupConfig);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
package io.micronaut.cache.hazelcast;

import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.core.HazelcastInstance;
import io.micronaut.context.annotation.EachBean;
import io.micronaut.context.annotation.Factory;

import javax.inject.Singleton;
Expand All @@ -28,16 +30,16 @@
* @since 1.0.0
*/
@Factory
public class HazelcastClientFactory {
public class HazelcastFactory {

/**
* Create a singleton {@link HazelcastInstance} client, based on configuration.
*
* @param hazelcastClientConfiguration the configuration read it as a bean
* @param clientConfig the configuration read it as a bean
* @return {@link HazelcastInstance}
*/
@Singleton
public HazelcastInstance hazelcastClientInstance(HazelcastClientConfiguration hazelcastClientConfiguration) {
return HazelcastClient.newHazelcastClient(hazelcastClientConfiguration);
public HazelcastInstance hazelcastClientInstance(ClientConfig clientConfig) {
return HazelcastClient.newHazelcastClient(clientConfig);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.micronaut.cache.hazelcast.converters;


import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.convert.TypeConverter;

import javax.inject.Singleton;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@Singleton
public class MapToConcurrentMapConverter implements TypeConverter<Map, ConcurrentMap> {

@Override
public Optional<ConcurrentMap> convert(Map object, Class<ConcurrentMap> targetType, ConversionContext context) {
return Optional.of(new ConcurrentHashMap(object));
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
/*
* Copyright 2017-2019 original authors
*
* 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.
*/

/**
* Classes related to the integration of Hazelcast caches into Micronaut's
* cache manager.
*/
@Configuration
@Requires(property = "hazelcast.enabled", notEquals = StringUtils.FALSE)
package io.micronaut.cache.hazelcast;

import io.micronaut.context.annotation.Configuration;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.util.StringUtils;
Original file line number Diff line number Diff line change
@@ -1,27 +1,78 @@
package io.micronaut.cache.hazelcast

import com.hazelcast.client.config.ClientConfig
import com.hazelcast.config.ListenerConfig
import io.micronaut.context.ApplicationContext
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import spock.lang.Specification

import javax.inject.Singleton

class HazelcastClientConfigurationSpec extends Specification {

void "test nested network configuration"() {
given:
ApplicationContext ctx = ApplicationContext.run(ApplicationContext, [
"hazelcast.instanceName": 'myInstance',
"hazelcast.network.connectionTimeout": 99,
"hazelcast.network.addresses": ['127.0.0.1:5701', 'http://hazelcast:5702'],
"hazelcast.network.redoOperation": true
"hazelcast.client.network.smartRouting": false,
"hazelcast.client.network.connectionAttemptPeriod": 1000,
"hazelcast.client.network.connectionAttemptLimit": 5,
"hazelcast.client.network.connectionTimeout": 1000,
"hazelcast.client.network.addresses": ['127.0.0.1:5701', 'http://hazelcast:5702'],
"hazelcast.client.network.redoOperation": true,
"hazelcast.client.network.outboundPortDefinitions": ["a", "b"],
"hazelcast.client.network.outboundPorts": [1, 2],
"hazelcast.client.network.socket.tcpNoDelay": false,
"hazelcast.client.network.socket.keepAlive": false,
"hazelcast.client.network.socket.reuseAddress": false,
"hazelcast.client.network.socket.lingerSeconds": 5,
"hazelcast.client.network.socket.bufferSize": 64,
"hazelcast.client.group.name": "Group",
"hazelcast.client.properties": [x: "x", y: "y"],
"hazelcast.client.executorPoolSize": 3,
"hazelcast.client.licenseKey": "license key",
"hazelcast.client.instanceName": "instance name",
"hazelcast.client.labels": ["a", "b"],
"hazelcast.client.userContext": [a: "a", b: "b"]
])

when:
HazelcastClientConfiguration hazelcastClientConfiguration = ctx.getBean(HazelcastClientConfiguration)

then:
hazelcastClientConfiguration.instanceName == "myInstance"
hazelcastClientConfiguration.networkConfig.connectionTimeout == 99
!hazelcastClientConfiguration.networkConfig.smartRouting
hazelcastClientConfiguration.networkConfig.connectionAttemptPeriod == 1000
hazelcastClientConfiguration.networkConfig.addresses[0] == "127.0.0.1:5701"
hazelcastClientConfiguration.networkConfig.addresses[1] == "http://hazelcast:5702"
hazelcastClientConfiguration.networkConfig.redoOperation
hazelcastClientConfiguration.networkConfig.outboundPortDefinitions == ["a", "b"]
hazelcastClientConfiguration.networkConfig.outboundPorts == [1, 2]
!hazelcastClientConfiguration.networkConfig.socketOptions.tcpNoDelay
!hazelcastClientConfiguration.networkConfig.socketOptions.keepAlive
!hazelcastClientConfiguration.networkConfig.socketOptions.reuseAddress
hazelcastClientConfiguration.networkConfig.socketOptions.lingerSeconds == 5
hazelcastClientConfiguration.networkConfig.socketOptions.bufferSize == 64
hazelcastClientConfiguration.groupConfig.name == "Group"
hazelcastClientConfiguration.properties.get("x") == "x"
hazelcastClientConfiguration.properties.get("y") == "y"
hazelcastClientConfiguration.executorPoolSize == 3
hazelcastClientConfiguration.licenseKey == "license key"
hazelcastClientConfiguration.instanceName == "instance name"
hazelcastClientConfiguration.labels == ["a", "b"] as Set
hazelcastClientConfiguration.userContext.get("a") == "a"
hazelcastClientConfiguration.userContext.get("b") == "b"
hazelcastClientConfiguration.listenerConfigs.size() == 1
}

@Singleton
static class CustomConfig implements BeanCreatedEventListener<ClientConfig> {

@Override
ClientConfig onCreated(BeanCreatedEvent<ClientConfig> event) {
event.getBean().addListenerConfig(new ListenerConfig(new EventListener() {

}))
event.getBean()
}
}
}
17 changes: 8 additions & 9 deletions src/main/docs/guide/hazelcast.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,25 @@ When using the link:{api}/io/micronaut/cache/annotation/Cacheable.html[@Cacheabl
the Hazelcast client and use the underlying https://docs.hazelcast.org/docs/latest/javadoc/com/hazelcast/core/IMap.html[IMap] Cache Datastore
on the server.

The full list of configuration options is below. Amongst the list, properties that have default built-in https://docs.micronaut.io/latest/api/io/micronaut/core/convert/TypeConverter.html[TypeConverter] can be set
in YAML. For example, `hazelcast.network.connectionTimeout` and `hazelcast.network.smart-routing` are primitive types and can be set in YAML files.

For other types, Micronaut users must create a https://docs.micronaut.io/latest/api/io/micronaut/core/convert/TypeConverter.html[TypeConverter] to set the property
in YAML.
The full list of configurable options is below.

include::{includedir}configurationProperties/io.micronaut.cache.hazelcast.HazelcastClientConfiguration.adoc[]

An alternative but more direct option is to create a https://docs.micronaut.io/latest/api/io/micronaut/context/event/BeanCreatedEventListener.html[BeanCreatedEventListener] for
link:{api}/io/micronaut/cache/hazelcast/HazelcastClientConfiguration.html[HazelcastClientConfiguration]. This will be invoked by the
Micronaut Framework on bean creation of `HazelcastClientConfiguration`, and from the event you can programmatically add to the instance. For example:
For settings not in the above list, a https://docs.micronaut.io/latest/api/io/micronaut/context/event/BeanCreatedEventListener.html[BeanCreatedEventListener] can be registered for
link:{api}/io/micronaut/cache/hazelcast/HazelcastClientConfiguration.html[HazelcastClientConfiguration]. The listener will allow all properties to be set directly on the configuration instance.

[source,java]
----
@Singleton
public class HazelcastAdditionalSettings implements BeanCreatedEventListener<HazelcastClientConfiguration> {
@Override
public HazelcastClientConfiguration onCreated(BeanCreatedEvent<HazelcastClientConfiguration> event) {
HazelcastClientConfiguration configuration = event.getBean();
configuration.getGroupConfig().setName("myName");
// Set anything on the configuration
return configuration;
}
}
----

Alternatively, the `HazelcastClientConfiguration` bean may be replaced with your own implementation.
7 changes: 3 additions & 4 deletions src/main/docs/guide/introduction.adoc
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
This project brings cache support to Micronaut.
This project brings additional cache implementations to Micronaut.

To get started, you need to declare the following dependency:

dependency:io.micronaut.cache:micronaut-cache-core:{version}[]
dependency:io.micronaut:micronaut-runtime[]

Note that this configuration module requires at least Micronaut 1.3. Since this version, caching support has been
extracted out from core into this configuration library.
NOTE: The configuration implementations in this module require at least Micronaut version 1.3.0. Each implementation is a separate dependency.

To use the `BUILD-SNAPSHOT` version of this library, check the
https://docs.micronaut.io/latest/guide/index.html#usingsnapshots[documentation to use snapshots].

0 comments on commit 5cd554b

Please sign in to comment.