com.adobe.target.edge.client.ondevice.DefaultRuleLoader Maven / Gradle / Ivy
/*
* Copyright 2021 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package com.adobe.target.edge.client.ondevice;
import static com.adobe.target.edge.client.ondevice.OnDeviceDecisioningService.TIMING_EXECUTE_REQUEST;
import com.adobe.target.edge.client.ClientConfig;
import com.adobe.target.edge.client.ClientProxyConfig;
import com.adobe.target.edge.client.exception.TargetClientException;
import com.adobe.target.edge.client.exception.TargetExceptionHandler;
import com.adobe.target.edge.client.http.JacksonObjectMapper;
import com.adobe.target.edge.client.model.ondevice.OnDeviceDecisioningHandler;
import com.adobe.target.edge.client.model.ondevice.OnDeviceDecisioningRuleSet;
import com.adobe.target.edge.client.service.TelemetryService;
import com.adobe.target.edge.client.utils.MathUtils;
import com.adobe.target.edge.client.utils.TimingTool;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import kong.unirest.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultRuleLoader implements RuleLoader {
private static final Logger logger = LoggerFactory.getLogger(DefaultRuleLoader.class);
private static final String MAJOR_VERSION = "1";
private static final int MAX_RETRIES = 10;
private OnDeviceDecisioningRuleSet latestRules;
private String lastETag;
private ClientConfig clientConfig;
private TelemetryService telemetryService;
private UnirestInstance unirestInstance = Unirest.spawnInstance();
private Timer timer = new Timer(this.getClass().getCanonicalName());
private boolean started = false;
private boolean succeeded = false;
private int retries = 0;
private int numFetches = 0;
private Date lastFetch = null;
public DefaultRuleLoader() {}
@Override
public OnDeviceDecisioningRuleSet getLatestRules() {
return latestRules;
}
@Override
public synchronized void start(
final ClientConfig clientConfig, TelemetryService telemetryService) {
if (!clientConfig.isOnDeviceDecisioningEnabled()) {
return;
}
if (started) {
return;
}
ObjectMapper mapper = new JacksonObjectMapper();
byte[] artifactPayload = clientConfig.getOnDeviceArtifactPayload();
if (artifactPayload != null) {
String payload = new String(artifactPayload, StandardCharsets.UTF_8);
OnDeviceDecisioningRuleSet ruleSet =
mapper.readValue(payload, new GenericType() {});
String invalidMessage = invalidRuleSetMessage(ruleSet, null);
if (invalidMessage == null) {
setLatestRules(ruleSet);
OnDeviceDecisioningHandler handler = clientConfig.getOnDeviceDecisioningHandler();
if (handler != null && !succeeded) {
succeeded = true;
handler.onDeviceDecisioningReady();
}
} else {
logger.warn(invalidMessage);
TargetExceptionHandler handler = clientConfig.getExceptionHandler();
if (handler != null) {
handler.handleException(new TargetClientException(invalidMessage));
}
}
}
started = true;
retries = 0;
if (unirestInstance != null) {
unirestInstance
.config()
.socketTimeout(clientConfig.getSocketTimeout())
.connectTimeout(clientConfig.getConnectTimeout())
.concurrency(
clientConfig.getMaxConnectionsTotal(), clientConfig.getMaxConnectionsPerHost())
.automaticRetries(clientConfig.isEnabledRetries())
.enableCookieManagement(false)
.setObjectMapper(mapper)
.setDefaultHeader("Accept", "application/json");
if (clientConfig.isProxyEnabled()) {
ClientProxyConfig proxyConfig = clientConfig.getProxyConfig();
if (proxyConfig.isAuthProxy()) {
unirestInstance
.config()
.proxy(
proxyConfig.getHost(),
proxyConfig.getPort(),
proxyConfig.getUsername(),
proxyConfig.getPassword());
} else {
unirestInstance.config().proxy(proxyConfig.getHost(), proxyConfig.getPort());
}
}
}
this.clientConfig = clientConfig;
this.telemetryService = telemetryService;
this.scheduleTimer(0);
}
public void stop() {
this.timer.cancel();
if (this.unirestInstance != null) {
this.unirestInstance.shutDown();
}
this.reset();
}
public void refresh() {
this.loadRules(this.clientConfig);
this.scheduleTimer(getPollingInterval());
}
private void reset() {
this.started = false;
this.succeeded = false;
this.retries = 0;
this.numFetches = 0;
this.lastFetch = null;
this.lastETag = null;
this.latestRules = null;
}
private void scheduleTimer(long delay) {
if (this.timer != null) {
this.timer.cancel();
}
this.timer = new Timer(this.getClass().getCanonicalName());
this.timer.schedule(
new TimerTask() {
@Override
public void run() {
boolean success = DefaultRuleLoader.this.loadRules(clientConfig);
OnDeviceDecisioningHandler handler = clientConfig.getOnDeviceDecisioningHandler();
if (!success && DefaultRuleLoader.this.latestRules == null) {
// retry if initial rules file download fails
String message;
if (DefaultRuleLoader.this.retries++ < MAX_RETRIES) {
long retryDelay = DefaultRuleLoader.this.retries * 1000;
message =
String.format(
"Download of local-decisioning rules failed, retying in %s ms", retryDelay);
logger.debug(message);
scheduleTimer(retryDelay);
} else {
message = "Exhausted retries trying to download local-decisioning rules.";
logger.warn(message);
}
if (handler != null) {
handler.artifactDownloadFailed(new TargetClientException(message));
}
} else {
if (handler != null && !succeeded) {
succeeded = true;
handler.onDeviceDecisioningReady();
}
DefaultRuleLoader.this.numFetches++;
DefaultRuleLoader.this.lastFetch = new Date();
}
}
},
delay,
getPollingInterval());
}
public long getPollingInterval() {
return clientConfig.getOnDeviceDecisioningPollingIntSecs() * 1000;
}
public int getNumFetches() {
return numFetches;
}
public Date getLastFetch() {
return lastFetch;
}
public String getLocation() {
return this.getLocalDecisioningUrl(this.clientConfig);
}
// For unit test mocking
protected GetRequest generateRequest(ClientConfig clientConfig) {
GetRequest getRequest = unirestInstance.get(getLocalDecisioningUrl(clientConfig));
if (this.lastETag != null) {
getRequest.header("If-None-Match", this.lastETag);
}
return getRequest;
}
// For unit test mocking
protected HttpResponse executeRequest(GetRequest getRequest) {
return getRequest.asObject(new GenericType() {});
}
// For unit test mocking
protected void setLatestRules(OnDeviceDecisioningRuleSet ruleSet) {
this.latestRules = ruleSet;
}
// For unit test mocking
protected void setLatestETag(String etag) {
this.lastETag = etag;
}
// For unit test mocking
protected boolean loadRules(ClientConfig clientConfig) {
try {
TargetExceptionHandler handler = clientConfig.getExceptionHandler();
GetRequest request = generateRequest(clientConfig);
TimingTool timer = new TimingTool();
timer.timeStart(TIMING_EXECUTE_REQUEST);
HttpResponse response = executeRequest(request);
double artifactDownloadTime = timer.timeEnd(TIMING_EXECUTE_REQUEST);
double artifactDownloadTimeRounded = MathUtils.roundDouble(artifactDownloadTime, 2);
this.telemetryService.addTelemetry(artifactDownloadTimeRounded);
if (response.getStatus() != 200) {
if (response.getStatus() == 304) {
// Not updated, skip
return true;
}
String message =
"Received invalid HTTP response while getting local-decisioning rule set: "
+ response.getStatus()
+ " : "
+ response.getStatusText()
+ " from "
+ getLocalDecisioningUrl(clientConfig);
logger.warn(message);
if (handler != null) {
handler.handleException(new TargetClientException(message));
}
return false;
}
OnDeviceDecisioningRuleSet ruleSet = response.getBody();
String invalidMessage = invalidRuleSetMessage(ruleSet, response);
if (invalidMessage == null) {
setLatestETag(response.getHeaders().getFirst("ETag"));
setLatestRules(ruleSet);
OnDeviceDecisioningHandler localHandler = clientConfig.getOnDeviceDecisioningHandler();
if (localHandler != null) {
localHandler.artifactDownloadSucceeded(
request == null ? null : request.asBytes().getBody());
}
logger.trace("rulesList={}", latestRules);
return true;
} else {
logger.warn(invalidMessage);
if (handler != null) {
handler.handleException(new TargetClientException(invalidMessage));
}
return false;
}
} catch (Throwable t) {
String message =
"Hit exception while getting local-decisioning rule set from: "
+ getLocalDecisioningUrl(clientConfig);
logger.warn(message, t);
TargetExceptionHandler handler = clientConfig.getExceptionHandler();
if (handler != null) {
handler.handleException(new TargetClientException(message, t));
}
return false;
}
}
private String invalidRuleSetMessage(
OnDeviceDecisioningRuleSet ruleSet, HttpResponse response) {
if (ruleSet == null || ruleSet.getRules() == null) {
String message = "Unable to parse local-decisioning rule set";
if (response != null) {
message +=
" from: "
+ getLocalDecisioningUrl(clientConfig)
+ ", error: "
+ response.getParsingError();
}
return message;
}
if (ruleSet.getVersion() == null || !ruleSet.getVersion().startsWith(MAJOR_VERSION + ".")) {
return "Unknown rules version: " + ruleSet.getVersion();
}
return null;
}
private String getLocalDecisioningUrl(ClientConfig clientConfig) {
return "https://"
+ clientConfig.getOnDeviceConfigHostname()
+ "/"
+ clientConfig.getClient()
+ "/"
+ clientConfig.getOnDeviceEnvironment().toLowerCase()
+ "/v"
+ MAJOR_VERSION
+ (clientConfig.getDefaultPropertyToken() != null
? "/" + clientConfig.getDefaultPropertyToken()
: "")
+ "/rules.json";
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy