// Copyright (c) 2014, SAS Institute Inc., Cary, NC, USA, All Rights Reserved
package com.sas.unravl;
import com.sas.unravl.annotations.UnRAVLAssertionPlugin;
import com.sas.unravl.annotations.UnRAVLAuthPlugin;
import com.sas.unravl.annotations.UnRAVLExtractorPlugin;
import com.sas.unravl.annotations.UnRAVLRequestBodyGeneratorPlugin;
import com.sas.unravl.assertions.UnRAVLAssertion;
import com.sas.unravl.auth.CredentialsProvider;
import com.sas.unravl.auth.NetrcCredentialsProvider;
import com.sas.unravl.auth.UnRAVLAuth;
import com.sas.unravl.extractors.UnRAVLExtractor;
import com.sas.unravl.generators.UnRAVLRequestBodyGenerator;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
/**
* Manages the mappings of keywords to plugin implementation classes.
*
* @author David.Biesack@sas.com
*/
public class UnRAVLPlugins {
private static final Logger logger = Logger.getLogger(UnRAVLRuntime.class);
private Map<String, Class<? extends UnRAVLRequestBodyGenerator>> requestBodyGenerators = new HashMap<String, Class<? extends UnRAVLRequestBodyGenerator>>();
private Map<String, Class<? extends UnRAVLAssertion>> assertions = new HashMap<String, Class<? extends UnRAVLAssertion>>();
private Map<String, Class<? extends UnRAVLExtractor>> extractors = new HashMap<String, Class<? extends UnRAVLExtractor>>();
private Map<String, Class<? extends UnRAVLAuth>> auth = new HashMap<String, Class<? extends UnRAVLAuth>>();
private CredentialsProvider credentialsProvider;
private RestTemplate defaultRestTemplate;
// must be "Groovy", "groovy", "JavaScript", "js", "javascript", or another
// valid ScriptEngine name
@Value("#{systemProperties['unravl.script.language'] ?: 'groovy'}")
private String scriptLanguage = "groovy";
public void setScriptLanguage(String scriptLanguage) {
this.scriptLanguage = scriptLanguage;
}
/**
* Return a credentials provider - the instance assigned in the setter, or a
* default {@link NetrcCredentialsProvider}
*
* @return the assigned CredentialsProvider
*/
public CredentialsProvider getCredentialsProvider() {
return (credentialsProvider == null) ? new NetrcCredentialsProvider()
: credentialsProvider;
}
/**
* Assign a credentials provider
*
* @param credentialsProvider
* the instance which can get userid/password for a host
*/
public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
}
public String getScriptLanguage() {
if (scriptLanguage == null || scriptLanguage.trim().length() == 0)
return "groovy";
else
return scriptLanguage;
}
public ScriptEngine interpreter(String lang) throws UnRAVLException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager
.getEngineByName(lang == null ? getScriptLanguage() : lang);
if (engine == null) {
logSupportedScriptEngines();
throw new UnRAVLException(String.format(
"No script engine available for %sscript lanaguge %s",
lang == null ? "unravl.script.langauge " : "",
getScriptLanguage()));
}
return engine;
}
public void addAssertion(Class<? extends UnRAVLAssertion> class1) {
UnRAVLAssertionPlugin a = class1
.getAnnotation(UnRAVLAssertionPlugin.class);
String[] keys = a.value();
for (String key : keys) {
logger.trace("Define assertion '" + key + "' via " + class1);
assertions.put(key, class1);
}
}
public void addAuth(Class<? extends UnRAVLAuth> class1) {
UnRAVLAuthPlugin a = class1.getAnnotation(UnRAVLAuthPlugin.class);
String[] keys = a.value();
for (String key : keys) {
logger.trace("Define auth '" + key + "' via " + class1);
auth.put(key, class1);
}
}
public void addExtractor(Class<? extends UnRAVLExtractor> class1) {
UnRAVLExtractorPlugin a = class1
.getAnnotation(UnRAVLExtractorPlugin.class);
for (String key : a.value()) {
logger.trace("Define extractor '" + key + "' via " + class1);
extractors.put(key, class1);
}
}
public void addRequestBodyGenerator(
Class<? extends UnRAVLRequestBodyGenerator> class1) {
UnRAVLRequestBodyGeneratorPlugin a = class1
.getAnnotation(UnRAVLRequestBodyGeneratorPlugin.class);
for (String key : a.value()) {
logger.trace("Define body generator '" + key + "' via " + class1);
requestBodyGenerators.put(key, class1);
}
}
public Map<String, Class<? extends UnRAVLAssertion>> getAssertions() {
return assertions;
}
public Map<String, Class<? extends UnRAVLRequestBodyGenerator>> getBodyGenerators() {
return requestBodyGenerators;
}
public Map<String, Class<? extends UnRAVLExtractor>> getExtractors() {
return extractors;
}
public Map<String, Class<? extends UnRAVLAuth>> getAuth() {
return auth;
}
/**
* log the availability of scripting engines supported in this environment.
*/
public static void logSupportedScriptEngines() {
ScriptEngineManager manager = new ScriptEngineManager();
logger.error("Available Script Engines:");
for (final ScriptEngineFactory scriptEngine : manager
.getEngineFactories()) {
logger.error(scriptEngine.getEngineName() + " "
+ scriptEngine.getEngineVersion());
logger.error("\tLanguage: " + scriptEngine.getLanguageName() + " "
+ scriptEngine.getLanguageVersion());
StringBuilder es = new StringBuilder();
for (final String engineAlias : scriptEngine.getNames()) {
es.append(engineAlias).append(",");
}
logger.error("\tAliases: " + es.toString());
}
}
/**
* Set the default RestTemplate instance that UnRAVL and ApiCall will use.
*
* @param restTemplate
* the default RestTemplate instance
*/
public void setRestTemplate(RestTemplate restTemplate) {
this.defaultRestTemplate = restTemplate;
}
/**
* @return the default RestTemplate instance that UnRAVL and ApiCall will
* use
*/
public RestTemplate getRestTemplate() {
return defaultRestTemplate == null ? newRestTemplate()
: defaultRestTemplate;
}
/**
* This RestTemplate uses HttpComponentsClientHttpRequestFactory that
* follows redirect for GET and HEAD calls. We use
* HttpComponentsClientHttpRequestFactory because the default
* HttpUrlConnection used by Spring throws I/O exceptions from
* response.getBody() when the remote call returns 4xx responses, which
* means the client can't read the response body that accompanies such
* errors. The Apache HTTP Commons implementation of HttpUrlConnection does
* not throw such exceptions, so UnRAVL can read the response body in all
* cases.
* <p>
* This instance also sets an error handler which ignores all errors, so
* that ApiCall can extract the HTTP response code, headers, and response
* body.
* </p>
*
* @return a RestTemplate instance to use for making HTTP calls when running
* UnRAVL scripts.
*/
public static RestTemplate newRestTemplate() {
final ResponseErrorHandler ignoreResponseErrors = new ResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response)
throws IOException {
// NO OP. This is only called if hasError returns true,
// but below we always return false, so we can extract the body
// and the HTTP status code, even for 4xx and 5xx errors.
}
@Override
public boolean hasError(ClientHttpResponse response)
throws IOException {
return false;
}
};
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
final HttpClient httpClient = HttpClientBuilder.create()
.setRedirectStrategy(new UnRAVLRedirectStrategy()).build();
factory.setHttpClient(httpClient);
RestTemplate rt = new RestTemplate(factory);
rt.setErrorHandler(ignoreResponseErrors);
return rt;
}
private static final class UnRAVLRedirectStrategy extends
DefaultRedirectStrategy {
@Override
protected boolean isRedirectable(final String method) {
return HttpGet.METHOD_NAME.equalsIgnoreCase(method) //
|| HttpHead.METHOD_NAME.equalsIgnoreCase(method);
}
}
}