package org.itsnat.droid.impl.browser;
import android.content.Context;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.util.DisplayMetrics;
import org.apache.http.params.HttpParams;
import org.itsnat.droid.AttrResourceInflaterListener;
import org.itsnat.droid.ClientErrorMode;
import org.itsnat.droid.HttpRequestResult;
import org.itsnat.droid.ItsNatDroidException;
import org.itsnat.droid.ItsNatDroidServerResponseException;
import org.itsnat.droid.OnPageLoadErrorListener;
import org.itsnat.droid.OnPageLoadListener;
import org.itsnat.droid.OnScriptErrorListener;
import org.itsnat.droid.PageRequest;
import org.itsnat.droid.impl.browser.serveritsnat.PageItsNatImpl;
import org.itsnat.droid.impl.browser.serveritsnat.XMLDOMLayoutPageItsNatDownloader;
import org.itsnat.droid.impl.browser.servernotitsnat.PageNotItsNatImpl;
import org.itsnat.droid.impl.dom.DOMAttrRemote;
import org.itsnat.droid.impl.dom.ParsedResource;
import org.itsnat.droid.impl.dom.ParsedResourceXMLDOM;
import org.itsnat.droid.impl.dom.layout.XMLDOMLayout;
import org.itsnat.droid.impl.dom.layout.XMLDOMLayoutPage;
import org.itsnat.droid.impl.dom.layout.XMLDOMLayoutPageItsNat;
import org.itsnat.droid.impl.domparser.XMLDOMParserContext;
import org.itsnat.droid.impl.domparser.XMLDOMRegistry;
import org.itsnat.droid.impl.domparser.layout.XMLDOMLayoutParser;
import org.itsnat.droid.impl.util.MimeUtil;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Created by jmarranz on 5/06/14.
*/
public class PageRequestImpl implements PageRequest
{
protected ItsNatDroidBrowserImpl browser;
protected Context ctx;
protected RequestPropertyMap requestPropertyMap;
protected HttpParams httpParams;
protected int connectTimeout;
protected int readTimeout;
protected int bitmapDensityReference = DisplayMetrics.DENSITY_XHIGH;
protected int errorMode = ClientErrorMode.SHOW_SERVER_AND_CLIENT_ERRORS;
protected OnPageLoadListener pageLoadListener;
protected OnPageLoadErrorListener pageLoadErrorListener;
protected OnScriptErrorListener scriptErrorListener;
protected AttrResourceInflaterListener attrResourcesInflaterListener;
protected boolean sync = false;
protected String url;
protected String pageURLBase;
public PageRequestImpl(ItsNatDroidBrowserImpl browser)
{
this.browser = browser;
this.requestPropertyMap = browser.getRequestPropertyMap().copy();
this.httpParams = browser.getHttpParams().copy();
this.connectTimeout = browser.getConnectTimeout();
this.readTimeout = browser.getReadTimeout();
}
public PageRequestImpl(PageRequestImpl origin) // clone/copy origin to "this"
{
this.browser = origin.browser;
this.ctx = origin.ctx;
this.requestPropertyMap = origin.requestPropertyMap.copy();
this.httpParams = origin.getHttpParams().copy();
this.connectTimeout = origin.connectTimeout;
this.readTimeout = origin.readTimeout;
this.bitmapDensityReference = origin.bitmapDensityReference;
this.errorMode = origin.errorMode;
this.pageLoadListener = origin.pageLoadListener;
this.pageLoadErrorListener = origin.pageLoadErrorListener;
this.scriptErrorListener = origin.getOnScriptErrorListener();
this.attrResourcesInflaterListener = origin.attrResourcesInflaterListener;
this.sync = origin.sync;
this.url = origin.url;
this.pageURLBase = origin.pageURLBase;
}
public ItsNatDroidBrowserImpl getItsNatDroidBrowserImpl()
{
return browser;
}
public Context getContext()
{
return ctx;
}
@Override
public PageRequest setContext(Context ctx)
{
this.ctx = ctx;
return this;
}
public int getBitmapDensityReference()
{
return bitmapDensityReference;
}
@Override
public PageRequest setBitmapDensityReference(int bitmapDensityReference)
{
this.bitmapDensityReference = bitmapDensityReference;
return this;
}
public int getClientErrorMode()
{
return errorMode;
}
@Override
public PageRequest setClientErrorMode(int errorMode)
{
this.errorMode = errorMode;
return this;
}
public OnPageLoadListener getOnPageLoadListener()
{
return pageLoadListener;
}
@Override
public PageRequest setOnPageLoadListener(OnPageLoadListener pageListener)
{
this.pageLoadListener = pageListener;
return this;
}
public OnPageLoadErrorListener getOnPageLoadErrorListener()
{
return pageLoadErrorListener;
}
@Override
public PageRequest setOnPageLoadErrorListener(OnPageLoadErrorListener errorListener)
{
this.pageLoadErrorListener = errorListener;
return this;
}
public OnScriptErrorListener getOnScriptErrorListener()
{
return scriptErrorListener;
}
@Override
public PageRequest setOnScriptErrorListener(OnScriptErrorListener listener)
{
this.scriptErrorListener = listener;
return this;
}
public AttrResourceInflaterListener getAttrResourceInflaterListener()
{
return attrResourcesInflaterListener;
}
@Override
public PageRequest setAttrResourceInflaterListener(AttrResourceInflaterListener attrResourcesInflaterListener)
{
this.attrResourcesInflaterListener = attrResourcesInflaterListener;
return this;
}
public RequestPropertyMap getRequestPropertyMap()
{
return requestPropertyMap;
}
@Override
public PageRequest addRequestProperty(String name, String value)
{
requestPropertyMap.addProperty(name, value);
return this;
}
@Override
public PageRequest setRequestProperty(String name, String value)
{
requestPropertyMap.setProperty(name, value);
return this;
}
@Override
public boolean removeProperty(String name)
{
return requestPropertyMap.removeProperty(name);
}
@Override
public String getRequestProperty(String name)
{
return requestPropertyMap.getPropertySingle(name);
}
@Override
public Map<String, List<String>> getRequestProperties()
{
return requestPropertyMap.getPropertyUnmodifiableMap();
}
public HttpParams getHttpParams()
{
return httpParams;
}
@Override
public PageRequest setConnectTimeout(int timeoutMillis)
{
this.connectTimeout = timeoutMillis;
return this;
}
@Override
public int getConnectTimeout()
{
return connectTimeout;
}
public PageRequest setReadTimeout(int timeoutMillis)
{
this.readTimeout = timeoutMillis;
return this;
}
public int getReadTimeout()
{
return readTimeout;
}
public boolean isSynchronous()
{
return sync;
}
@Override
public PageRequest setSynchronous(boolean sync)
{
this.sync = sync;
return this;
}
public String getURL()
{
return url;
}
@Override
public PageRequest setURL(String url)
{
this.url = url;
this.pageURLBase = HttpUtil.getBasePathOfURL(url);
return this;
}
public String getPageURLBase()
{
return pageURLBase;
}
@Override
public void execute()
{
// El PageRequestImpl debe poder ser reutilizado si quiere el usuario
if (url == null) throw new ItsNatDroidException("Missing URL");
Context ctx = getContext();
Resources res = ctx.getResources();
String pageURLBase = getPageURLBase();
HttpRequestData httpRequestData = new HttpRequestData(this);
Map<String,ParsedResource> urlResDownloadedMap = new HashMap<String,ParsedResource>();
XMLDOMRegistry xmlDOMRegistry = browser.getItsNatDroidImpl().getXMLDOMRegistry();
XMLDOMParserContext xmlDOMParserContext = new XMLDOMParserContext(xmlDOMRegistry,ctx);
if (sync)
executeSync(url,pageURLBase,httpRequestData,urlResDownloadedMap,xmlDOMParserContext);
else
executeAsync(url,pageURLBase,httpRequestData,urlResDownloadedMap,xmlDOMParserContext);
}
private void executeSync(String url,String pageURLBase,HttpRequestData httpRequestData,Map<String,ParsedResource> urlResDownloadedMap,XMLDOMParserContext xmlDOMParserContext)
{
PageRequestResult pageRequestResult;
try
{
pageRequestResult = executeInBackground(url,pageURLBase, httpRequestData,urlResDownloadedMap,xmlDOMParserContext);
}
catch(Exception ex)
{
// Aunque la excepción pueda ser capturada por el usuario al llamar al método público síncrono, tenemos
// que respetar su decisión de usar un listener
onFinishError(this,ex);
return; // No se puede continuar
}
onFinishOk(this, pageRequestResult);
}
private void executeAsync(String url,String pageURLBase,HttpRequestData httpRequestData,Map<String,ParsedResource> urlResDownloadedMap,XMLDOMParserContext xmlDOMParserContext)
{
HttpGetPageAsyncTask task = new HttpGetPageAsyncTask(this,url,pageURLBase,httpRequestData,urlResDownloadedMap,xmlDOMParserContext, ctx);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); // Con execute() a secas se ejecuta en un "pool" de un sólo hilo sin verdadero paralelismo
}
public static PageRequestResult executeInBackground(String url,String pageURLBase,HttpRequestData httpRequestData,
Map<String,ParsedResource> urlResDownloadedMap,XMLDOMParserContext xmlDOMParserContext) throws Exception
{
// Ejecutado en multihilo en el caso async
HttpRequestResultOKImpl result = HttpUtil.httpGet(url, httpRequestData,null, null);
PageRequestResult pageReqResult = processHttpRequestResultMultiThread(result, pageURLBase, httpRequestData,urlResDownloadedMap,xmlDOMParserContext);
return pageReqResult;
}
public static void onFinishOk(PageRequestImpl pageRequest,PageRequestResult pageRequestResult)
{
try
{
pageRequest.processResponse(pageRequestResult);
}
catch(Exception ex)
{
OnPageLoadErrorListener errorListener = pageRequest.getOnPageLoadErrorListener();
if (errorListener != null)
{
HttpRequestResult resultError = (ex instanceof ItsNatDroidServerResponseException) ? ((ItsNatDroidServerResponseException)ex).getHttpRequestResult() : pageRequestResult.getHttpRequestResultOKImpl();
errorListener.onError(pageRequest, ex, resultError); // Para poder recogerla desde fuera
}
else
{
if (ex instanceof ItsNatDroidException) throw (ItsNatDroidException)ex;
else throw new ItsNatDroidException(ex);
}
}
}
public static void onFinishError(PageRequestImpl pageRequest,Exception ex)
{
HttpRequestResult result = (ex instanceof ItsNatDroidServerResponseException) ? ((ItsNatDroidServerResponseException)ex).getHttpRequestResult() : null;
ItsNatDroidException exFinal = (ex instanceof ItsNatDroidException) ? (ItsNatDroidException)ex : new ItsNatDroidException(ex);
OnPageLoadErrorListener errorListener = pageRequest.getOnPageLoadErrorListener();
if (errorListener != null)
{
errorListener.onError(pageRequest, exFinal, result);
}
else
{
// No se ha cargado la página en este contexto, no tenemos por ejemplo un errorMode
throw exFinal;
}
}
private static PageRequestResult processHttpRequestResultMultiThread(HttpRequestResultOKImpl httpRequestResult,
String pageURLBase, HttpRequestData httpRequestData,
Map<String,ParsedResource> urlResDownloadedMap,XMLDOMParserContext xmlDOMParserContext) throws Exception
{
// Método ejecutado en hilo downloader NO UI
String markup = httpRequestResult.getResponseText();
String itsNatServerVersion = httpRequestResult.getItsNatServerVersion(); // Puede ser null (page no servida por ItsNat)
XMLDOMRegistry xmlDOMRegistry = xmlDOMParserContext.getXMLDOMRegistry();
ParsedResourceXMLDOM<XMLDOMLayout> resourceXMLDOM = xmlDOMRegistry.buildXMLDOMLayoutAndCachingByMarkupAndResDesc(markup,null, itsNatServerVersion, XMLDOMLayoutParser.LayoutType.PAGE, xmlDOMParserContext);
XMLDOMLayoutPage xmlDOMLayoutPage = (XMLDOMLayoutPage)resourceXMLDOM.getXMLDOM();
PageRequestResult pageReqResult = new PageRequestResult(httpRequestResult, xmlDOMLayoutPage,xmlDOMParserContext);
{
XMLDOMLayoutPageDownloader downloader = (XMLDOMLayoutPageDownloader) XMLDOMDownloader.createXMLDOMDownloader(xmlDOMLayoutPage,pageURLBase, httpRequestData,itsNatServerVersion,urlResDownloadedMap,xmlDOMParserContext);
downloader.downloadRemoteResources();
}
if (xmlDOMLayoutPage instanceof XMLDOMLayoutPageItsNat)
{
XMLDOMLayoutPageItsNat xmldomLayoutPageParent = (XMLDOMLayoutPageItsNat)xmlDOMLayoutPage;
String loadInitScript = xmldomLayoutPageParent.getLoadInitScript();
if (loadInitScript != null) // Es nulo si el scripting está desactivado
{
XMLDOMLayoutPageItsNatDownloader downloader = XMLDOMLayoutPageItsNatDownloader.createXMLDOMLayoutPageItsNatDownloader(xmldomLayoutPageParent,pageURLBase, httpRequestData,itsNatServerVersion,urlResDownloadedMap,xmlDOMParserContext);
LinkedList<DOMAttrRemote> attrRemoteListBSParsed = downloader.parseBeanShellAndDownloadRemoteResources(loadInitScript);
if (attrRemoteListBSParsed != null)
pageReqResult.setAttrRemoteListBSParsed(attrRemoteListBSParsed);
}
}
return pageReqResult;
}
private void processResponse(PageRequestResult pageRequestResult)
{
HttpRequestResultOKImpl httpReqResult = pageRequestResult.getHttpRequestResultOKImpl();
if (!MimeUtil.MIME_ANDROID_LAYOUT.equals(httpReqResult.getMimeType()))
throw new ItsNatDroidServerResponseException("Expected " + MimeUtil.MIME_ANDROID_LAYOUT + " MIME in Content-Type:" + httpReqResult.getMimeType(),httpReqResult);
String itsNatServerVersion = httpReqResult.getItsNatServerVersion();
PageImpl page = itsNatServerVersion != null ? new PageItsNatImpl(this,pageRequestResult,itsNatServerVersion) : new PageNotItsNatImpl(this,pageRequestResult);
OnPageLoadListener pageListener = getOnPageLoadListener();
if (pageListener != null) pageListener.onPageLoad(page);
}
public PageRequestImpl clonePageRequest()
{
return new PageRequestImpl(this);
}
}