feat(platforms-3scale): Add config functionality and reorganise.
MODIFY
gateway/engine/core/src/main/java/io/apiman/gateway/engine/IApiRequestPathParser.java
gateway/engine/core/src/main/java/io/apiman/gateway/engine/IApiRequestPathParser.java
@@ -27,7 +27,7 @@ import io.apiman.gateway.engine.beans.util.HeaderMap;
*
* <p>
* In essence, this is the identification of information that the gateway needs so that it can
- * be separated.
+ * be distinguished.
* </p>
*
* @author Marc Savy {@literal <marc@rhymewithgravy.com>}

MODIFY
gateway/engine/vertx-polling/src/main/java/io/apiman/gateway/engine/vertx/polling/ThreeScaleRequestPathParser.java
gateway/engine/vertx-polling/src/main/java/io/apiman/gateway/engine/vertx/polling/ThreeScaleRequestPathParser.java
* limitations under the License.
*/
-package io.apiman.gateway.platforms.vertx3.common.config;
+package io.apiman.gateway.engine.vertx.polling;
import io.apiman.common.util.ApimanPathUtils.ApiRequestPathInfo;
import io.apiman.gateway.engine.IApiRequestPathParser;
@@ -30,9 +30,13 @@ import org.apache.commons.lang3.StringUtils;
* @author Marc Savy {@literal <marc@rhymewithgravy.com>}
*/
@SuppressWarnings("nls")
-public class ThreeScaleRequestPathParser implements IApiRequestPathParser { // TODO move to common area so servlet can use
+public class ThreeScaleRequestPathParser implements IApiRequestPathParser {
+ private final String defaultOrgName;
+ private final String defaultVersion;
public ThreeScaleRequestPathParser(Map<String, String> config) {
+ this.defaultOrgName = config.getOrDefault("defaultOrgName", ThreeScaleURILoadingRegistry.DEFAULT_ORGNAME);
+ this.defaultVersion = config.getOrDefault("defaultVersion", ThreeScaleURILoadingRegistry.DEFAULT_VERSION);
}
@Override
@@ -40,12 +44,12 @@ public class ThreeScaleRequestPathParser implements IApiRequestPathParser { // T
String[] split = StringUtils.split(path, "/", 3);
if (split == null || split.length < 2 || !"services".equalsIgnoreCase(split[0])) {
- throw new IllegalArgumentException("Invalid path format, expected /service/serviceName");
+ throw new IllegalArgumentException("Invalid path format, expected /services/serviceName");
}
ApiRequestPathInfo parsed = new ApiRequestPathInfo();
- parsed.orgId="DEFAULT";
- parsed.apiVersion="DEFAULT";
+ parsed.orgId = defaultOrgName;
+ parsed.apiVersion = defaultVersion;
parsed.apiId = split[1];
if (split.length > 2) {
parsed.resource = "/" + split[2];

MODIFY
gateway/engine/vertx-polling/src/main/java/io/apiman/gateway/engine/vertx/polling/ThreeScaleURILoadingRegistry.java
gateway/engine/vertx-polling/src/main/java/io/apiman/gateway/engine/vertx/polling/ThreeScaleURILoadingRegistry.java
@@ -29,6 +29,7 @@ import io.apiman.gateway.engine.impl.InMemoryRegistry;
import io.apiman.gateway.engine.vertx.polling.fetchers.AccessTokenResourceFetcher;
import io.apiman.gateway.engine.vertx.polling.fetchers.FileResourceFetcher;
import io.apiman.gateway.engine.vertx.polling.fetchers.HttpResourceFetcher;
+import io.apiman.gateway.engine.vertx.polling.fetchers.threescale.beans.Auth3ScaleBean;
import io.apiman.gateway.engine.vertx.polling.fetchers.threescale.beans.Content;
import io.apiman.gateway.engine.vertx.polling.fetchers.threescale.beans.ProxyConfigRoot;
import io.apiman.gateway.engine.vertx.polling.fetchers.threescale.beans.Service;
@@ -64,10 +65,20 @@ import java.util.stream.Collectors;
* <li>apiEndpoint: 3scale API endpoint</li>
* <li>environment: which environment (e.g. production, staging)</li>
* <li>policyConfigUri: apiman policy config to load as JSON from file
- * ({@link FileResourceFetcher}) or HTTP/S ({@link HttpResourceFetcher}).
- * See the corresponding fetcher for additional options.</li>
+ * ({@link FileResourceFetcher}) or HTTP/S ({@link HttpResourceFetcher}). See the corresponding
+ * fetcher for additional options.</li>
+ * <li>orgName: 3scale does not presently support multi-tenanted namespacing within a single
+ * gateway, so a default namespace is used internally (reflected in metrics, etc). <em>Does
+ * not</em> impact the path used to call the gateway <em>Default: apiman</em></li>
+ * <li>version: 3scale does not presently support versioning, so a default version is used
+ * internally (reflected in metrics, etc). <em>Does not</em> impact the path used to call the
+ * gateway <em>Default: 1.0</em></li>
* </ul>
*
+ * <p>
+ * From a metrics perspective, with defaults, you would see: Org: apiman; Api: [Your API Name]; Version: 1.0.
+ * </p>
+ *
* @author Marc Savy {@literal <marc@rhymewithgravy.com>}
* @see FileResourceFetcher
* @see HttpResourceFetcher
@@ -75,10 +86,12 @@ import java.util.stream.Collectors;
*/
@SuppressWarnings("nls")
public class ThreeScaleURILoadingRegistry extends InMemoryRegistry implements AsyncInitialize {
+ public static final String DEFAULT_ORGNAME = "apiman";
+ public static final String DEFAULT_VERSION = "1.0";
+
private static volatile OneShotURILoader instance;
private Vertx vertx;
private Map<String, String> options;
- public static final String DEFAULT_NS = "DEFAULT";
/**
* @param vertx the vertx instance
@@ -158,23 +171,27 @@ public class ThreeScaleURILoadingRegistry extends InMemoryRegistry implements As
private Deque<ThreeScaleURILoadingRegistry> awaiting = new ArrayDeque<>();
private List<ThreeScaleURILoadingRegistry> allRegistries = new ArrayList<>();
private boolean dataProcessed = false;
- private List<ProxyConfigRoot> configs = new ArrayList<>();
+ private List<Auth3ScaleBean> configs = new ArrayList<>();
private List<Client> clients = new ArrayList<>();
private List<Api> apis = new ArrayList<>();
private Logger log = LoggerFactory.getLogger(OneShotURILoader.class);
private IAsyncHandler<Void> reloadHandler;
private List<Api> policyConfigApis = Collections.emptyList();
private String environment;
+ private String defaultOrgName;
+ private String defaultVersion;
public OneShotURILoader(Vertx vertx, Map<String, String> config) {
this.config = config;
this.vertx = vertx;
+ this.defaultOrgName = config.getOrDefault("defaultOrgName", DEFAULT_ORGNAME);
+ this.defaultVersion = config.getOrDefault("defaultVersion", DEFAULT_VERSION);
+ this.apiUri = URI.create(requireOpt("apiEndpoint", "apiEndpoint is required in configuration"));
+ this.environment = config.getOrDefault("environment", "production");
-
- apiUri = URI.create(requireOpt("apiEndpoint", "apiEndpoint is required in configuration"));
- environment = config.getOrDefault("environment", "production");
- if (config.containsKey("policyConfigUri"))
- policyConfigUri = URI.create(config.get("policyConfigUri")); // Can be null.
+ if (config.containsKey("policyConfigUri")) {
+ this.policyConfigUri = URI.create(config.get("policyConfigUri")); // Can be null.
+ }
fetchResource();
}
@@ -215,7 +232,7 @@ public class ThreeScaleURILoadingRegistry extends InMemoryRegistry implements As
.collect(Collectors.toList());
// If policyConfigUri is provided, then load API policy config.
- // NB: THESE ARE API BEANSs WITH *ONLY* POLICY CONFIG!
+ // NB: THESE ARE API BEANS WITH *ONLY* POLICY CONFIG!
if (policyConfigUri != null) {
configFutures.add(fetchPolicyConfig());
}
@@ -254,7 +271,11 @@ public class ThreeScaleURILoadingRegistry extends InMemoryRegistry implements As
if (buffer.length() > 0) {
ProxyConfigRoot pc = Json.decodeValue(buffer.toString(), ProxyConfigRoot.class);
log.debug("Received Proxy Config: {0}", pc);
- configs.add(pc);
+ Auth3ScaleBean bean = new Auth3ScaleBean()
+ .setThreescaleConfig(pc)
+ .setDefaultOrg(defaultOrgName)
+ .setDefaultVersion(defaultVersion);
+ configs.add(bean);
}
future.complete();
});
@@ -288,19 +309,19 @@ public class ThreeScaleURILoadingRegistry extends InMemoryRegistry implements As
}
try {
// Naive version initially.
- for (ProxyConfigRoot root : configs) {
+ for (Auth3ScaleBean bean : configs) {
// Reflects the remote data structure.
- Content config = root.getProxyConfig().getContent();
+ Content config = bean.getThreescaleConfig().getProxyConfig().getContent();
Api api = new Api();
api.setApiId(config.getSystemName());
- api.setOrganizationId(DEFAULT_NS);
+ api.setOrganizationId(defaultOrgName);
api.setEndpoint(config.getProxy().getApiBackend());
api.setEndpointContentType("text/json"); // don't think there is an equivalent of this in 3scale
api.setEndpointType("rest"); //don't think there is an equivalent of this in 3scale
api.setParsePayload(false); // can let user override this?
api.setPublicAPI(true); // is there an equivalent of this?
- api.setVersion(DEFAULT_NS); // don't think this is relevant anymore
- setPolicies(api, root);
+ api.setVersion(defaultVersion); // don't think this is relevant anymore
+ setPolicies(api, bean);
log.info("Processing - {0}: ", config);
log.info("Creating API - {0}: ", api);
@@ -314,7 +335,7 @@ public class ThreeScaleURILoadingRegistry extends InMemoryRegistry implements As
}
}
- private void setPolicies(Api api, ProxyConfigRoot config) { // FIXME optimise
+ private void setPolicies(Api api, Auth3ScaleBean config) { // FIXME optimise
// Add 3scale policy
Policy pol = new Policy();
pol.setPolicyImpl(determinePolicyImpl()); // TODO get version? Hmm! Env?
@@ -381,7 +402,6 @@ public class ThreeScaleURILoadingRegistry extends InMemoryRegistry implements As
vertx.runOnContext(run -> failureHandler.handle(failure));
});
}
-
}
}

ADD
gateway/engine/vertx-polling/src/main/java/io/apiman/gateway/engine/vertx/polling/fetchers/threescale/beans/Auth3ScaleBean.java
+/*
+ * Copyright 2016 JBoss Inc
+ *
+ * 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.vertx.polling.fetchers.threescale.beans;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Does nothing (yet).
+ *
+ * @author Marc Savy {@literal <msavy@redhat.com>}
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Auth3ScaleBean {
+ @JsonProperty("3scaleConfig")
+ private ProxyConfigRoot threescaleConfig;
+ private String defaultOrg;
+ private String defaultVersion;
+
+ public ProxyConfigRoot getThreescaleConfig() {
+ return threescaleConfig;
+ }
+
+ public Auth3ScaleBean setThreescaleConfig(ProxyConfigRoot threescaleConfig) {
+ this.threescaleConfig = threescaleConfig;
+ return this;
+ }
+
+ public String getDefaultOrg() {
+ return defaultOrg;
+ }
+
+ public Auth3ScaleBean setDefaultOrg(String defaultOrgName) {
+ this.defaultOrg = defaultOrgName;
+ return this;
+ }
+
+ public String getDefaultVersion() {
+ return defaultVersion;
+ }
+
+ public Auth3ScaleBean setDefaultVersion(String defaultVersion) {
+ this.defaultVersion = defaultVersion;
+ return this;
+ }
+}

MODIFY
gateway/engine/vertx-polling/src/main/java/io/apiman/gateway/engine/vertx/polling/fetchers/threescale/beans/Content.java
gateway/engine/vertx-polling/src/main/java/io/apiman/gateway/engine/vertx/polling/fetchers/threescale/beans/Content.java
@@ -70,6 +70,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
public class Content implements Serializable
{
+ private RateLimitingStrategy rateLimitingStrategy = RateLimitingStrategy.STANDARD;
@JsonProperty("id")
private long id;
@JsonProperty("account_id")
@@ -918,4 +919,12 @@ public class Content implements Serializable
.append(additionalProperties, rhs.additionalProperties).isEquals();
}
+ public RateLimitingStrategy getRateLimitingStrategy() {
+ return rateLimitingStrategy;
+ }
+
+ public void setRateLimitingStrategy(RateLimitingStrategy rateLimitingStrategy) {
+ this.rateLimitingStrategy = rateLimitingStrategy;
+ }
+
}

MODIFY
gateway/engine/vertx-polling/src/main/java/io/apiman/gateway/engine/vertx/polling/fetchers/threescale/beans/Proxy.java
gateway/engine/vertx-polling/src/main/java/io/apiman/gateway/engine/vertx/polling/fetchers/threescale/beans/Proxy.java
@@ -139,6 +139,8 @@ public class Proxy implements Serializable
@JsonIgnore
private transient MultiPatternMatcher routeMatcher;
private final static long serialVersionUID = 7319432853004376356L;
+ // Avoid repeated reevaluation of Regex
+ private Map<String, int[]> REGEX_MATCH_CACHE = new HashMap<>();
@JsonProperty("id")
public long getId() {
@@ -645,12 +647,31 @@ public class Proxy implements Serializable
.map(ProxyRule::getRegex)
.map(Pattern::toString)
.collect(Collectors.toList());
- System.out.println("Built some patterns? " + patterns); //$NON-NLS-1$
this.routeMatcher = MultiPattern.of(patterns).matcher();
}
- public MultiPatternMatcher getRouteMatcher() {
- return routeMatcher;
+ public int[] match(String path) {
+ if (REGEX_MATCH_CACHE.containsKey(path)) {
+ return REGEX_MATCH_CACHE.get(path);
+ }
+ int[] match = routeMatcher.match(path);
+ REGEX_MATCH_CACHE.put(path, match);
+ return match;
+ }
+
+ // TODO consider storing cache of metricName -> index mappings for O(1) performance.
+ public boolean match(String destination, String metricName) {
+ int[] matches = match(destination);
+ if (matches == null || matches.length == 0) {
+ return false;
+ }
+ for (int match : matches) {
+ String candidateMetricName = proxyRules.get(match).getMetricSystemName();
+ if (metricName.equals(candidateMetricName)) {
+ return true;
+ }
+ }
+ return false;
}
@JsonProperty("proxy_rules")
@@ -687,7 +708,13 @@ public class Proxy implements Serializable
@Override
public int hashCode() {
- return new HashCodeBuilder().append(id).append(tenantId).append(serviceId).append(endpoint).append(deployedAt).append(apiBackend).append(authAppKey).append(authAppId).append(authUserKey).append(credentialsLocation).append(errorAuthFailed).append(errorAuthMissing).append(createdAt).append(updatedAt).append(errorStatusAuthFailed).append(errorHeadersAuthFailed).append(errorStatusAuthMissing).append(errorHeadersAuthMissing).append(errorNoMatch).append(errorStatusNoMatch).append(errorHeadersNoMatch).append(secretToken).append(hostnameRewrite).append(oauthLoginUrl).append(sandboxEndpoint).append(apiTestPath).append(apiTestSuccess).append(hostnameRewriteForSandbox).append(endpointPort).append(valid).append(serviceBackendVersion).append(hosts).append(backend).append(proxyRules).append(additionalProperties).toHashCode();
+ return new HashCodeBuilder().append(id).append(tenantId).append(serviceId).append(endpoint).append(deployedAt).append(apiBackend)
+ .append(authAppKey).append(authAppId).append(authUserKey).append(credentialsLocation).append(errorAuthFailed).append(errorAuthMissing)
+ .append(createdAt).append(updatedAt).append(errorStatusAuthFailed).append(errorHeadersAuthFailed).append(errorStatusAuthMissing)
+ .append(errorHeadersAuthMissing).append(errorNoMatch).append(errorStatusNoMatch).append(errorHeadersNoMatch).append(secretToken)
+ .append(hostnameRewrite).append(oauthLoginUrl).append(sandboxEndpoint).append(apiTestPath).append(apiTestSuccess)
+ .append(hostnameRewriteForSandbox).append(endpointPort).append(valid).append(serviceBackendVersion).append(hosts).append(backend)
+ .append(proxyRules).append(additionalProperties).toHashCode();
}
@Override
@@ -699,7 +726,19 @@ public class Proxy implements Serializable
return false;
}
Proxy rhs = ((Proxy) other);
- return new EqualsBuilder().append(id, rhs.id).append(tenantId, rhs.tenantId).append(serviceId, rhs.serviceId).append(endpoint, rhs.endpoint).append(deployedAt, rhs.deployedAt).append(apiBackend, rhs.apiBackend).append(authAppKey, rhs.authAppKey).append(authAppId, rhs.authAppId).append(authUserKey, rhs.authUserKey).append(credentialsLocation, rhs.credentialsLocation).append(errorAuthFailed, rhs.errorAuthFailed).append(errorAuthMissing, rhs.errorAuthMissing).append(createdAt, rhs.createdAt).append(updatedAt, rhs.updatedAt).append(errorStatusAuthFailed, rhs.errorStatusAuthFailed).append(errorHeadersAuthFailed, rhs.errorHeadersAuthFailed).append(errorStatusAuthMissing, rhs.errorStatusAuthMissing).append(errorHeadersAuthMissing, rhs.errorHeadersAuthMissing).append(errorNoMatch, rhs.errorNoMatch).append(errorStatusNoMatch, rhs.errorStatusNoMatch).append(errorHeadersNoMatch, rhs.errorHeadersNoMatch).append(secretToken, rhs.secretToken).append(hostnameRewrite, rhs.hostnameRewrite).append(oauthLoginUrl, rhs.oauthLoginUrl).append(sandboxEndpoint, rhs.sandboxEndpoint).append(apiTestPath, rhs.apiTestPath).append(apiTestSuccess, rhs.apiTestSuccess).append(hostnameRewriteForSandbox, rhs.hostnameRewriteForSandbox).append(endpointPort, rhs.endpointPort).append(valid, rhs.valid).append(serviceBackendVersion, rhs.serviceBackendVersion).append(hosts, rhs.hosts).append(backend, rhs.backend).append(proxyRules, rhs.proxyRules).append(additionalProperties, rhs.additionalProperties).isEquals();
+ return new EqualsBuilder().append(id, rhs.id).append(tenantId, rhs.tenantId).append(serviceId, rhs.serviceId).append(endpoint, rhs.endpoint)
+ .append(deployedAt, rhs.deployedAt).append(apiBackend, rhs.apiBackend).append(authAppKey, rhs.authAppKey)
+ .append(authAppId, rhs.authAppId).append(authUserKey, rhs.authUserKey).append(credentialsLocation, rhs.credentialsLocation)
+ .append(errorAuthFailed, rhs.errorAuthFailed).append(errorAuthMissing, rhs.errorAuthMissing).append(createdAt, rhs.createdAt)
+ .append(updatedAt, rhs.updatedAt).append(errorStatusAuthFailed, rhs.errorStatusAuthFailed)
+ .append(errorHeadersAuthFailed, rhs.errorHeadersAuthFailed).append(errorStatusAuthMissing, rhs.errorStatusAuthMissing)
+ .append(errorHeadersAuthMissing, rhs.errorHeadersAuthMissing).append(errorNoMatch, rhs.errorNoMatch)
+ .append(errorStatusNoMatch, rhs.errorStatusNoMatch).append(errorHeadersNoMatch, rhs.errorHeadersNoMatch)
+ .append(secretToken, rhs.secretToken).append(hostnameRewrite, rhs.hostnameRewrite).append(oauthLoginUrl, rhs.oauthLoginUrl)
+ .append(sandboxEndpoint, rhs.sandboxEndpoint).append(apiTestPath, rhs.apiTestPath).append(apiTestSuccess, rhs.apiTestSuccess)
+ .append(hostnameRewriteForSandbox, rhs.hostnameRewriteForSandbox).append(endpointPort, rhs.endpointPort).append(valid, rhs.valid)
+ .append(serviceBackendVersion, rhs.serviceBackendVersion).append(hosts, rhs.hosts).append(backend, rhs.backend)
+ .append(proxyRules, rhs.proxyRules).append(additionalProperties, rhs.additionalProperties).isEquals();
}

ADD
gateway/engine/vertx-polling/src/main/java/io/apiman/gateway/engine/vertx/polling/fetchers/threescale/beans/RateLimitingStrategy.java
+/*
+ * Copyright 2017 JBoss Inc
+ *
+ * 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.vertx.polling.fetchers.threescale.beans;
+
+public enum RateLimitingStrategy {
+ STANDARD(false), BATCHED(true), XC(true);
+
+ private boolean batched;
+
+ RateLimitingStrategy(boolean batched) {
+ this.batched = batched;
+ }
+
+ public boolean isBatched() {
+ return batched;
+ }
+}

feat(platforms-3scale): Add config functionality and reorganise.