package detective.core.geb;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.http.client.CookieStore;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.openqa.selenium.Cookie;
import detective.core.Detective;
import detective.core.Parameters;
import detective.core.exception.StoryFailException;
import detective.task.HttpClientTask;
import geb.Page;
import groovy.lang.Closure;
/**
* Features this class added:
* <ul>
* <li>Fill parameters from detective parameter system automatically,
* for example google.co?local=en&additional=? the ? mark will be filled automatically first
* if there is a parameter called "additional" exists. For parameter "local", the value "en" is default setup, it
* still can be overwrite by this class if "local" exists in parameter list</li>
* </ul>
*
* @author james
*
*/
public class GebDetectivePage extends Page {
private String urlWithoutQuery = null;
/**
* Get page url without the query pages as we are going to replace it with our parameters
*/
public String getPageUrl() {
return getUrlWithoutQuery();
}
/**
* Return the page url which defined in url static content
* @return string
*/
public String getOriginPageUrl(){
return super.getPageUrl();
}
public void onLoad(Page previousPage) {
updateParameters();
reportIfNeeded();
afterLoad(previousPage);
}
/**
* Lifecycle method called when the page is connected to the browser.
* This implementation does nothing.
* You can still use onLoad but please remember call super.onLoad(previousPager) there
*
* @param previousPage The page that was active before this one
*/
public void afterLoad(Page previousPage){
}
/**
* Lifecycle method called when the page about to load, driver has been initialized.
* This implementation does nothing.
*/
public void beforeLoad(){
}
/**
* Share the cookies with HttpClientTask
*/
public void shareCookies(){
Object store = getCookieStore();
if (store == null){
store = new BasicCookieStore();
this.getParametersInner().put(HttpClientTask.PARAM_HTTP_COOKIES, store);
}
CookieStore cookieStore = (CookieStore)store;
for (Cookie cookie : this.getDriver().manage().getCookies()){
BasicClientCookie newCookie = new BasicClientCookie(cookie.getName(), cookie.getValue());
newCookie.setDomain(cookie.getDomain());
newCookie.setPath(cookie.getPath());
newCookie.setExpiryDate(cookie.getExpiry());
newCookie.setSecure(cookie.isSecure());
cookieStore.addCookie(newCookie);
}
}
/**
* Read cookies from Detective parameter system, the cookies usually created by HttpClientTask.
* As selenium only allow setup for current active domain, we have to check domain name
*/
public void readCookies(){
Object store = getCookieStore();
if (store != null){
String domainName = getCurrentDomainName();
if (domainName == null)
return;
CookieStore cookieStore = (CookieStore)store;
for (org.apache.http.cookie.Cookie cookie : cookieStore.getCookies()){
if (domainName.equalsIgnoreCase(cookie.getDomain()))
this.getDriver().manage().addCookie(new Cookie(cookie.getName(), cookie.getValue()));
}
}
}
protected CookieStore getCookieStore() {
Object store = this.getParametersInner().get(HttpClientTask.PARAM_HTTP_COOKIES);
if (store == null)
return null;
return (CookieStore)store;
}
/**
* return the current opened domain name
* @return null if there is no page currently open
*/
private String getCurrentDomainName(){
String domainName = this.getDriver().getCurrentUrl();
try {
if (domainName != null && domainName.length() > 0)
domainName = new URL(domainName).getHost();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
return domainName;
}
@Override
public void to(Map params, Object[] args) {
beforeLoad();
try {
params = prepareUrlParameters(prepareUrlParameters(params));
} catch (Exception e) {
throw new RuntimeException(e);
}
super.to(params, args);
}
Map prepareUrlParameters(Map params) throws MalformedURLException, UnsupportedEncodingException{
String url = super.getPageUrl();
Map<String, String> queries = splitQuery(url);
Parameters ps = this.getParametersInner();
for (String key : queries.keySet()){
if (ps.containsKey(key) && ps.get(key) != null)
queries.put(key, ps.get(key).toString());
if ("?".equals(queries.get(key)))
queries.put(key, "");
}
queries.putAll(params);
return queries;
}
/**
* When a page fully loaded (at check returns true),
* this method can be invoked, you can read anything from the page
* and write into detective parameter system
*/
protected void putParameter(String key, Object value){
GebSession.getParameters().put(key, value);
}
/**
* Get parameters from detective framework
* @return the Parameters in whole scenario
*/
protected Parameters getParametersInner(){
return GebSession.getParameters();
}
protected static Map<String, String> splitQuery(String url) throws UnsupportedEncodingException {
Map<String, String> query_pairs = new LinkedHashMap<String, String>();
if (url.indexOf("?") < 0 & url.length() > 1)
return query_pairs;
String query = url.substring(url.indexOf("?") + 1, url.length());
if (query == null)
return query_pairs;
String[] pairs = query.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
query_pairs.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
}
return query_pairs;
}
private String getUrlWithoutQuery(){
String urlStr = super.getPageUrl();
if (urlStr.indexOf("?") >= 0){
urlStr = urlStr.substring(0, urlStr.indexOf("?"));
}
//Handle path parameters, see test UrlParameterAutoFillPage
return replaceOneParameter(urlStr, this.getParametersInner());
}
private String replaceOneParameter(String url, Parameters parameters){
int indexFirst = url.indexOf("{");
int indexLast = url.indexOf("}");
if (indexFirst < 0 && indexLast < 0)
return url;
else if (indexFirst >= 0 && indexLast < 0){
throw new RuntimeException("found { in url path but not }");
}else if (indexFirst < 0 && indexLast >= 0){
throw new RuntimeException("found } in url path but not {");
}else{
String parameterName = url.substring(indexFirst + 1, indexLast);
if (! parameters.containsKey(parameterName))
throw new RuntimeException(parameterName + " not exists in secario parameter list.");
else{
url = url.replace("{" + parameterName + "}", parameters.get(parameterName).toString());
return replaceOneParameter(url, parameters);
}
}
}
private void updateParameters(){
Method fieldParams;
try {
fieldParams = this.getClass().getMethod("getParameters");
} catch (Exception e) {
fieldParams = null;
}
if (fieldParams == null)
return;
if (fieldParams.getReturnType() == Object.class){
try {
Closure<?> closure = (Closure<?>)fieldParams.invoke(this);
if (closure != null){
closure = (Closure<?>)closure.clone();
closure.setDelegate(this);
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
Object result = closure.call();
if (result != null && result instanceof Map){
Map params = (Map)result;
for (Object key : params.keySet()){
this.putParameter(key.toString(), params.get(key));
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}else{
throw new RuntimeException("parameters have to be a cloure.");
}
}
private void reportIfNeeded(){
if ("everyPage".equals(Detective.getConfig().getString("browser.report"))){
this.getBrowser().report(this.getClass().getSimpleName());
}
}
}