View Javadoc

1   package com.jbergin;
2   
3   import java.io.IOException;
4   import java.net.MalformedURLException;
5   import java.net.URL;
6   import java.util.Collections;
7   import java.util.Date;
8   import java.util.Iterator;
9   import java.util.List;
10  import java.util.NoSuchElementException;
11  import java.util.regex.Pattern;
12  
13  import org.apache.commons.httpclient.Cookie;
14  import org.apache.commons.httpclient.HttpState;
15  import org.apache.commons.logging.Log;
16  import org.apache.commons.logging.LogFactory;
17  import org.mozilla.javascript.Function;
18  import org.mozilla.javascript.Scriptable;
19  import org.mozilla.javascript.WrappedException;
20  
21  import com.gargoylesoftware.htmlunit.AlertHandler;
22  import com.gargoylesoftware.htmlunit.ConfirmHandler;
23  import com.gargoylesoftware.htmlunit.Page;
24  import com.gargoylesoftware.htmlunit.RefreshHandler;
25  import com.gargoylesoftware.htmlunit.ScriptException;
26  import com.gargoylesoftware.htmlunit.ScriptResult;
27  import com.gargoylesoftware.htmlunit.WebClient;
28  import com.gargoylesoftware.htmlunit.WebWindow;
29  import com.gargoylesoftware.htmlunit.WebWindowEvent;
30  import com.gargoylesoftware.htmlunit.WebWindowListener;
31  import com.gargoylesoftware.htmlunit.html.ClickableElement;
32  import com.gargoylesoftware.htmlunit.html.DomNode;
33  import com.gargoylesoftware.htmlunit.html.DomText;
34  import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
35  import com.gargoylesoftware.htmlunit.html.HtmlDivision;
36  import com.gargoylesoftware.htmlunit.html.HtmlElement;
37  import com.gargoylesoftware.htmlunit.html.HtmlForm;
38  import com.gargoylesoftware.htmlunit.html.HtmlInput;
39  import com.gargoylesoftware.htmlunit.html.HtmlOption;
40  import com.gargoylesoftware.htmlunit.html.HtmlPage;
41  import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput;
42  import com.gargoylesoftware.htmlunit.html.HtmlSelect;
43  import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
44  import com.gargoylesoftware.htmlunit.javascript.host.Event;
45  
46  
47  
48  class ElementFactory 
49  {
50    final Log logger = LogFactory.getLog(this.getClass());
51    final WebClient webClient;
52    private boolean jsExecuting = false;
53    
54    ElementFactory(WebClient client) {
55      this.webClient = client;
56      webClient.setPageCreator(new LocalPageCreator());
57      webClient.setRedirectEnabled(true);
58      webClient.setAlertHandler(alertHandler);
59      webClient.setConfirmHandler(confirmHandler);
60      webClient.setRefreshHandler(refreshHandler);
61      webClient.addWebWindowListener(windowListener);
62      webClient.setThrowExceptionOnFailingStatusCode(false);
63    }
64  
65    private static HtmlElement  focusElement;
66    private String       htmlSource;
67  
68    HtmlElement getFocusElement() {
69      return focusElement;
70    }
71    void setFocusElement(HtmlElement e) {
72    	if (e != null) {
73    		logger.debug("Set focus element to " + e.asText());
74    	} else {
75    		logger.debug("Set focus to null");
76    	}
77      focusElement = e;
78    }
79    String getHtmlSource() {
80      return htmlSource;
81    }
82    void setHtmlSource(String s) {
83      this.htmlSource = s;
84    }
85    
86    private WebClient getWebClient() {
87      return this.webClient;
88    }
89  
90    public void setJavaScriptEnabled(boolean b) {
91      getWebClient().setJavaScriptEnabled(b);
92    }
93    public boolean isJavaScriptEnabled() {
94      return getWebClient().isJavaScriptEnabled();
95    }
96  
97    HtmlPage getHtmlPage() {
98      return getFocusElement().getPage();
99    }
100   URL getUrl() {
101     return getHtmlPage().getWebResponse().getUrl();
102   }
103 
104   public static boolean isUrlValid(String url) {
105     try {
106       new URL(url);
107       return true;
108     } catch (MalformedURLException e) {
109       return false;
110     }
111   }
112 
113   public void clickFocusElement() throws IOException {
114     HtmlElement focusElement = getFocusElement();
115     
116     HtmlPage page = null;
117     
118     if (focusElement instanceof HtmlForm) {
119     	HtmlForm form = (HtmlForm) focusElement;
120     	page = (HtmlPage)form.submit();
121     	setFocusElement(page.getDocumentElement());
122     } else if (focusElement instanceof ClickableElement) {
123       final ClickableElement clickable = (ClickableElement) focusElement;
124       
125       page = (HtmlPage)clickable.click();
126       
127       final HtmlPage oldPage = clickable.getPage();
128       
129       if (oldPage != page) {
130       	setFocusElement(page.getDocumentElement());
131       }
132     } else {
133       throw new IllegalArgumentException("Current element is not clickable.");
134     }	
135   }
136 
137 
138   final private DomNodeMemory iMemory = new DomNodeMemory();
139   
140   public void clear() {
141     iMemory.clear();
142   }
143   public void store(String key, Object value) {
144     iMemory.store(key, value);
145   }
146   public DomNode retrieve(String key) {
147     return retrieve(key, false);
148   }
149   public DomNode retrieve(String key, boolean throwOnNull) {
150   	if ("$currentPage$".equals(key)) {
151   		final DomNode node = getFocusElement();
152   		if (node == null) {
153   			throw new IllegalStateException("no focus element");
154   		}
155   		return node.getPage();
156   	}
157     final DomNode node = iMemory.retrieve(key);
158     if (node != null || !throwOnNull) return node;
159     throw new NoSuchElementException();
160   }
161   public Object remove(Object key) {
162   	if ("alerts".equals(key)) {
163   		HtmlDivision alerts = getAlerts();
164   		if (alerts != null) {
165   			alerts.removeAllChildren();
166   		}
167   		return alerts;
168   	} 
169   	return iMemory.remove(key);
170   }
171   int storageSize() {
172     return iMemory.size();
173   }
174   boolean memoryContains(String key) {
175     return iMemory.containsKey(key);
176   }
177   
178   private HtmlElement loadElementFromUrlOrMemory(String sourceNameOrUrl) throws MalformedURLException, IOException {
179     if (!isUrlValid(sourceNameOrUrl))
180       return (HtmlElement) retrieve(sourceNameOrUrl, false);
181 
182     return loadElementFromUrl(new URL(sourceNameOrUrl));
183   }
184   private HtmlElement loadElementFromUrl(URL url) throws IOException {
185     Page aPage = getWebClient().getPage(url);
186     if (aPage instanceof HtmlPage) {
187       return ((HtmlPage) aPage).getDocumentElement();
188     }
189     throw new IllegalStateException("Document isn't an HtmlPage: " + url);
190   }
191   
192   public void loadElementRelative(String urlString) throws IOException {
193     URL url = getFocusElement().getPage().getFullyQualifiedUrl(urlString);
194     HtmlElement oldElement = getFocusElement();
195     setHtmlSource(urlString);
196     setFocusElement(null);
197     setFocusElement(loadElementFromUrl(url));
198     if (getFocusElement() == null) {
199       setFocusElement(oldElement);
200       throw new NoSuchElementException(url.toString());
201     }
202   }
203   
204   public void loadElementSafely(String sourceNameOrUrl) throws NoSuchElementException, IOException, IOException {
205     HtmlElement oldElement = getFocusElement();
206     setHtmlSource(sourceNameOrUrl);
207     setFocusElement(loadElementFromUrlOrMemory(sourceNameOrUrl));
208     if (getFocusElement() == null) {
209       setFocusElement(oldElement);
210       throw new NoSuchElementException("Element is not in storage: ");
211     }
212   }
213 
214   public void setInputFieldValue(String textValue) {
215     String tagName = getFocusElement().getNodeName().toLowerCase();
216     if ("input".equals(tagName))
217       this.setInputValue(textValue);
218     else if ("textarea".equals(tagName))
219       ((HtmlTextArea) getFocusElement()).setText(textValue);
220     else if ("select".equals(tagName))
221       ((HtmlSelect) getFocusElement()).setSelectedAttribute(textValue, true);
222     else
223       throw new UnsupportedOperationException(tagName + " is not currently a supported input type");
224   }
225 
226   // Set the value for an HTML <input> tag.
227   private void setInputValue(String textValue) {
228     String inputType = getFocusElement().getAttributeValue("type");
229     String message = inputType + " is not currently a supported input type";
230     if (inputType != null) {
231       inputType = inputType.toLowerCase();
232       if (inputType.equals("") || inputType.equals("text") || inputType.equals("password"))
233         ((HtmlInput) getFocusElement()).setValueAttribute(textValue);
234       else if (inputType.equals("checkbox")) {
235         if (textValue.toLowerCase().equals("checked"))
236           ((HtmlCheckBoxInput) getFocusElement()).setChecked(true);
237         else
238           ((HtmlCheckBoxInput) getFocusElement()).setChecked(false);
239       } else if (inputType.equals("radio")) {
240         if (textValue.toLowerCase().equals("checked"))
241           ((HtmlRadioButtonInput) getFocusElement()).setChecked(true);
242       } else
243         throw new UnsupportedOperationException(message);
244     } else
245       throw new UnsupportedOperationException(message);
246   }
247   
248   /***
249    * @param deselectOthers
250    * @return number of options matched.  if the select box is not a multi and
251    * more than one match is found, only the last option matched will be selected.
252    */
253   public int selectOptions(String textValue, boolean deselectOthers) {
254 	HtmlSelect select = (HtmlSelect) getFocusElement();
255 
256     List options = select.getAllOptions();
257 
258     Pattern pattern = Pattern.compile(textValue);
259 
260     final boolean prevJsExecuting = jsExecuting;
261     
262     logger.debug("begin select command");
263     
264     jsExecuting = true;
265     
266     int found = 0;
267     for (Iterator itr = options.iterator(); itr.hasNext();) {
268       HtmlOption option = (HtmlOption) itr.next();
269       if (pattern.matcher(option.asText()).matches()) {
270         select.setSelectedAttribute(option, true);
271         found++;
272       } else if (deselectOthers) {
273       	select.setSelectedAttribute(option, false);
274       }
275     }
276     
277     jsExecuting = prevJsExecuting;
278     logger.debug("select command complete");
279     logger.debug("focus element: " + getFocusElement().asText());
280     return found;
281   }
282   
283   public void expireAllCookies(HtmlPage page) {
284     URL url = page.getWebResponse().getUrl();
285     HttpState state = page.getWebClient().getWebConnection().getStateForUrl(url);
286 
287     Date ancient = new Date(0);
288 
289     Cookie[] cookies = state.getCookies();
290     for (int i = 0; i < cookies.length; i++) {
291       cookies[i].setExpiryDate(ancient);
292     }
293     state.purgeExpiredCookies();
294   }
295 
296   public Object executeEventHandler(String actionToRun) {
297     Function functionToRun = getFocusElement().getEventHandler(actionToRun);
298     
299     if (functionToRun == null) {
300       throw new IllegalArgumentException(actionToRun + " event handler is not defined");
301     }
302     
303     boolean jsEnable = this.isJavaScriptEnabled();
304     try {
305       jsExecuting = true;
306       setJavaScriptEnabled(true);
307 
308       Event event = new Event(getFocusElement(), getFocusElement().getScriptObject());
309       Object[] args = new Object[] { event };
310       ScriptResult result = getHtmlPage().executeJavaScriptFunctionIfPossible(functionToRun, (Scriptable)getFocusElement().getScriptObject(), args, getFocusElement());
311       return result.getJavaScriptResult();
312     } catch (ScriptException e) {
313         try {
314         	throw e.getEnclosedException();
315         } catch(WrappedException we) {
316         	Throwable t = we.getCause();
317         	if (t instanceof RuntimeException) {
318         		throw (RuntimeException) t;
319         	}
320         	throw new RuntimeException(t);
321         } catch (RuntimeException re) {
322         	throw re;
323         } catch (Throwable ex) {
324         	throw new RuntimeException(ex);
325         }
326     } finally {
327       setJavaScriptEnabled(jsEnable);
328       jsExecuting = false;
329     }
330   }
331   
332   public Object evaluateExpression(String expression) {
333     return getHtmlPage().executeJavaScriptIfPossible(expression, "HtmlFixture expression", false, getFocusElement());
334   }
335 
336   HtmlDivision getAlerts() {
337   	return (HtmlDivision) retrieve("alerts", false);
338   }
339   HtmlDivision getAlerts(Page page) {
340   	HtmlDivision alerts = getAlerts();
341     if (alerts == null) {
342       alerts = new HtmlDivision((HtmlPage) page, Collections.EMPTY_MAP);
343       store("alerts", alerts);
344     }
345     return alerts;
346   }
347 
348   AlertHandler alertHandler = new AlertHandler() {
349                               public void handleAlert(Page page, String message) {
350                                 getAlerts(page).appendChild(new DomText((HtmlPage) page, message));
351                               }
352                             };
353 
354   
355   final ConfirmHandler confirmHandler = new ConfirmHandler() {
356   	public boolean handleConfirm(Page page, String message) {
357   	  return iMemory.getNextAnswer(message);
358 	}
359   };
360   
361   /***
362    * Always returns false, to keep the URL from being fetched infinitely recursively.
363    */
364   RefreshHandler refreshHandler = new RefreshHandler() {
365     public boolean shouldRefresh(Page page, URL url, int timeBeforeRefresh) {
366       return false;
367     }
368   };
369   
370   WebWindowListener windowListener = new WebWindowListener() 
371   {
372   	
373     private void reportEvent(WebWindowEvent event) {
374       StringBuffer buffer = new StringBuffer("WebWindowEvent:\n");
375       reportNVP("Type", getEventType(event), buffer);
376       reportNVP("Source", event.getSource().toString(), buffer);
377       reportNVP("WindowName", event.getWebWindow().getName(), buffer);
378       reportNVP("OldPage", getPageDescription((HtmlPage) event.getOldPage()), buffer);
379       reportNVP("NewPage", getPageDescription((HtmlPage) event.getNewPage()), buffer);
380       
381 	  logger.debug(buffer.toString());
382     }
383     void reportNVP(String name, String value, StringBuffer buffer) {
384       buffer.append(name + ":\t" + value + "\n");
385     }
386 
387     String getPageDescription(HtmlPage page) {
388       if (page == null)
389         return "Null page";
390       
391       try {
392       	return page.hashCode() + " " + page.getTitleText() + " (" + page.getFullyQualifiedUrl("") + ")";
393       } catch (MalformedURLException e) {
394       	throw new RuntimeException(e);
395       }
396     }
397 
398     String getEventType(WebWindowEvent event) {
399       switch (event.getEventType()) {
400       	case WebWindowEvent.CHANGE:	return "CHANGE"; 
401       	case WebWindowEvent.OPEN:		return "OPEN"; 
402       	case WebWindowEvent.CLOSE:	return "CLOSE"; 
403       	default:	
404       	  return Integer.toString(event.getEventType()); 
405       }
406     }
407 
408     public void webWindowOpened( WebWindowEvent event ) 
409     {
410       reportEvent(event);
411     }
412     
413     public void webWindowContentChanged( WebWindowEvent event ) 
414     {
415       reportEvent(event);
416       
417       iMemory.purgeElementsInPage(event.getOldPage());
418       
419       final WebWindow webWindow = event.getWebWindow();
420        
421       final HtmlPage newPage = (HtmlPage) event.getNewPage();
422       
423       String windowName = webWindow.getName();
424 
425       if (windowName.length() > 0) {
426 	      store(windowName, newPage.getDocumentElement() );
427       }
428       
429 	  final String titleText = newPage.getTitleText();
430 	  if (titleText.length() > 0) {
431 	  	final HtmlElement documentElement = newPage.getDocumentElement();
432         store( titleText,	documentElement );
433 	  }
434   	  logger.debug("Focus on new page with title '" + newPage.getTitleText() + "'");
435    	  setFocusElement(newPage.getDocumentElement());
436     }    
437     public void webWindowClosed( WebWindowEvent event ) 
438     {
439       reportEvent(event);
440       iMemory.purgeElementsInPage(event.getOldPage());
441     }
442   };
443 
444   public void addAnswer(String message, boolean answer) {
445   	this.iMemory.addAnswer(message, answer);	
446   }
447   public void setPreserve(boolean preserve) {
448   	this.iMemory.setPreserve(preserve);
449   }
450 }