/** * License Agreement. * * Rich Faces - Natural Ajax for Java Server Faces (JSF) * * Copyright (C) 2007 Exadel, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.ajax4jsf.component; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.faces.component.UIComponent; import javax.faces.component.UIComponentBase; import javax.faces.component.UIForm; import javax.faces.component.UIViewRoot; import javax.faces.component.html.HtmlOutputText; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.render.Renderer; import org.richfaces.javascript.JSFunction; import org.richfaces.javascript.JSFunctionDefinition; import org.richfaces.javascript.JSReference; import org.ajax4jsf.renderkit.AjaxRendererUtils; import org.ajax4jsf.renderkit.UserResourceRenderer2; import org.ajax4jsf.renderkit.RendererUtils.HTML; import org.ajax4jsf.resource.InternetResourceBuilder; import org.ajax4jsf.resource.ResourceNotFoundException; import org.ajax4jsf.tests.AbstractAjax4JsfTestCase; import org.mozilla.javascript.FunctionObject; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.NativeObject; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Undefined; import com.gargoylesoftware.htmlunit.AlertHandler; import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.ScriptPreProcessor; import com.gargoylesoftware.htmlunit.ScriptResult; import com.gargoylesoftware.htmlunit.html.HtmlElement; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine; import com.sun.facelets.Facelet; import com.sun.facelets.FaceletFactory; import com.sun.facelets.compiler.Compiler; import com.sun.facelets.compiler.SAXCompiler; import com.sun.facelets.impl.DefaultFaceletFactory; import com.sun.facelets.impl.ResourceResolver; /** * @author Nick Belaevski * @since 3.3.0 */ public abstract class AbstractQueueComponentTest extends AbstractAjax4JsfTestCase { private static final String AJAX_SUBMIT = "ajaxSubmit"; private static final String COMPONENT_TYPE = AjaxSubmitFunctionComponent.class.getName(); public static final int DEFAULT_REQUEST_TIME = 1000; private static final String SIMULATION_SCRIPT_NAME = "org/ajax4jsf/component/simulation.js"; private static final Compiler compiler = new SAXCompiler(); private static final ScriptableObject systemOut = new ScriptableObject() { /** * */ private static final long serialVersionUID = -8574162538513136625L; @Override public String getClassName() { return "systemOut"; } @SuppressWarnings("unused") public void println(String s) { System.out.println(s); } }; static { try { systemOut.defineProperty("println", new FunctionObject(null, systemOut.getClass().getMethod("println", String.class), systemOut), ScriptableObject.READONLY); } catch (SecurityException e) { throw new IllegalStateException(e.getMessage(), e); } catch (NoSuchMethodException e) { throw new IllegalStateException(e.getMessage(), e); } } protected HtmlPage page; public AbstractQueueComponentTest(String name) { super(name); } @Override public void setUp() throws Exception { super.setUp(); InternetResourceBuilder resourceBuilder = InternetResourceBuilder.getInstance(); try { resourceBuilder.getResource(SIMULATION_SCRIPT_NAME); } catch (ResourceNotFoundException e) { resourceBuilder.createResource(null, SIMULATION_SCRIPT_NAME); } UIViewRoot viewRoot = facesContext.getViewRoot(); UIResource resource; UIComponent form = application.createComponent(UIForm.COMPONENT_TYPE); viewRoot.getChildren().add(form); final UIComponent commandButton = application.createComponent(UIAjaxCommandButton.COMPONENT_TYPE); form.getChildren().add(commandButton); facesContext.getRenderKit().addRenderer(COMPONENT_TYPE, COMPONENT_TYPE, new AjaxSubmitFunctionResourceRenderer()); form.getChildren().add(new AjaxSubmitFunctionComponent()); resource = (UIResource) application.createComponent("org.ajax4jsf.LoadScript"); resource.setSrc("resource:///" + SIMULATION_SCRIPT_NAME); viewRoot.getChildren().add(resource); } @Override public void tearDown() throws Exception { this.page = null; super.tearDown(); } protected ParametersBuilder createAjaxParameters() { return new ParametersBuilder(); } protected void checkRequestData(RequestData requestData, String data, double startTime, double endTime, boolean aborted) { assertEquals("Data check failed for " + requestData, data, requestData.getData()); assertEquals("Start time check failed for " + requestData, startTime, requestData.getStartTime()); assertEquals("End time check failed for " + requestData, endTime, requestData.getEndTime()); assertEquals("Aborted check failed for " + requestData, aborted, requestData.isAborted()); } /** * Execute simulated ajax request starting on given time and having data and paramaters passed as arguments. * For simulated requests defaut value of data is id of the element firing request and default request time * is 1000 * * @param time * @param data * @param builder */ protected void ajax(int time, String data, ParametersBuilder builder) { JSFunction function = new JSFunction("simulationContext.ajax", time, data, builder.getParameters()); page.executeJavaScript(function.toScript()); } protected void executeOnTime(int time, String expression) { JSFunction function = new JSFunction("simulationContext.executeOnTime", time, new JSFunctionDefinition().addToBody(expression)); page.executeJavaScript(function.toScript()); } private String buildClickExpression(String id) { return "document.getElementById('" + id + "').click()"; } protected void clickOnTime(int time, String id) { JSFunction function = new JSFunction("simulationContext.executeOnTime", time, new JSFunctionDefinition().addToBody(buildClickExpression(id))); page.executeJavaScript(function.toScript()); } protected String getRootContextPath() { return this.getClass().getPackage().getName().replace('.', '/'); } protected ResourceResolver createResourceResolver() { return new ResourceResolver() { public URL resolveUrl(String path) { return Thread.currentThread().getContextClassLoader().getResource(getRootContextPath() + path); } }; } protected void buildView(String viewId) throws IOException { FaceletFactory factory = new DefaultFaceletFactory(compiler, createResourceResolver()); FaceletFactory.setInstance(factory); FaceletFactory f = FaceletFactory.getInstance(); Facelet at = f.getFacelet(viewId); UIViewRoot root = facesContext.getViewRoot(); root.setViewId(viewId); at.apply(facesContext, root); } protected void preRenderView() throws Exception { StringBuilder builder = new StringBuilder("<script type='text/javascript'>"); builder.append("DEFAULT_REQUEST_TIME = " + DEFAULT_REQUEST_TIME + ";"); builder.append("window.simulationContext = new SimulationContext("); builder.append(AJAX_SUBMIT); builder.append(");</script>"); HtmlOutputText text = new HtmlOutputText(); text.setEscape(false); text.setValue(builder.toString()); List<UIComponent> childList = facesContext.getViewRoot().getChildren(); childList.add(childList.size(), text); } protected void postRenderView() throws Exception { ScriptableObject scriptableObject = (ScriptableObject) this.page.executeJavaScript("window.LOG").getJavaScriptResult(); scriptableObject.defineProperty("out", systemOut, ScriptableObject.READONLY); } protected HtmlPage renderView(String viewId) throws Exception { buildView(viewId); preRenderView(); this.page = super.renderView(); postRenderView(); return this.page; } @Override protected HtmlPage renderView() throws Exception { preRenderView(); this.page = super.renderView(); postRenderView(); return this.page; } protected void click(String id) { executeJavaScript(buildClickExpression(id)); } protected Object executeJavaScript(String expression) { return page.executeJavaScript(expression).getJavaScriptResult(); } protected void executeTimer() { page.executeJavaScript("Timer.execute();"); } protected TestsResult getTestsResult() { TestsResult result = new TestsResult(); executeTimer(); ScriptResult scriptResult = page.executeJavaScript("window.simulationContext.results"); NativeArray array = (NativeArray) scriptResult.getJavaScriptResult(); for (int i = 0; i < array.getLength(); i++) { NativeObject object = (NativeObject) array.get(i, array); String data = null; Object dataObject = object.get("data", object); if (!(dataObject instanceof Undefined)) { data = (String) dataObject; } Double startTime = (Double) object.get("startTime", object); Double endTime = (Double) object.get("endTime", object); Object aborted = object.get("aborted", object); boolean abortedBoolean = aborted instanceof Boolean && (Boolean) aborted; result.addData(new RequestData(data, startTime, endTime, abortedBoolean)); } scriptResult = page.executeJavaScript("Timer.currentTime"); result.setCurrentTime((Double) scriptResult.getJavaScriptResult()); return result; } @Override protected void setupWebClient() { super.setupWebClient(); webClient.setJavaScriptEngine(new JavaScriptEngine(webClient)); webClient.setScriptPreProcessor(new UnescapingScriptPreprocessor()); webClient.setThrowExceptionOnScriptError(true); webClient.setAlertHandler(new AlertHandler() { public void handleAlert(Page page, String message) { fail(message); } }); } public final static class AjaxSubmitFunctionComponent extends UIComponentBase { @Override public String getRendererType() { return COMPONENT_TYPE; } @Override public String getFamily() { return COMPONENT_TYPE; } } private final static class AjaxSubmitFunctionResourceRenderer extends Renderer implements UserResourceRenderer2 { public void encodeToHead(FacesContext facesContext, UIComponent component) throws IOException { JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(component, facesContext); Map<String, Object> options = AjaxRendererUtils.buildEventOptions(facesContext, component, true); options.put("requestDelay", new JSReference("options.requestDelay")); options.put("similarityGroupingId", new JSReference("options.similarityGroupingId || '" + component.getClientId(facesContext) + "'")); options.put("data", new JSReference("data")); options.put("requestTime", new JSReference("options.requestTime")); options.put("timeout", new JSReference("options.timeout")); options.put("eventsQueue", new JSReference("options.eventsQueue")); options.put("implicitEventsQueue", new JSReference("options.implicitEventsQueue")); options.put("ignoreDupResponses", new JSReference("options.ignoreDupResponses")); ajaxFunction.addParameter(options); ResponseWriter responseWriter = facesContext.getResponseWriter(); responseWriter.startElement(HTML.SCRIPT_ELEM, component); responseWriter.writeAttribute(HTML.TYPE_ATTR, "text/javascript", null); responseWriter.writeText("var " + AJAX_SUBMIT + " = function(data, options) {" + ajaxFunction.toScript() + "};", null); responseWriter.endElement(HTML.SCRIPT_ELEM); } } protected static final class ParametersBuilder { private Map<String, Object> parameters; private ParametersBuilder() { this.parameters = new HashMap<String, Object>(); } private ParametersBuilder(Map<String, Object> parameters) { this.parameters = new HashMap<String, Object>(parameters); } private ParametersBuilder put(String key, Object value) { this.parameters.put(key, value); return this; } protected Map<String, Object> getParameters() { return parameters; } /** * Sets value of requestDelay parameter * @param value * @return */ public ParametersBuilder requestDelay(double value) { return new ParametersBuilder(this.parameters).put("requestDelay", value); } /** * Sets value of similarityGroupingId parameter * @param id * @return */ public ParametersBuilder similarityGroupingId(Object id) { return new ParametersBuilder(this.parameters).put("similarityGroupingId", id); } /** * Defines how long this request will be executed on server * @param value * @return */ public ParametersBuilder requestTime(double value) { return new ParametersBuilder(this.parameters).put("requestTime", value); } /** * Sets value of timeout parameter * @param value * @return */ public ParametersBuilder timeout(double value) { return new ParametersBuilder(this.parameters).put("timeout", value); } /** * Sets value of eventsQueue parameter * @param name * @return */ public ParametersBuilder eventsQueue(String name) { return new ParametersBuilder(this.parameters).put("eventsQueue", name); } /** * Sets value of implicitEventsQueue parameter * @param name * @return */ public ParametersBuilder implicitEventsQueue(String name) { return new ParametersBuilder(this.parameters).put("implicitEventsQueue", name); } /** * Sets value of ignoreDupResponses parameter * @param value * @return */ public ParametersBuilder ignoreDupResponses(boolean value) { return new ParametersBuilder(this.parameters).put("ignoreDupResponses", value); } } protected static final class RequestData { private boolean aborted; private String data; private double endTime; private double startTime; public RequestData(String data, double startTime, double endTime, boolean aborted) { super(); this.data = data; this.startTime = startTime; this.endTime = endTime; this.aborted = aborted; } public boolean isAborted() { return aborted; } public double getStartTime() { return startTime; } public double getEndTime() { return endTime; } public String getData() { return data; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("data: "); builder.append(data); builder.append(", "); builder.append("startTime: "); builder.append(startTime); builder.append(", "); builder.append("endTime: "); builder.append(endTime); if (isAborted()) { builder.append(", aborted: "); builder.append(true); } return builder.toString(); } } protected static final class TestsResult { private List<RequestData> dataList = new ArrayList<RequestData>(); private double currentTime; public void addData(RequestData data) { this.dataList.add(data); } public List<RequestData> getDataList() { return dataList; } public void setCurrentTime(double number) { this.currentTime = number; } public double getCurrentTime() { return currentTime; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("[\n"); for (RequestData data : dataList) { builder.append(" "); builder.append(data); builder.append("\n"); } builder.append("]\n"); builder.append("Current time: " + this.currentTime); return builder.toString(); } } ; } class UnescapingScriptPreprocessor implements ScriptPreProcessor { private static final Map<String, String> ENTITIES_MAP = new HashMap<String, String>(); private static final Pattern ENTITIES_PATTERN; static { ENTITIES_MAP.put("'", "\""); ENTITIES_MAP.put(""", "'"); ENTITIES_MAP.put("&", "&"); ENTITIES_MAP.put("<", "<"); ENTITIES_MAP.put(">", ">"); } static { StringBuilder sb = new StringBuilder(); for (String entity : ENTITIES_MAP.keySet()) { if (sb.length() != 0) { sb.append('|'); } sb.append(Pattern.quote(entity)); } ENTITIES_PATTERN = Pattern.compile("(" + sb.toString() + ")"); } public String preProcess(HtmlPage htmlPage, String sourceCode, String sourceName, HtmlElement htmlElement) { if (sourceName != null && !sourceName.startsWith("http:/")) { Matcher m = ENTITIES_PATTERN.matcher(sourceCode); StringBuffer sb = new StringBuffer(); while (m.find()) { String entity = m.group(1); m.appendReplacement(sb, ENTITIES_MAP.get(entity)); } m.appendTail(sb); return sb.toString(); } else { return sourceCode; } } }