Adds a Hazelcast implementation for rate limiting and shared state.
ADD
gateway/engine/hazelcast/pom.xml
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>io.apiman</groupId>
+ <artifactId>apiman-gateway-engine</artifactId>
+ <version>1.2.10-SNAPSHOT</version>
+ </parent>
+ <artifactId>apiman-gateway-engine-hazelcast</artifactId>
+ <packaging>bundle</packaging>
+ <name>apiman-gateway-engine-hazelcast</name>
+
+ <dependencies>
+ <!-- Project Dependencies -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apiman-common-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apiman-gateway-engine-beans</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apiman-gateway-engine-core</artifactId>
+ </dependency>
+
+ <!-- Third Party Dependencies -->
+ <dependency>
+ <groupId>com.hazelcast</groupId>
+ <artifactId>hazelcast</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ </plugins>
+ </build>
+</project>

ADD
gateway/engine/hazelcast/src/main/java/io/apiman/gateway/engine/hazelcast/AbstractHazelcastComponent.java
+/*
+ * Copyright 2017 Pete Cornish
+ *
+ * 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.apiman.gateway.engine.hazelcast;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+
+import java.util.Map;
+
+/**
+ * Common base class for components backed by a Hazelcast Map.
+ *
+ * @author Pete Cornish
+ */
+abstract class AbstractHazelcastComponent {
+ private final String storeName;
+ private final HazelcastInstance hazelcastInstance;
+
+ /**
+ * Constructor.
+ */
+ public AbstractHazelcastComponent(String storeName) {
+ this(storeName, new Config());
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param config the config
+ */
+ public AbstractHazelcastComponent(String storeName, Config config) {
+ this.storeName = storeName;
+ hazelcastInstance = Hazelcast.newHazelcastInstance(config);
+ }
+
+ /**
+ * Returns an instance of the shared state.
+ *
+ * @param <T> the value type
+ * @return the shared state
+ */
+ protected <T> Map<String, T> getSharedState() {
+ return hazelcastInstance.getMap(storeName);
+ }
+
+ /**
+ * Builds a key derived from the namespace.
+ *
+ * @param namespace the namespace
+ * @param propertyName the property name
+ * @return the namespaced key
+ */
+ protected String buildNamespacedKey(String namespace, String propertyName) {
+ return namespace + "." + propertyName;
+ }
+}

ADD
gateway/engine/hazelcast/src/main/java/io/apiman/gateway/engine/hazelcast/HazelcastRateLimiterComponent.java
+/*
+ * Copyright 2017 Pete Cornish
+ *
+ * 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.apiman.gateway.engine.hazelcast;
+
+import com.hazelcast.config.Config;
+import io.apiman.gateway.engine.async.AsyncResultImpl;
+import io.apiman.gateway.engine.async.IAsyncResultHandler;
+import io.apiman.gateway.engine.components.IRateLimiterComponent;
+import io.apiman.gateway.engine.components.rate.RateLimitResponse;
+import io.apiman.gateway.engine.rates.RateBucketPeriod;
+import io.apiman.gateway.engine.rates.RateLimiterBucket;
+
+/**
+ * Rate limiter component backed by a Hazelcast Map. This allows rate limiting
+ * to be done across nodes in a cluster of gateways.
+ *
+ * @author Pete Cornish
+ */
+public class HazelcastRateLimiterComponent extends AbstractHazelcastComponent implements IRateLimiterComponent {
+ private static final String STORE_NAME = "rate-limiter"; //$NON-NLS-1$
+
+ private final Object mutex = new Object();
+
+ /**
+ * Constructor.
+ */
+ public HazelcastRateLimiterComponent() {
+ super(STORE_NAME);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param config the config
+ */
+ public HazelcastRateLimiterComponent(Config config) {
+ super(STORE_NAME, config);
+ }
+
+ /**
+ * @see io.apiman.gateway.engine.components.IRateLimiterComponent#accept(java.lang.String, io.apiman.gateway.engine.rates.RateBucketPeriod, long, long, io.apiman.gateway.engine.async.IAsyncResultHandler)
+ */
+ @Override
+ public void accept(final String bucketId, final RateBucketPeriod period, final long limit,
+ final long increment, final IAsyncResultHandler<RateLimitResponse> handler) {
+ RateLimiterBucket bucket;
+ synchronized (mutex) {
+ bucket = (RateLimiterBucket) getSharedState().get(bucketId);
+ if (bucket == null) {
+ bucket = new RateLimiterBucket();
+ getSharedState().put(bucketId, bucket);
+ }
+ bucket.resetIfNecessary(period);
+
+ RateLimitResponse response = new RateLimitResponse();
+ if (bucket.getCount() > limit) {
+ response.setAccepted(false);
+ } else {
+ response.setAccepted(bucket.getCount() < limit);
+ bucket.setCount(bucket.getCount() + increment);
+ bucket.setLast(System.currentTimeMillis());
+ }
+ int reset = (int) (bucket.getResetMillis(period) / 1000L);
+ response.setReset(reset);
+ response.setRemaining(limit - bucket.getCount());
+ handler.handle(AsyncResultImpl.create(response));
+ getSharedState().put(bucketId, bucket);
+ }
+ }
+}

ADD
gateway/engine/hazelcast/src/main/java/io/apiman/gateway/engine/hazelcast/HazelcastSharedStateComponent.java
+/*
+ * Copyright 2017 Pete Cornish
+ *
+ * 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.apiman.gateway.engine.hazelcast;
+
+import com.hazelcast.config.Config;
+import io.apiman.gateway.engine.async.AsyncResultImpl;
+import io.apiman.gateway.engine.async.IAsyncResultHandler;
+import io.apiman.gateway.engine.components.ISharedStateComponent;
+
+/**
+ * Shared state component backed by a Hazelcast Map. This allows the shared state
+ * to be easily clusterable.
+ *
+ * @author Pete Cornish
+ */
+public class HazelcastSharedStateComponent extends AbstractHazelcastComponent implements ISharedStateComponent {
+ private static final String STORE_NAME = "shared-state"; //$NON-NLS-1$
+
+ /**
+ * Constructor.
+ */
+ public HazelcastSharedStateComponent() {
+ super(STORE_NAME);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param config the config
+ */
+ public HazelcastSharedStateComponent(Config config) {
+ super(STORE_NAME, config);
+ }
+
+ /**
+ * @see io.apiman.gateway.engine.components.ISharedStateComponent#getProperty(java.lang.String, java.lang.String, java.lang.Object, io.apiman.gateway.engine.async.IAsyncResultHandler)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> void getProperty(String namespace, String propertyName, T defaultValue, IAsyncResultHandler<T> handler) {
+ final String namespacedKey = buildNamespacedKey(namespace, propertyName);
+ if (getSharedState().containsKey(namespacedKey)) {
+ try {
+ T rval = (T) getSharedState().get(namespacedKey);
+ handler.handle(AsyncResultImpl.create(rval));
+ } catch (Exception e) {
+ handler.handle(AsyncResultImpl.create(e));
+ }
+
+ } else {
+ handler.handle(AsyncResultImpl.create(defaultValue));
+ }
+ }
+
+ /**
+ * @see io.apiman.gateway.engine.components.ISharedStateComponent#setProperty(java.lang.String, java.lang.String, java.lang.Object, io.apiman.gateway.engine.async.IAsyncResultHandler)
+ */
+ @Override
+ public <T> void setProperty(String namespace, String propertyName, T value, IAsyncResultHandler<Void> handler) {
+ final String namespacedKey = buildNamespacedKey(namespace, propertyName);
+ try {
+ getSharedState().put(namespacedKey, value);
+ handler.handle(AsyncResultImpl.create((Void) null));
+ } catch (Exception e) {
+ handler.handle(AsyncResultImpl.create(e));
+ }
+ }
+
+ /**
+ * @see io.apiman.gateway.engine.components.ISharedStateComponent#clearProperty(java.lang.String, java.lang.String, io.apiman.gateway.engine.async.IAsyncResultHandler)
+ */
+ @Override
+ public <T> void clearProperty(String namespace, String propertyName, IAsyncResultHandler<Void> handler) {
+ final String namespacedKey = buildNamespacedKey(namespace, propertyName);
+ try {
+ getSharedState().remove(namespacedKey);
+ handler.handle(AsyncResultImpl.create((Void) null));
+ } catch (Exception e) {
+ handler.handle(AsyncResultImpl.create(e));
+ }
+ }
+}

MODIFY
gateway/engine/pom.xml
gateway/engine/pom.xml
<module>core</module>
<module>es</module>
<module>hawkular</module>
+ <module>hazelcast</module>
<module>influxdb</module>
<module>ispn</module>
<module>jdbc</module>

MODIFY
gateway/platforms/war/micro/pom.xml
gateway/platforms/war/micro/pom.xml
<groupId>${project.groupId}</groupId>
<artifactId>apiman-gateway-engine-hawkular</artifactId>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apiman-gateway-engine-hazelcast</artifactId>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>apiman-gateway-engine-jdbc</artifactId>

MODIFY
gateway/platforms/war/pom.xml
gateway/platforms/war/pom.xml
<groupId>${project.groupId}</groupId>
<artifactId>apiman-gateway-engine-core</artifactId>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apiman-gateway-engine-hazelcast</artifactId>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>apiman-gateway-engine-ispn</artifactId>

MODIFY
gateway/test/pom.xml
gateway/test/pom.xml
<groupId>${project.groupId}</groupId>
<artifactId>apiman-gateway-engine-core</artifactId>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apiman-gateway-engine-hazelcast</artifactId>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>apiman-gateway-engine-ispn</artifactId>

MODIFY
pom.xml
pom.xml
<version.io.vertx>3.3.2</version.io.vertx>
<version.net.openhft>0.6</version.net.openhft>
<version.com.ibm.icu4j>57.1</version.com.ibm.icu4j>
+ <version.com.hazelcast>3.6.4</version.com.hazelcast>
</properties>
<dependencyManagement>
<artifactId>apiman-gateway-engine-core</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apiman-gateway-engine-hazelcast</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>apiman-gateway-engine-influxdb</artifactId>
<artifactId>netty-codec-http</artifactId>
<version>${version.io.netty}</version>
</dependency>
+ <dependency>
+ <groupId>com.hazelcast</groupId>
+ <artifactId>hazelcast</artifactId>
+ <version>${version.com.hazelcast}</version>
+ </dependency>
<!-- Spec Libraries -->
<dependency>

Adds a Hazelcast implementation for rate limiting and shared state. (#517)