/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.ext.apispark;
import org.restlet.Context;
import org.restlet.engine.application.Encoder;
import org.restlet.engine.util.StringUtils;
import org.restlet.ext.apispark.internal.ApiSparkConfig;
import org.restlet.ext.apispark.internal.ApiSparkFilter;
import org.restlet.ext.apispark.internal.agent.AgentConfigurationTimerTask;
import org.restlet.ext.apispark.internal.firewall.rule.FirewallRule;
import org.restlet.routing.Filter;
import org.restlet.service.EncoderService;
import org.restlet.service.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* Configures a proxy for your own application and provides some services hosted
* by the APISpark platform such as analytics, security.
*
* The service could be configured by a property file with the
* {@link #loadConfiguration()} method.
*
* @author Cyprien Quilici
* @author Manuel Boillod
*/
public class ApiSparkService extends Service {
/** Internal logger. */
protected static Logger LOGGER = Logger.getLogger(ApiSparkService.class
.getName());
/** The URL of the remote service used by default. */
public static final String DEFAULT_AGENT_SERVICE_URL = "https://apispark.restlet.com";
/** The system property key for agent configuration file. */
public static final String CONFIGURATION_FILE_SYSTEM_PROPERTY_KEY = "apiSparkServiceConfig";
/** The filter performing the services */
private ApiSparkFilter apiSparkFilter;
/** Indicates if the APISpark agent is enabled.*/
private boolean agentEnabled;
/** The password used to connect to the APISpark platform. */
private char[] agentPassword;
/** The url of the APISpark service. */
private String agentServiceUrl = DEFAULT_AGENT_SERVICE_URL;
/**
* The maximum size of the stored analytics. When this threshold is
* exceeded, an asynchronous task is triggered to send them to the APISpark
* service.
*/
private int agentAnalyticsBufferSize = 100;
/**
* The period in seconds of the timer triggering the asynchronous post
* of analytics to the APISpark service.
*/
private long agentAnalyticsPostPeriodInSecond = 60;
/** The timer that triggers agent re-configuration */
private Timer agentRefreshTimer;
/** Agent refresh period in seconds */
private long agentRefreshPeriodInSecond = TimeUnit.MINUTES.toSeconds(15);
/** The login used to connect to the APISpark platform. */
private String agentLogin;
/**
* The identifier of the cell configured on the APISpark platform for your
* application.
*/
private Integer agentCellId;
/**
* The identifier of the cell version configured on the APISpark platform
* for your application.
*/
private Integer agentCellVersion;
/**
* The list of associated
* {@link org.restlet.ext.apispark.internal.firewall.rule.FirewallRule}.
*/
private List<FirewallRule> firewallRules = new ArrayList<>();
/**
* Indicates if the firewall is enabled. Add firewall rules with
* {@link #firewallConfig}.
*/
private boolean firewallEnabled;
/**
* Firewall configuration
*/
private FirewallConfig firewallConfig = new FirewallConfig(firewallRules);
/**
* Indicates if the request redirection is enabled. If true, the
* {@link #reverseProxyTargetUrl} should be set.
*/
private boolean reverseProxyEnabled;
/**
* The redirection URL. Used if {@link #reverseProxyEnabled} is true.
*/
private String reverseProxyTargetUrl;
/**
* Default constructor.
*/
public ApiSparkService() {
super(true);
}
/**
* Constructor using the default APISpark service url.
*
* @param agentLogin
* The login used to connect to the APISpark platform.
* @param agentPassword
* The password used to connect to the APISpark platform.
* @param agentCellId
* The identifier of the cell configured on the APISpark platform
* for your application.
* @param agentCellVersion
* The identifier of the cell version configured on the APISpark
* platform for your application.
* @param reverseProxyEnabled
* Indicates if the request redirection is enabled.
* @param reverseProxyTargetUrl
* The redirection URL.
*/
public ApiSparkService(String agentLogin, char[] agentPassword,
Integer agentCellId, Integer agentCellVersion,
boolean reverseProxyEnabled, String reverseProxyTargetUrl) {
this(DEFAULT_AGENT_SERVICE_URL, agentLogin, agentPassword, agentCellId,
agentCellVersion, reverseProxyEnabled, reverseProxyTargetUrl);
}
/**
* Constructor.
*
* @param agentServiceUrl
* The url of the APISpark service.
* @param agentLogin
* The login used to connect to the APISpark platform.
* @param agentPassword
* The password used to connect to the APISpark platform.
* @param agentCellId
* The identifier of the cell configured on the APISpark platform
* for your application.
* @param agentCellVersion
* The identifier of the cell version configured on the APISpark
* platform for your application.
* @param reverseProxyEnabled
* Indicates if the request redirection is enabled.
* @param reverseProxyTargetUrl
* The redirection URL.
*/
public ApiSparkService(String agentServiceUrl, String agentLogin,
char[] agentPassword, Integer agentCellId,
Integer agentCellVersion, boolean reverseProxyEnabled,
String reverseProxyTargetUrl) {
super(true);
this.agentPassword = agentPassword;
this.agentServiceUrl = agentServiceUrl;
this.agentLogin = agentLogin;
this.agentCellId = agentCellId;
this.agentCellVersion = agentCellVersion;
this.reverseProxyEnabled = reverseProxyEnabled;
this.reverseProxyTargetUrl = reverseProxyTargetUrl;
}
@Override
public Filter createInboundFilter(Context context) {
ApiSparkConfig apiSparkConfig = new ApiSparkConfig();
apiSparkConfig.setAgentCellId(agentCellId);
apiSparkConfig.setAgentCellVersion(agentCellVersion);
apiSparkConfig.setAgentServiceUrl(agentServiceUrl);
apiSparkConfig.setAgentLogin(agentLogin);
apiSparkConfig.setAgentPassword(agentPassword);
apiSparkConfig.setReverseProxyEnabled(reverseProxyEnabled);
apiSparkConfig.setReverseProxyTargetUrl(reverseProxyTargetUrl);
apiSparkConfig.setAgentAnalyticsBufferSize(agentAnalyticsBufferSize);
apiSparkConfig.setAgentAnalyticsPostPeriodInSecond(agentAnalyticsPostPeriodInSecond);
apiSparkFilter = new ApiSparkFilter(context, apiSparkConfig,
agentEnabled, firewallEnabled, firewallRules);
if (agentEnabled && agentRefreshPeriodInSecond > 0) {
TimerTask task = new AgentConfigurationTimerTask(apiSparkFilter);
agentRefreshTimer = new Timer(true);
long agentRefreshPeriodInMs = TimeUnit.SECONDS.toMillis(agentRefreshPeriodInSecond);
agentRefreshTimer.scheduleAtFixedRate(task, agentRefreshPeriodInMs,
agentRefreshPeriodInMs);
LOGGER.info("Setting agent refresh timer every "
+ TimeUnit.SECONDS.toMinutes(agentRefreshPeriodInSecond)
+ " minutes");
}
return apiSparkFilter;
}
@Override
public Filter createOutboundFilter(Context context) {
Encoder encoder = new Encoder(context, true, false,
new EncoderService());
return encoder;
}
/**
* Returns the agent Analytics module buffer size.
*
* @return The agent Analytics module buffer size.
*/
public int getAgentAnalyticsBufferSize() {
return agentAnalyticsBufferSize;
}
/**
* Returns the agent Analytics module post period.
*
* @return The agent Analytics module post period.
*/
public long getAgentAnalyticsPostPeriodInSecond() {
return agentAnalyticsPostPeriodInSecond;
}
/**
* Returns the password used to connect to the APISpark platform.
*
* @return The password used to connect to the APISpark platform.
*/
public String getAgentPassword() {
return new String(agentPassword);
}
/**
* Returns the url of the APISpark service.
*
* @return The url of the APISpark service.
*/
public String getAgentServiceUrl() {
return agentServiceUrl;
}
/**
* Returns the agent refresh period in seconds
*
* @return The agent refresh period in seconds
*/
public long getAgentRefreshPeriodInSecond() {
return agentRefreshPeriodInSecond;
}
/**
* Returns the login used to connect to the APISpark platform.
*
* @return The login used to connect to the APISpark platform.
*/
public String getAgentLogin() {
return agentLogin;
}
/**
* Returns the identifier of the cell configured on the APISpark platform
* for your application.
*
* @return The identifier of the cell configured on the APISpark platform
* for your application.
*/
public Integer getAgentCellId() {
return agentCellId;
}
/**
* Returns the identifier of the cell version configured on the APISpark
* platform for your application.
*
* @return The identifier of the cell version configured on the APISpark
* platform for your application.
*/
public Integer getAgentCellVersion() {
return agentCellVersion;
}
public FirewallConfig getFirewallConfig() {
return firewallConfig;
}
/**
* Returns the redirection URL. Used if {@link #isReverseProxyEnabled()}
* returns true.
*
* @return The redirection URL.
*/
public String getReverseProxyTargetUrl() {
return reverseProxyTargetUrl;
}
/**
* Indicates if the APISpark agent is enabled.
*
* @return True if the APISpark agent is enabled.
*/
public boolean isAgentEnabled() {
return agentEnabled;
}
/**
* Indicates if the firewall is enabled. Add firewall rules with
* {@link #firewallConfig}.
*
* @return True if the firewall is enabled.
*/
public boolean isFirewallEnabled() {
return firewallEnabled;
}
/**
* Indicates if the request redirection is enabled. If true, the redirection
* URL should be set with {@link #setReverseProxyTargetUrl(String)}.
*
* @return True if the request redirection is enabled.
*/
public boolean isReverseProxyEnabled() {
return reverseProxyEnabled;
}
/**
* Load the agent configuration from the file set by the system property
* 'apiSparkServiceConfig'.
*
* @see #CONFIGURATION_FILE_SYSTEM_PROPERTY_KEY
*/
public void loadConfiguration() {
String configurationFile = System
.getProperty(CONFIGURATION_FILE_SYSTEM_PROPERTY_KEY);
if (configurationFile == null) {
throw new IllegalArgumentException(
"Agent configuration file is not set. "
+ "Use system property '"
+ CONFIGURATION_FILE_SYSTEM_PROPERTY_KEY
+ "' to define it.");
}
loadConfiguration(new File(configurationFile));
}
/**
* Load the agent configuration from the file.
*
* @param configurationFile
* The configuration file.
*/
public void loadConfiguration(File configurationFile) {
if (configurationFile == null) {
throw new IllegalArgumentException(
"APISpark configuration file is null.");
}
if (!configurationFile.exists()) {
throw new IllegalArgumentException(
"APISpark configuration file does not exist: "
+ configurationFile.getAbsolutePath());
}
try {
loadConfiguration(new FileInputStream(configurationFile));
} catch (FileNotFoundException e) {
throw new IllegalArgumentException(
"APISpark configuration file error. See exception for details.",
e);
}
}
/**
* Load the agent configuration from the input stream.
*
* @param inputStream
* The input stream of the configuration file.
*/
public void loadConfiguration(InputStream inputStream) {
Properties properties = new Properties();
try {
properties.load(inputStream);
} catch (IOException e) {
throw new IllegalArgumentException(
"APISpark configuration file error. See exception for details.",
e);
}
this.agentServiceUrl = properties.getProperty("agent.serviceUrl",
DEFAULT_AGENT_SERVICE_URL);
this.agentLogin = properties.getProperty("agent.login");
this.agentPassword = getRequiredProperty(properties, "agent.password")
.toCharArray();
this.agentCellId = getRequiredIntegerProperty(properties,
"agent.cellId");
this.agentCellVersion = getRequiredIntegerProperty(properties,
"agent.cellVersion");
this.reverseProxyEnabled = Boolean.valueOf(getRequiredProperty(
properties, "reverseProxy.enabled"));
if (this.reverseProxyEnabled) {
this.reverseProxyTargetUrl = getRequiredProperty(properties,
"reverseProxy.targetUrl");
}
}
private String getRequiredProperty(Properties properties, String key) {
String value = properties.getProperty(key);
if (StringUtils.isNullOrEmpty(value)) {
throw new IllegalArgumentException(
"APISpark configuration file error. The property '" + key
+ "' is required");
}
return value;
}
private Integer getRequiredIntegerProperty(Properties properties, String key) {
String value = getRequiredProperty(properties, key);
try {
return Integer.valueOf(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"APISpark configuration file error. The property '" + key
+ "' should be a number", e);
}
}
/**
* Sets the agent Analytics module buffer size.
*
* @param agentAnalyticsBufferSize
* The agent Analytics module buffer size.
*/
public void setAgentAnalyticsBufferSize(int agentAnalyticsBufferSize) {
this.agentAnalyticsBufferSize = agentAnalyticsBufferSize;
}
/**
* Sets the agent Analytics module post period.
*
* @param agentAnalyticsPostPeriodInSecond
* The agent Analytics module post period.
*/
public void setAgentAnalyticsPostPeriodInSecond(long agentAnalyticsPostPeriodInSecond) {
this.agentAnalyticsPostPeriodInSecond = agentAnalyticsPostPeriodInSecond;
}
/**
* Sets the password used to connect to the APISpark platform.
*
* @param agentPassword
* The password used to connect to the APISpark platform.
*/
public void setAgentPassword(String agentPassword) {
this.agentPassword = agentPassword != null ? agentPassword
.toCharArray() : null;
}
/**
* Sets the url of the APISpark agent service.
*
* @param agentServiceUrl
* The url of the APISpark agent service.
*/
public void setAgentServiceUrl(String agentServiceUrl) {
this.agentServiceUrl = agentServiceUrl;
}
/**
* Sets the agent refresh period
*
* @param agentRefreshPeriodInSecond
* The agent refresh period in seconds
*
*/
public void setAgentRefreshPeriodInSecond(long agentRefreshPeriodInSecond) {
this.agentRefreshPeriodInSecond = agentRefreshPeriodInSecond;
}
/**
* Sets the login used to connect to the APISpark platform.
*
* @param agentLogin
* The login used to connect to the APISpark platform.
*/
public void setAgentLogin(String agentLogin) {
this.agentLogin = agentLogin;
}
/**
* Sets the identifier of the cell configured on the APISpark platform for
* your application.
*
* @param agentCellId
* The identifier of the cell configured on the APISpark platform
* for your application.
*/
public void setAgentCellId(Integer agentCellId) {
this.agentCellId = agentCellId;
}
/**
* Sets the identifier of the cell version configured on the APISpark
* platform for your application.
*
* @param agentCellVersion
* The version of the cell configured on the APISpark platform
* for your application.
*/
public void setAgentCellVersion(Integer agentCellVersion) {
this.agentCellVersion = agentCellVersion;
}
/**
* Indicates if the APISpark agent is enabled.
*
* @param agentEnabled
* True if the APISpark agent is enabled.
*/
public void setAgentEnabled(boolean agentEnabled) {
this.agentEnabled = agentEnabled;
}
/**
* Indicates if the firewall is enabled.
*
* @param firewallEnabled
* True if the firewall is enabled.
*/
public void setFirewallEnabled(boolean firewallEnabled) {
this.firewallEnabled = firewallEnabled;
}
/**
* Indicates if the reverse proxy is enabled. If true, the target URL should
* be set with {@link #setReverseProxyTargetUrl(String)}.
*
* @param reverseProxyEnabled
* True if the reverse proxy is enabled.
*/
public void setReverseProxyEnabled(boolean reverseProxyEnabled) {
this.reverseProxyEnabled = reverseProxyEnabled;
}
/**
* Set the target URL of the reverse proxy. Used if
* {@link #isReverseProxyEnabled()} is true.
*
* @param reverseProxyTargetUrl
* The target URL.
*/
public void setReverseProxyTargetUrl(String reverseProxyTargetUrl) {
this.reverseProxyTargetUrl = reverseProxyTargetUrl;
}
/** Stops the service and its timer */
@Override
public synchronized void stop() throws Exception {
super.stop();
if (agentRefreshTimer != null) {
agentRefreshTimer.cancel();
}
}
}