1 package eteg.sinon.executor;
2
3 import eteg.sinon.core.Action;
4 import eteg.sinon.core.Catalog;
5 import eteg.sinon.core.Collector;
6 import eteg.sinon.core.CollectorConfiguration;
7 import eteg.sinon.core.DataExtraction;
8 import eteg.sinon.core.LoopStep;
9 import eteg.sinon.core.Page;
10 import eteg.sinon.core.PageConfiguration;
11 import eteg.sinon.core.Parameter;
12 import eteg.sinon.core.ParameterSet;
13 import eteg.sinon.core.PositioningStep;
14 import eteg.sinon.core.StartPage;
15 import eteg.sinon.core.Step;
16 import eteg.sinon.exception.EvaluationException;
17 import eteg.sinon.exception.SinonException;
18 import eteg.sinon.exception.StepAfterLoopLimitPositionException;
19 import eteg.sinon.exception.StepException;
20 import eteg.sinon.exception.StepWithBeforeException;
21 import eteg.sinon.exception.StepWithFailIfFoundException;
22 import eteg.sinon.exception.UnknownPageException;
23 import eteg.sinon.listener.CatalogStateListener;
24 import eteg.sinon.listener.ErrorListener;
25 import eteg.sinon.listener.PageStateListener;
26 import org.apache.commons.httpclient.Cookie;
27 import org.apache.commons.httpclient.HttpClient;
28 import org.apache.commons.httpclient.HttpMethod;
29 import org.apache.commons.httpclient.URI;
30 import org.apache.commons.httpclient.methods.GetMethod;
31 import org.apache.commons.httpclient.methods.PostMethod;
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.apache.log4j.PropertyConfigurator;
35 import org.apache.velocity.VelocityContext;
36 import org.apache.velocity.app.VelocityEngine;
37
38 import java.io.BufferedReader;
39 import java.io.File;
40 import java.io.FileNotFoundException;
41 import java.io.FileReader;
42 import java.io.FileWriter;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.StringWriter;
46 import java.io.UnsupportedEncodingException;
47 import java.io.FileOutputStream;
48 import java.io.BufferedOutputStream;
49 import java.lang.reflect.Method;
50 import java.net.MalformedURLException;
51 import java.net.URLEncoder;
52 import java.net.UnknownHostException;
53 import java.text.DecimalFormat;
54 import java.util.ArrayList;
55 import java.util.Date;
56 import java.util.Enumeration;
57 import java.util.HashMap;
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.Properties;
62
63 /***
64 * Class that executes a {@link eteg.sinon.core.Collector} instance.
65 *
66 * @author <a href="mailto:thiagohp at users.sourceforge.net">Thiago H. de Paula
67 * Figueiredo </a>
68 * @author Last modified by $Author: thiagohp $
69 * @version $Revision: 1.7 $
70 */
71 public class CollectorExecutor {
72
73 final private static String HTTP_PREFIX = "http://";
74
75 final private static String HTTPS_PREFIX = "https://";
76
77 final private static String VELOCITY_PROPERTIES_PATH =
78 "/velocity.properties";
79
80 final private static String COLLECTOR_PROPERTY = "collector";
81
82 final private static PageState BEFORE_DOWNLOAD =
83 PageState.BEFORE_DOWNLOAD;
84
85 final private static PageState BEFORE_PROCESSING =
86 PageState.BEFORE_PROCESSING;
87
88 final private static PageState AFTER_PROCESSING =
89 PageState.AFTER_PROCESSING;
90
91 final private static PageState FINISHED =
92 PageState.FINISHED;
93
94 final private static String LOG4J_PROPERTIES_FILENAME = "log4j.properties";
95
96 final private static String TEMPLATE_SUFFIX = "}";
97
98 final private static String TEMPLATE_PREFIX = "${";
99
100 /***
101 * Suffix used in URLs that point to local files.
102 */
103 final private static String FILE_PROTOCOL = "file:/";
104
105 /***
106 * Current directory.
107 */
108 final private static File CURRENT_DIRECTORY = new File(".");
109
110 /***
111 * Codificação padrão utilizada para se fazer <i>URL enconding </i> de
112 * parâmetros HTTP.
113 */
114 final private static String DEFAULT_URL_ENCODING = "ISO_8859_1";
115
116 /***
117 * Sinon log filename.
118 */
119 final public static String LOG_FILENAME = "sinon.log";
120
121 /***
122 * Velocity context.
123 */
124 private VelocityContext context;
125
126 /***
127 * Velocity engine.
128 */
129 private VelocityEngine engine;
130
131 /***
132 * Collector being executed.
133 */
134 private Collector collector;
135
136 /***
137 * Think time instance used by this execution.
138 */
139 private ThinkTime thinkTime;
140
141 /***
142 * HTTP headers.
143 */
144 private Properties httpHeaders;
145
146 /***
147 * List containing the names of all properties set.
148 */
149 private ArrayList propertyNames;
150
151 /***
152 * Hash table that maps multivalued properties (lists) to their
153 * values list.
154 */
155 private HashMap multivaluedProperties;
156
157 /***
158 * Logger.
159 */
160 private Log log;
161
162 /***
163 * Catalog instance being executed now.
164 */
165 private Catalog currentCatalog;
166
167 /***
168 * Page instance being executed now.
169 */
170 private Page currentPage;
171
172 /***
173 * Data extraction instance being executed now.
174 */
175 private DataExtraction currentDataExtraction;
176
177 /***
178 * Execution step instance being executed now.
179 */
180 private Step currentStep;
181
182 /***
183 * Current action text.
184 */
185 private String currentText;
186
187 /***
188 * Current document bytes.
189 */
190 private byte[] currentBytes;
191
192 /***
193 * Current position inside the text of the current action.
194 */
195 private int currentPosition;
196
197 /***
198 * Position at which the desired information starts.
199 */
200 private int from;
201
202 /***
203 * Position at which the desired information ends.
204 */
205 private int to;
206
207 /***
208 * Number of downloaded pages..
209 */
210 private int pagesDownloaded;
211
212 /***
213 * Number of transferred bytes.
214 */
215 private int bytesDownloaded;
216
217 /***
218 * {@link ErrorListener} of the current action.
219 */
220 private ErrorListener currentErrorListener;
221
222 /***
223 * {@link PageStateListener} of the current action.
224 */
225 private PageStateListener currentPageStateListener;
226
227 /***
228 * <code>org.apache.commons.httpclient.HttpClient</code> instance.
229 */
230 private HttpClient httpClient;
231
232 /***
233 * Action queue.
234 */
235 private ArrayList actionQueue;
236
237 /***
238 * Current catalog state change listener.
239 */
240 private CatalogStateListener catalogStateListener;
241
242 /***
243 * Execution start Unix timestamp.
244 */
245 private long start;
246
247 /***
248 * Execution finish Unix timestamp.
249 */
250 private long finish;
251
252 /***
253 * Flag used to stop an execution.
254 */
255 private boolean mustStop = false;
256
257 /***
258 * Single constructor of this class.
259 *
260 * @param collector a {@link Collector} instance.
261 * @throws IllegalArgumentException if <code>collector</code> is
262 * <code>null</code>.
263 */
264 public CollectorExecutor(Collector collector) {
265
266 if (collector == null) {
267
268 throw new IllegalArgumentException(
269 "Parameter collector can't be null");
270
271 }
272
273 this.collector = collector;
274
275 propertyNames = new ArrayList();
276 pagesDownloaded = 0;
277 bytesDownloaded = 0;
278 currentCatalog = null;
279 currentPage = null;
280 currentDataExtraction = null;
281 currentStep = null;
282 currentPosition = -1;
283 currentText = null;
284 actionQueue = new ArrayList();
285
286 configureLogger();
287
288 }
289
290 /***
291 * Executes all catalogs of this collector in sequence.
292 *
293 * @throws SinonException if some error ocurrs.
294 */
295 public void execute() throws SinonException {
296 execute(collector.getCatalogs());
297 }
298
299 /***
300 * Executes a list of catalogs of the coletor.
301 *
302 * @param catalogs a {@link Catalog} array..
303 * @throws SinonException if some unhandled error ocurrs.
304 * @todo i18n
305 */
306 public void execute(Catalog[] catalogs) throws SinonException {
307
308 start = System.currentTimeMillis();
309 finish = -1;
310
311 if (log.isInfoEnabled()) {
312 log.info("Starting execution of collector " + collector.getId());
313 }
314
315 try {
316
317 Catalog[] currentCatalogs = collector.getCatalogs();
318
319 for (int i = 0; i < catalogs.length; i++) {
320
321 boolean ok = false;
322
323 for (int j = 0; j < currentCatalogs.length; j++) {
324
325 if (catalogs[i] == null) {
326
327 throw new IllegalArgumentException(
328 "No member of the catalogs vector can be null");
329
330 }
331
332 if (catalogs[i] == currentCatalogs[j]) {
333
334 ok = true;
335 break;
336
337 }
338
339 }
340
341 if (ok == false) {
342
343 throw new SinonException("Catalog "
344 + catalogs[i].getId()
345 + " does not belong to this collector.", this);
346
347 }
348
349 }
350
351 engine = new VelocityEngine();
352 Properties props = new Properties();
353
354 try {
355
356 props.load(this.getClass().getResourceAsStream(
357 VELOCITY_PROPERTIES_PATH));
358 } catch (RuntimeException e1) {
359 throw new SinonException("velocity.properties não found.");
360 }
361
362 try {
363
364 engine.init(props);
365
366 } catch (Exception e) {
367
368 final String message = "Error during Velocity initialization";
369
370 if (log.isFatalEnabled()) {
371 log.fatal(message, e);
372 }
373
374 throw new SinonException(message, e, this);
375
376 }
377
378 loadCatalogStateListener();
379
380 } catch (Exception e) {
381
382 if (log.isFatalEnabled()) {
383
384 log.fatal("Fatal error initializing collector "
385 + collector.getId(), e);
386 return;
387
388 }
389
390 }
391
392 try {
393
394 for (int i = 0; i < catalogs.length; i++) {
395
396 setup(catalogs[i]);
397
398 if (catalogStateListener != null) {
399 catalogStateListener.onStateChange(catalogs[i],
400 CatalogState.BEFORE_PROCESSING, this);
401
402 }
403
404 executeCatalog(catalogs[i]);
405
406 if (catalogStateListener != null) {
407
408 catalogStateListener.onStateChange(catalogs[i],
409 CatalogState.AFTER_PROCESSING, this);
410
411 }
412
413 }
414
415 } catch (SinonException e) {
416
417 finish = System.currentTimeMillis();
418
419 if (log.isFatalEnabled()) {
420
421 log.fatal("");
422 log.fatal("Execution state: ");
423 log.fatal(getExecutionInfo());
424 log.fatal("");
425 log.fatal("Properties values: ");
426 log.fatal(getPropertyInfo());
427 log.fatal("Fatal error: ", e);
428
429 }
430
431 throw e;
432
433 }
434
435 finish = System.currentTimeMillis();
436
437 if (log.isInfoEnabled()) {
438
439 log.info("Execution finished.");
440 log.info("Information:");
441 log.info(getExecutionInfo());
442
443 }
444
445 }
446
447 /***
448 * Adds all properties of a given <code>catalog</code> to the
449 * Velocity context.
450 *
451 * @param catalog a {@link Catalog} instance.
452 */
453 private void addCatalogProperties(final Catalog catalog)
454 throws SinonException {
455
456 Properties properties = catalog.getProperties();
457
458 Enumeration keys = properties.keys();
459
460 while (keys.hasMoreElements()) {
461
462 String name = (String) keys.nextElement();
463 String value = properties.getProperty(name);
464 setProperty(name, value);
465
466 }
467
468 }
469
470 /***
471 * Loads this collector's {@link CatalogStateListener} if it exists.
472 *
473 * @throws SinonException if some error ocurrs.
474 */
475 private void loadCatalogStateListener() throws SinonException {
476
477 String className = collector.getCatalogStateListenerClassName();
478
479 if (className != null) {
480
481 Object instance = invokeGetInstanceMethod(className);
482
483 try {
484
485 catalogStateListener = (CatalogStateListener) instance;
486
487 } catch (ClassCastException e) {
488
489 final String message = "Object returned by static method " +
490 "getInstance() of class" + className
491 + " is not a CatalogStateListener instance.";
492
493 throw new SinonException(message, e, this);
494
495 }
496
497 } else {
498 catalogStateListener = null;
499 }
500
501 }
502
503 /***
504 * Sets up this {@link CollectorExecutor} for the execution of
505 * <code>catalog</code>
506 *
507 * @param catalog a {@link Catalog} instance.
508 * @throws SinonException if some error ocurrs.
509 * @todo i18n
510 */
511 private void setup(Catalog catalog) throws SinonException {
512
513 if (log.isDebugEnabled()) {
514 log.debug("Initializing the context of this execution.");
515 }
516
517 context = new VelocityContext();
518 context.put(COLLECTOR_PROPERTY, this);
519
520 httpClient = new HttpClient();
521 httpClient.setConnectionTimeout(
522 collector.getConfiguration().getTimeout());
523 httpClient.setTimeout(collector.getConfiguration().getTimeout());
524
525 context = new VelocityContext();
526
527 configureThinkTime();
528
529 httpHeaders = collector.getConfiguration().getHttpHeaders();
530
531 propertyNames = new ArrayList();
532 multivaluedProperties = new HashMap();
533
534 addCatalogProperties(catalog);
535
536 }
537
538 /***
539 * Retuns the {@link Collector} to be executed by this executor.
540 *
541 * @return a {@link Collector} instance.
542 */
543 public Collector getCollector() {
544 return collector;
545 }
546
547 /***
548 * Returns the {@link Catalog} being executed.
549 * @return a {@link Catalog} instance.
550 */
551 public Catalog getCurrentCatalog() {
552 return currentCatalog;
553 }
554
555 /***
556 * Returns the {@link Page} being executed.
557 * @return a {@link Page} instance.
558 */
559 public Page getCurrentPage() {
560 return currentPage;
561 }
562
563 /***
564 * Returns the {@link DataExtraction} being executed.
565 * @return a {@link DataExtraction} instance.
566 */
567 public DataExtraction getCurrentDataExtraction() {
568 return currentDataExtraction;
569 }
570
571 /***
572 * Returns the {@link Step} being executed.
573 * @return a {@link Step} instance.
574 */
575 public Step getCurrentStep() {
576 return currentStep;
577 }
578
579 /***
580 * Retorns a <code>String</code> array containing the name of all
581 * properties set in this executor.
582 *
583 * @return a <code>String[]</code>.
584 */
585 public String[] getPropertyNames() {
586
587 String[] propertyArray = new String[propertyNames.size()];
588
589 propertyNames.toArray(propertyArray);
590
591 return propertyArray;
592
593 }
594
595 /***
596 * Retorns the last downloaded page text.
597 *
598 * @return a <code>String</code>.
599 */
600 public String getCurrentText() {
601 return currentText;
602 }
603
604 /***
605 * Retorns the last downloaded document as a byte array.
606 *
607 * @return a <code>byte[]</code>.
608 */
609 public byte[] getCurrentBytes() {
610 return currentBytes;
611 }
612
613 /***
614 * Retorns the current position in the current page.
615 *
616 * @return an <code>int</code>.
617 */
618 public int getCurrentPosition() {
619 return currentPosition;
620 }
621
622 /***
623 * Returns the number of pages already downloaded.
624 *
625 * @return an <code>int</code>.
626 */
627 public int getPagesDownloaded() {
628 return pagesDownloaded;
629 }
630
631 /***
632 * Return the number of bytes already downloaded.
633 *
634 * @return an <code>int</code>.
635 */
636 public int getBytesDownloaded() {
637 return bytesDownloaded;
638 }
639
640 /***
641 * Retorns the moment this executor started in Unix timestamp format.
642 * Returns -1 if this executor has not started yet.
643 *
644 * @return a <code>long</code>.
645 */
646 public long getStartTime() {
647 return start;
648 }
649
650 /***
651 * Retorns the moment this executor finished in Unix timestamp format.
652 * Returns -1 if this executor has not finished yet.
653 *
654 * @return a <code>long</code>.
655 */
656 public long getFinishTime() {
657 return finish;
658 }
659
660 /***
661 * Returns a <code>String</code> containing some execution information.
662 *
663 * @return a <code>String</code>.
664 */
665 public String getExecutionInfo() {
666
667 StringBuffer buffer = new StringBuffer();
668
669 buffer.append("Collector : " + collector.getId());
670 buffer.append("\n");
671
672 buffer.append("Catalog : ");
673 if (currentCatalog == null) {
674 buffer.append("none");
675 } else {
676 buffer.append(currentCatalog.getId());
677 }
678 buffer.append("\n");
679
680 buffer.append("Page : ");
681 if (currentPage == null) {
682 buffer.append("none");
683 } else {
684 buffer.append(currentPage.getId());
685 }
686 buffer.append("\n");
687
688 buffer.append("Data extraction : ");
689 if (currentDataExtraction == null) {
690 buffer.append("none");
691 } else {
692 buffer.append(currentDataExtraction.getId());
693 }
694 buffer.append("\n");
695
696 buffer.append("Step type : ");
697 if (currentStep == null) {
698 buffer.append("none");
699 } else {
700 buffer.append(currentStep.getClass().getName());
701 }
702 buffer.append("\n");
703
704 buffer.append("Page downloads : ");
705 buffer.append(getPagesDownloaded());
706 buffer.append("\n");
707
708 buffer.append("Bytes downloaded: ");
709 buffer.append(getBytesDownloaded());
710 buffer.append("\n");
711
712 buffer.append("Start time : ");
713 buffer.append(new Date(getStartTime()));
714 buffer.append("\n");
715
716 buffer.append("Finish time : ");
717 buffer.append(new Date(getFinishTime()));
718 buffer.append("\n");
719
720 if (finish == -1) {
721 finish = System.currentTimeMillis();
722 }
723
724 DecimalFormat decimalFormat = new DecimalFormat("0.00");
725 String elapsed = decimalFormat.format((finish - start) / 1000.0);
726
727 buffer.append("Elapsed time : ");
728 buffer.append(elapsed);
729 buffer.append(" seconds");
730 buffer.append("\n");
731
732 return buffer.toString();
733
734 }
735
736 /***
737 * Retorns a <code>String</code> containing name and value of all properties
738 * set.
739 *
740 * @return a <code>String</code>.
741 */
742 public String getPropertyInfo() {
743
744 StringBuffer buffer = new StringBuffer();
745
746 String[] propertyNames = getPropertyNames();
747
748 for (int i = 0; i < propertyNames.length; i++) {
749
750 buffer.append("\t");
751 buffer.append(propertyNames[i]);
752 buffer.append(" = ");
753
754 final Object propertyValue = getPropertyValue(propertyNames[i]);
755
756 if (propertyValue instanceof List) {
757 buffer.append("list : ");
758 }
759
760 buffer.append(propertyValue);
761 buffer.append("\n");
762
763 }
764
765 return buffer.toString();
766
767 }
768
769 /***
770 * Returns the <i>logger</i> used by this executor.
771 *
772 * @return a <code>org.apache.commons.logging.Log</code> instance.
773 */
774 public Log getLog() {
775 return log;
776 }
777
778 /***
779 * Returns the value of the property which name is
780 * <code>propertyName</code>. If this property is a simple value, a
781 * <code>String</code> is returned. Otherwise, a <code>List</code>
782 * containing <code>String</code>s is returned.
783 *
784 * @param propertyName a <code>String</code>.
785 * @return the value of <code>propertyName</code>.
786 * @throws IllegalArgumentException if <code>propertyName</code> is
787 * <code>null</code>.
788 */
789 final public Object getPropertyValue(String propertyName) {
790
791 if (propertyName == null) {
792
793 throw new IllegalArgumentException(
794 "Parameter propertyName can't be null.");
795
796 }
797
798 if (multivaluedProperties.containsKey(propertyName)) {
799
800 return context.get(propertyName);
801
802 } else {
803
804 String value = null;
805 final String template =
806 TEMPLATE_PREFIX + propertyName + TEMPLATE_SUFFIX;
807
808 try {
809 value = evaluate(template);
810 } catch (EvaluationException e) {
811
812 if (log.isErrorEnabled()) {
813 log.error(e);
814 }
815
816 return null;
817
818 }
819
820 if (template.equals(value)) {
821 return null;
822 } else {
823 return value;
824 }
825
826 }
827
828 }
829
830 /***
831 * Evaluates the Velocity template contained in <code>template</code>.
832 *
833 * @param template a <code>String</code> contendo um Velocity template.
834 */
835 private String evaluate(String template) throws EvaluationException {
836
837 StringWriter sw = new StringWriter();
838
839 try {
840
841 engine.evaluate(context, sw, template, template);
842
843 } catch (Exception e) {
844
845 final String message = "Problems evaluation template : "
846 + template;
847
848 if (log.isFatalEnabled()) {
849 log.fatal(message);
850 }
851
852 throw new EvaluationException(e, this, template);
853
854 }
855
856 return sw.toString();
857
858 }
859
860 /***
861 * Adds a value to a multivalued property (list). If the property does not
862 * exists yet, it is created. To set simple properties, use
863 * {@link #setProperty}.
864 *
865 * @param propertyId a <code>String</code> containing the property name.
866 * @param value a <code>String</code> containing the value to be added.
867 * @param allowDuplicates a <code>boolean</code> telling if duplicate values
868 * can be added or not.
869 * @throws SinonException if <code>propertyId</code> is not a multivalued
870 * property.
871 * @throws IllegalArgumentException if <code>propertyId</code> is
872 * <code>null</code> or if <code>value</code> is <code>null</code>.
873 * @todo this method cannot be public.
874 */
875 final public void addValueToProperty(String propertyId, String value,
876 boolean allowDuplicates)
877 throws SinonException {
878
879 if (propertyId == null) {
880
881 throw new IllegalArgumentException(
882 "Parameter propertyId can't be null");
883
884 }
885
886 if (value == null) {
887
888 throw new IllegalArgumentException(
889 "Parameter value can't be null");
890
891 }
892
893 if (multivaluedProperties.containsKey(propertyId) == false) {
894
895 if (propertyNames.contains(propertyId)) {
896
897 throw new SinonException("Attempt to add a value to a " +
898 "property that is not multivalued : "
899 + propertyId);
900
901 }
902
903 ArrayList values = new ArrayList();
904 values.add(value);
905
906 propertyNames.add(propertyId);
907 multivaluedProperties.put(propertyId, values);
908 context.put(propertyId, values);
909
910 if (log.isDebugEnabled()) {
911
912 log.debug("Creating multivalued property " + propertyId
913 + " with value " + value);
914
915 }
916
917 } else {
918
919 if (propertyNames.contains(propertyId) == false) {
920 propertyNames.add(propertyId);
921 }
922
923 List values = (List) multivaluedProperties.get(propertyId);
924
925 if (allowDuplicates == false) {
926
927 if (values.contains(value)) {
928
929 log.debug("Attempt to add value to multivalued property " +
930 "that does not accept duplicate values: "
931 + propertyId + " value: "
932 + value);
933
934 return;
935
936 }
937
938 }
939
940 values.add(value);
941
942 if (log.isDebugEnabled()) {
943
944 log.debug("Added value to property " + propertyId
945 + " : " + value);
946
947 }
948
949 }
950
951 }
952
953 /***
954 * Sets a property. This method can only be used with simple properties.
955 * To add a value to a multivalued property, use
956 * {@link #addValueToProperty}.
957 *
958 * @param propertyId a <code>String</code> containing the property name.
959 * @param value a <code>String</code> containing the property value.
960 * @throws SinonException if <code>propertyId</code> for o nome de uma propriedade
961 * multivalorada.
962 * @throws IllegalArgumentException
963 * se <code>propertyId</code> é <code>null</code> ou se
964 * <code>value</code> é <code>null</code>.
965 */
966 final public void setProperty(String propertyId, String value)
967 throws SinonException {
968
969 if (propertyId == null) {
970
971 throw new IllegalArgumentException(
972 "Parameter propertyId cannot be null");
973
974 }
975
976 if (value == null) {
977
978 throw new IllegalArgumentException(
979 "Parameter value cannot be null");
980
981 }
982
983 if (propertyNames.contains(propertyId) == false) {
984
985 if (multivaluedProperties.containsKey(propertyId)) {
986
987 throw new SinonException("Attempt to set a value for a "
988 + "multivalued property: " + propertyId);
989
990 }
991
992 propertyNames.add(propertyId);
993
994 }
995
996 if (log.isDebugEnabled()) {
997
998 log.debug("Setting property : " + propertyId + " = " + value
999 + " (previous value : " + context.get(propertyId) + ")");
1000
1001 }
1002
1003 context.put(propertyId, value);
1004
1005 }
1006
1007 /***
1008 * Executes a catalog.
1009 *
1010 * @param catalog a {@link Catalog} instance.
1011 * @throws SinonException if some error ocurrs.
1012 */
1013 private void executeCatalog(Catalog catalog) throws SinonException {
1014
1015 currentCatalog = catalog;
1016 StartPage[] startPages = catalog.getStartPages();
1017 Page page;
1018
1019 if (mustStop == false) {
1020
1021 if (log.isInfoEnabled()) {
1022
1023 log.info("Starting processing of catalog "
1024 + catalog.getId());
1025
1026 }
1027
1028 for (int i = 0; i < startPages.length; i++) {
1029
1030 page = catalog.getPage(startPages[i].getRefid());
1031
1032 if (page == null) {
1033
1034 final UnknownPageException exception =
1035 new UnknownPageException(
1036 startPages[i].getRefid(), this);
1037
1038 throw exception;
1039
1040 }
1041
1042
1043
1044
1045 if (i > 0) {
1046 setup(catalog);
1047 }
1048
1049 setProperties(startPages[i].getProperties());
1050
1051 if (log.isInfoEnabled()) {
1052
1053 log.info("Starting download and processing of initial " +
1054 "action " + page.getId() + " (" + (i + 1)
1055 + " out of " + startPages.length + ")");
1056
1057 }
1058
1059 actionQueue.clear();
1060
1061 executePage(page);
1062
1063 while (actionQueue.isEmpty() == false) {
1064
1065
1066
1067 final int size = actionQueue.size();
1068 ActionContextPair pair =
1069 (ActionContextPair) actionQueue.get(size - 1);
1070
1071 actionQueue.remove(pair);
1072
1073 final Action action = pair.getAction();
1074
1075 Page thisPage = catalog.getPage(action.getPageId());
1076
1077
1078
1079
1080 context = pair.getContext();
1081
1082 executePage(thisPage, action);
1083
1084 }
1085
1086 }
1087
1088 }
1089
1090 if (log.isInfoEnabled()) {
1091
1092 log.info("Finished execution of catalog " + catalog.getId());
1093 log.info("Execution information up to now:");
1094 log.info(getExecutionInfo());
1095
1096 }
1097
1098 }
1099
1100 /***
1101 * Executes a action. If <code>action</code> is not <code>null</code>,
1102 * the action download will follow the <code>action</code>.
1103 * Otherwise, the action download will use the configurations of
1104 * <code>action</code>.
1105 *
1106 * @param page a {@link Page} instance.
1107 * @param action an {@link Action} instance.
1108 * @throws SinonException if some error ocurrs.
1109 */
1110 private void executePageAndItsActions(Page page, Action action)
1111 throws SinonException {
1112
1113 executePage(page, action);
1114
1115 }
1116
1117 /***
1118 * Calls {@link #setProperty} for each property in
1119 * <code>properties</code>.
1120 *
1121 * @param properties a <code>java.util.Properties</code> instance.
1122 */
1123 private void setProperties(Properties properties) throws SinonException {
1124
1125 Enumeration keys = properties.keys();
1126
1127 while (keys.hasMoreElements()) {
1128
1129 String key = (String) keys.nextElement();
1130 String value = properties.getProperty(key);
1131 setProperty(key, value);
1132
1133 }
1134
1135 }
1136
1137 /***
1138 * Executes a action. The same as <code>executeCatalog(action, null)</code>.
1139 *
1140 * @param page a {@link Page} instance.
1141 * @throws SinonException if some error ocurrs.
1142 */
1143 private void executePage(Page page) throws SinonException {
1144 executePage(page, null);
1145 }
1146
1147 /***
1148 * Executes a pages. If <code>action</code> is not <code>null</code>,
1149 * the action download will follow the <code>action</code>.
1150 * Otherwise, the action download will use the configurations of
1151 * <code>action</code>.
1152 *
1153 * @param page a {@link Page} instance.
1154 * @param page an {@link Action} instance.
1155 * @throws SinonException if some error ocurrs.
1156 */
1157 private void executePage(Page page, Action action) throws SinonException {
1158
1159
1160 if (mustStop == false) {
1161
1162 currentPage = page;
1163
1164 loadListeners(page);
1165
1166 currentText = download(page, action);
1167
1168
1169
1170 if (currentText == null) {
1171
1172 notifyPageListener(FINISHED);
1173 return;
1174
1175 }
1176
1177 final String filename =
1178 page.getStorageConfiguration().getFilename();
1179
1180 if (filename != null) {
1181 writePageToFile(filename);
1182 }
1183
1184 processPage(page);
1185
1186 notifyPageListener(AFTER_PROCESSING);
1187
1188 Action[] actions = page.getActions();
1189
1190 for (int i = 0; i < actions.length; i++) {
1191 executeActionContextPairs(actions[i]);
1192 }
1193
1194 currentPage = page;
1195
1196 notifyPageListener(FINISHED);
1197
1198 }
1199
1200 }
1201
1202 /***
1203 * Writes {@link #currentText} to a file named <code>filename</code>.
1204 * If it already exists, it will be overwritten.
1205 *
1206 * @param filename a <code>String</code>.
1207 */
1208 private void writePageToFile(final String filename) throws SinonException {
1209
1210 String pathname = evaluate(filename);
1211 File destination = new File(pathname);
1212
1213 try {
1214
1215 if (destination.isAbsolute() == false) {
1216 destination = new File(CURRENT_DIRECTORY, pathname);
1217 }
1218
1219 BufferedOutputStream outputStream =
1220 new BufferedOutputStream(new FileOutputStream(destination));
1221
1222 outputStream.write(currentBytes);
1223 outputStream.close();
1224
1225 } catch (IOException e) {
1226
1227 throw new SinonException("Error writing page "
1228 + currentPage.getId()
1229 + " to " + destination.getAbsolutePath(), e, this);
1230
1231 }
1232
1233 }
1234
1235 /***
1236 * Loads the listeners of a given action.
1237 */
1238 private void loadListeners(Page page) throws SinonException {
1239
1240 String className;
1241 Object instance;
1242
1243 className = page.getErrorListenerClassName();
1244
1245 if (className != null) {
1246
1247 instance = invokeGetInstanceMethod(className);
1248
1249 try {
1250
1251 currentErrorListener = (ErrorListener) instance;
1252
1253 } catch (ClassCastException e) {
1254
1255 final String message = "The object returned by the static "
1256 + "method getInstance() of class " + className
1257 + " is not an ErrorListener instance.";
1258
1259 throw new SinonException(message, e, this);
1260
1261 }
1262
1263 } else {
1264 currentErrorListener = null;
1265 }
1266
1267 className = currentPage.getPageStateListenerClassName();
1268
1269 if (className != null) {
1270
1271 instance = invokeGetInstanceMethod(className);
1272
1273 try {
1274
1275 currentPageStateListener = (PageStateListener) instance;
1276
1277 } catch (ClassCastException e) {
1278
1279 final String message = "The object returned by the static "
1280 + "method getInstance() of class " + className
1281 + " is not an PageStateListener instance.";
1282
1283 throw new SinonException(message, e, this);
1284
1285 }
1286
1287 } else {
1288 currentPageStateListener = null;
1289 }
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315 }
1316
1317 /***
1318 * Invokes static method <code>getInstance</code> of the class whose
1319 * fully qualified name is <code>className</code>.
1320 *
1321 * @param className a <code>String</code>.
1322 * @return the object returned by static method <code>getInstance</code>
1323 * of the class whose fully qualified name is <code>className</code>.
1324 * @throws SinonException if some error ocurrs.
1325 */
1326 private Object invokeGetInstanceMethod(String className)
1327 throws SinonException {
1328
1329 Object instance;
1330 Class someClass = null;
1331
1332 try {
1333
1334 someClass = Class.forName(className);
1335
1336 } catch (ClassNotFoundException e) {
1337
1338 final String message = "Class " + className
1339 + " was not found.";
1340
1341 throw new SinonException(message, e, this);
1342
1343 }
1344
1345 try {
1346
1347 Method method = someClass.getMethod("getInstance", null);
1348 instance = method.invoke(null, null);
1349
1350 } catch (Exception e) {
1351
1352 final String message = "Could not create a " + className +
1353 "instance. Check if it has a public static getInstance() " +
1354 "method that returns one instance of this class.";
1355
1356 throw new SinonException(message, e, this);
1357
1358 }
1359
1360 return instance;
1361
1362 }
1363
1364 /***
1365 * Notifies the current action {@link PageStateListener} that its
1366 * state changed to <code>newPageState</code>.
1367 *
1368 * @param newPageState a {@link PageState} instance.
1369 */
1370 private void notifyPageListener(PageState newPageState) {
1371
1372 if (currentPageStateListener != null) {
1373
1374 currentPageStateListener.onStateChange(currentPage, newPageState,
1375 catalogStateListener, this);
1376
1377 }
1378
1379 }
1380
1381 /***
1382 * Processes a action.
1383 *
1384 * @param page a {@link Page} instance.
1385 */
1386 private void processPage(Page page) throws SinonException {
1387
1388 Step[] steps = page.getSteps();
1389 currentPosition = 0;
1390 from = -1;
1391 to = -1;
1392
1393 for (int i = 0; i < steps.length; i++) {
1394
1395 Step step = steps[i];
1396
1397 try {
1398
1399 executeStep(step, Integer.MAX_VALUE);
1400
1401 } catch (SinonException e) {
1402
1403 ErrorResponse response = notifyErrorListener(e);
1404
1405 if (response.isResumeNext()) {
1406 continue;
1407 } else if (response.isRetry()) {
1408 executeStep(step, Integer.MAX_VALUE);
1409 } else if (response.isStop()) {
1410 throw e;
1411 }
1412
1413 }
1414
1415 }
1416
1417 }
1418
1419 /***
1420 * Executes an extraction step (action, data extraction, loop, jump,
1421 * from or to).
1422 *
1423 * @param step a {@link Step} instance.
1424 * @param limitPosition a <code>int</code> containing a position that
1425 * this step cannot go further.
1426 * @throws StepException if some error ocurrs.
1427 */
1428 private void executeStep(Step step, int limitPosition)
1429 throws SinonException {
1430
1431 currentStep = step;
1432
1433 if (step.isPositioningStep()) {
1434
1435 PositioningStep positioningStep = (PositioningStep) step;
1436 executePositioningStep(positioningStep, limitPosition);
1437
1438 } else if (step.isDataExtraction()) {
1439
1440 DataExtraction dataExtraction = (DataExtraction) step;
1441 executeDataExtraction(dataExtraction, limitPosition);
1442
1443 } else if (step.isAction()) {
1444
1445 log.fatal("This could not happen!!!");
1446 System.exit(1);
1447
1448 } else if (step.isLoop()) {
1449
1450 LoopStep loopStep = (LoopStep) step;
1451 executeLoop(loopStep, limitPosition);
1452
1453 } else {
1454
1455 throw new StepException("Unknown step type: " + step + " : "
1456 + step.getClass(), this);
1457
1458 }
1459
1460 }
1461
1462 /***
1463 * Executes a loop.
1464 *
1465 * @param loop a {@link LoopStep} instance.
1466 * @param limitPosition a <code>int</code> containing a position that
1467 * this step cannot go further.
1468 * @throws StepException if some error ocurrs.
1469 */
1470 private void executeLoop(LoopStep loop, int limitPosition)
1471 throws SinonException {
1472
1473 int iterations = 0;
1474 int loopReferencePosition = getLoopReferencePosition(loop);
1475
1476 if (limitPosition < loopReferencePosition) {
1477 loopReferencePosition = limitPosition;
1478 }
1479
1480 while (evaluateLoopCondition(loop, loopReferencePosition)) {
1481
1482 Step[] steps = loop.getSteps();
1483 int oldCurrentPosition = currentPosition;
1484
1485 try {
1486
1487 for (int i = 0; i < steps.length; i++) {
1488
1489 if (oldCurrentPosition > currentPosition) {
1490 break;
1491 }
1492
1493 oldCurrentPosition = currentPosition;
1494 executeStep(steps[i], loopReferencePosition);
1495
1496 if (oldCurrentPosition > currentPosition) {
1497 break;
1498 }
1499
1500 }
1501
1502
1503 } catch (StepAfterLoopLimitPositionException afterLimitPosition) {
1504
1505 if (log.isDebugEnabled()) {
1506 log.debug("Iterations executed : " + iterations);
1507 }
1508
1509 return;
1510
1511 } catch (StepWithBeforeException beforeException) {
1512 throw beforeException;
1513 } catch (SinonException sinonException) {
1514
1515 if (loop.getCondition().equals("notFails")) {
1516
1517 currentPosition = oldCurrentPosition;
1518
1519 if (log.isDebugEnabled()) {
1520
1521 log.debug("Expected failure in loop in action "
1522 + "identificador " + currentPage.getId());
1523
1524 if (currentStep.isPositioningStep()) {
1525
1526 PositioningStep positioningStep =
1527 (PositioningStep) currentStep;
1528
1529 log.debug("Reference : "
1530 + positioningStep.getReference());
1531
1532 }
1533
1534 log.debug("Iterations executed : " + iterations);
1535
1536 }
1537
1538 return;
1539
1540 } else {
1541 throw sinonException;
1542 }
1543
1544 }
1545
1546 iterations++;
1547
1548 if (evaluateLoopCondition(loop, loopReferencePosition) == false) {
1549 return;
1550 }
1551
1552 }
1553
1554 }
1555
1556 /***
1557 * Retruns the reference position of the current loop.
1558 *
1559 * @param loop a {@link LoopStep} instance.
1560 * @throws StepException if some error ocurrs.
1561 */
1562 private int getLoopReferencePosition(LoopStep loop) throws SinonException {
1563
1564 int loopReferencePosition;
1565
1566 if (loop.getCondition().equals("before")) {
1567
1568 loopReferencePosition = currentText.indexOf(
1569 loop.getValue(), currentPosition);
1570
1571 if (loopReferencePosition < 0) {
1572
1573 throw new SinonException("Loop reference not found : "
1574 + loop.getValue());
1575
1576 }
1577
1578 } else {
1579 loopReferencePosition = Integer.MAX_VALUE;
1580 }
1581
1582 return loopReferencePosition;
1583
1584 }
1585
1586 /***
1587 * Returns <code>true</code> if this loop must continue, <code>false</code>
1588 * otherwise.
1589 *
1590 * @param loop a {@link LoopStep} instance.
1591 * @return <code>true</code> if this loop must continue, <code>false</code>
1592 * otherwise.
1593 * @throws StepException if some error ocurrs.
1594 */
1595 private boolean evaluateLoopCondition(LoopStep loop,
1596 int loopReferencePosition) throws SinonException {
1597
1598 if (loop.getCondition().equals("notFails")) {
1599
1600 return (true);
1601
1602 } else if (loop.getCondition().equals("before")) {
1603
1604 return (currentPosition < loopReferencePosition);
1605
1606 } else {
1607
1608 throw new SinonException("Unknown loop stop condition : "
1609 + loop.getCondition());
1610
1611 }
1612
1613 }
1614
1615 /***
1616 * Enqueues one or more {@link ActionContextPair} to execute
1617 * this <code>action</code>.
1618 *
1619 * @param action an {@link Action} instance.
1620 * @throws SinonException if some error ocurrs.
1621 */
1622 private void executeActionContextPairs(Action action)
1623 throws SinonException {
1624
1625 String condition = action.getCondition();
1626
1627
1628 if (condition != null) {
1629
1630 final boolean mustExecute = evaluateActionCondition(condition,
1631 action.getPropertyId());
1632
1633 if (mustExecute == false) {
1634 return;
1635 }
1636
1637 }
1638
1639 Page page = currentCatalog.getPage(action.getPageId());
1640
1641 if (page == null) {
1642
1643 if (log.isFatalEnabled()) {
1644
1645 final String message = "Page " + action.getPageId()
1646 + ", referenced in a " + currentPage.getId() +
1647 " action, was not found.";
1648
1649 log.fatal(message);
1650
1651 throw new UnknownPageException(action.getPageId(), this);
1652
1653 }
1654
1655 }
1656
1657 String foreach = action.getForeach();
1658
1659
1660 if (foreach != null) {
1661
1662 String index = action.getIndex();
1663 String variable = action.getVariable();
1664 List values = (List) getPropertyValue(foreach);
1665
1666 if (values == null) {
1667
1668 throw new SinonException("Property " + foreach
1669 + " does not exists or no value was added to it.");
1670
1671 }
1672
1673 final int size = values.size();
1674
1675
1676
1677
1678 for (int i = size - 1; i >= 0; i--) {
1679
1680 String value = (String) values.get(i);
1681
1682 if (index != null) {
1683 setProperty(index, String.valueOf(i + 1));
1684 }
1685
1686 if (variable != null) {
1687 setProperty(variable, value);
1688 }
1689
1690 enqueueAction(action);
1691
1692 }
1693
1694 } else {
1695
1696 page = currentCatalog.getPage(action.getPageId());
1697
1698 if (page == null) {
1699
1700 if (log.isFatalEnabled()) {
1701
1702 final String message = "Page " + action.getPageId()
1703 + ", referenced by a "
1704 + currentPage.getId() + " action, was not found.";
1705
1706 log.fatal(message);
1707
1708 throw new UnknownPageException(action.getPageId(), this);
1709
1710 }
1711
1712 }
1713
1714 enqueueAction(action);
1715
1716 }
1717
1718 }
1719
1720 /***
1721 * Enqueues an action.
1722 *
1723 * @param action an {@link Action} instance.
1724 */
1725 private void enqueueAction(Action action) {
1726
1727 if (action == null) {
1728
1729 throw new IllegalArgumentException(
1730 "Parameter action can't be null");
1731
1732 }
1733
1734 final VelocityContext contextClone = (VelocityContext) context.clone();
1735
1736 actionQueue.add(new ActionContextPair(action, contextClone));
1737
1738 }
1739
1740 /***
1741 * Evaluates if a given {@link Action} condition is true or not.
1742 *
1743 * @param condition a <code>String</code> containing the name of the
1744 * condition.
1745 * @param propertyId a <code>String</code> containing the name of
1746 * the variable used to evaluate the condition.
1747 * @return <code>true</code> if the condition is true,
1748 * <code>false</code> otherwise.
1749 * @throws IllegalArgumentException if <code>propertyId</code> is
1750 * <code>null</code>.
1751 * @throws SinonException if some error ocurrs.
1752 */
1753 private boolean evaluateActionCondition(String condition, String propertyId)
1754 throws SinonException {
1755
1756 String value = (String) getPropertyValue(propertyId);
1757
1758 if (value == null) {
1759 throw new SinonException("The property " + propertyId + " does " +
1760 "not exists.");
1761 }
1762
1763 if (condition.equals("notEmpty")) {
1764
1765 if (value.trim().length() == 0) {
1766 return false;
1767 } else {
1768 return true;
1769 }
1770
1771 } else {
1772
1773 throw new IllegalArgumentException("The condition " + condition
1774 + " is not valid.");
1775
1776 }
1777
1778 }
1779
1780 /***
1781 * Executes a data extraction. The action current position is changed.
1782 *
1783 * @param dataExtraction a {@link PositioningStep} instance.
1784 * @param limitPosition a <code>int</code> containing a position that
1785 * this step cannot go further.
1786 * @throws StepException if some error ocurrs.
1787 */
1788 private void executeDataExtraction(DataExtraction dataExtraction,
1789 int limitPosition)
1790 throws SinonException {
1791
1792 currentDataExtraction = dataExtraction;
1793
1794 if (dataExtraction.isResetPositionNeeded()) {
1795 currentPosition = 0;
1796 }
1797
1798 Step[] steps = dataExtraction.getSteps();
1799 int oldCurrentPosition = currentPosition;
1800
1801 try {
1802
1803 for (int i = 0; i < steps.length; i++) {
1804 executeStep(steps[i], limitPosition);
1805 }
1806
1807 } catch (StepAfterLoopLimitPositionException e) {
1808
1809 throw e;
1810
1811 } catch (StepException e) {
1812
1813 if (dataExtraction.isFailOnError()) {
1814 throw e;
1815 } else {
1816
1817 currentPosition = oldCurrentPosition;
1818
1819 if (log.isDebugEnabled()) {
1820
1821 if (e instanceof StepWithFailIfFoundException
1822 || e instanceof StepWithBeforeException) {
1823
1824 log.debug(e.getClass() + " : " + e.getMessage());
1825
1826 } else {
1827
1828 log.debug("Data extraction " + dataExtraction.getId()
1829 + " had an expected failure, so everything " +
1830 "OK.");
1831
1832 }
1833
1834 if (currentStep.isPositioningStep()) {
1835
1836 PositioningStep positioningStep = (PositioningStep) currentStep;
1837
1838 log.debug("Reference : "
1839 + positioningStep.getReference());
1840
1841 }
1842
1843 }
1844
1845 if (dataExtraction.isMultivalued()) {
1846 addValueToProperty(dataExtraction.getId(),
1847 dataExtraction.getValueOnError(), true);
1848 } else {
1849 setProperty(dataExtraction.getId(),
1850 dataExtraction.getValueOnError());
1851 }
1852
1853 return;
1854
1855 }
1856
1857 }
1858
1859 String value = extractFromToData(dataExtraction);
1860
1861 if (dataExtraction.isTrimValueNeeded()) {
1862 value = value.trim();
1863 }
1864
1865 if (dataExtraction.isMultivalued() == false) {
1866 setProperty(dataExtraction.getId(), value);
1867 } else {
1868
1869 addValueToProperty(dataExtraction.getId(), value,
1870 dataExtraction.isAllowDuplicates());
1871
1872 }
1873
1874 }
1875
1876 /***
1877 * Extracts a piece of data after <code>from</code> and <code>to</code>
1878 * steps were already done. The extracted string is returned.
1879 *
1880 * @param dataExtraction the {@link DataExtraction} being executed.
1881 * @throws SinonException if the <code>from</code> step was not
1882 * executed succesfully of if the <code>to</code> step was not
1883 * executed succesfully of if the <code>from</code> position is
1884 * higher than the <code>to</code> one.
1885 */
1886 private String extractFromToData(DataExtraction dataExtraction)
1887 throws SinonException {
1888
1889 if (from < 0) {
1890
1891 throw new SinonException("Initial position (from) of data " +
1892 " extraction " + dataExtraction.getId()
1893 + " was not found or was not set in action "
1894 + currentPage.getId());
1895
1896 }
1897
1898 if (to < 0) {
1899
1900 throw new SinonException("End position (to) of data " +
1901 " extraction " + dataExtraction.getId()
1902 + " was not found or was not set in action "
1903 + currentPage.getId());
1904
1905 }
1906
1907 if (from > to) {
1908
1909 throw new SinonException("Initial position (from) of data "
1910 + "extraction " + dataExtraction.getId() + " is higher "
1911 + "than the end positioin (to) in action " +
1912 currentPage.getId());
1913
1914 }
1915
1916 String value = currentText.substring(from, to);
1917 return value;
1918
1919 }
1920
1921 /***
1922 * Executes a positioning step. The current position is changed.
1923 *
1924 * @param step a {@link PositioningStep} instance.
1925 * @param limitPosition a <code>int</code> containing a position that
1926 * this step cannot go further.
1927 * @throws StepException if the positioning step's reference was not found.
1928 */
1929 private void executePositioningStep(PositioningStep step, int limitPosition)
1930 throws StepException {
1931
1932 currentStep = step;
1933
1934 int oldCurrentPosition = currentPosition;
1935
1936 final String originalReference = step.getReference();
1937 String reference = null;
1938
1939 try {
1940 reference = evaluate(originalReference);
1941 } catch (EvaluationException e) {
1942
1943 throw new StepException("Could not evaluate reference "
1944 + originalReference + " : " + e.getClass() + " : "
1945 + e.getMessage(), this);
1946
1947 }
1948
1949 int beforePosition = Integer.MAX_VALUE;
1950
1951 String before = step.getBefore();
1952
1953 if (before != null) {
1954
1955 beforePosition = currentText.indexOf(before, currentPosition);
1956
1957 if (beforePosition == -1) {
1958
1959 throw new StepWithBeforeException(
1960 "Failure executing positioning step: the reference " +
1961 "string " + before + ", used as maximum position, " +
1962 "was not found.", this);
1963
1964 }
1965
1966 }
1967
1968 if (step.getDirection() == PositioningStep.FORWARD) {
1969
1970 currentPosition = currentText.indexOf(reference, currentPosition);
1971
1972 } else {
1973
1974 currentPosition = currentText.lastIndexOf(reference,
1975 currentPosition);
1976
1977 }
1978
1979 if (step.isFailIfFound()) {
1980
1981 if (currentPosition >= 0) {
1982
1983 throw new StepWithFailIfFoundException("Reference string "
1984 + originalReference + " (" + reference
1985 + "), that means an expected failure, was found" +
1986 "and everything is OK.", this);
1987
1988 } else {
1989
1990 currentPosition = oldCurrentPosition;
1991 return;
1992
1993 }
1994
1995 }
1996
1997 if (currentPosition > limitPosition) {
1998
1999 throw new StepAfterLoopLimitPositionException("The positioning " +
2000 "step with reference " + originalReference +
2001 " (" + reference + ") went further the loop limit.", this);
2002
2003 }
2004
2005 if (currentPosition == -1) {
2006
2007 currentPosition = oldCurrentPosition;
2008
2009 throw new StepException("Failure during execution of positioning " +
2010 "step: the reference string " + originalReference +
2011 " (" + reference + ") "
2012 + " was not found.", this);
2013
2014 }
2015
2016 if (currentPosition >= beforePosition) {
2017
2018 throw new StepException("Failure during execution of positioning " +
2019 "step: the reference string " + originalReference +
2020 " (" + reference + ") "
2021 + " was found after string " + before, this);
2022
2023 }
2024
2025 if (step.isFrom() || step.isJump()) {
2026 currentPosition += reference.length();
2027 }
2028
2029 if (step.isFrom()) {
2030 from = currentPosition;
2031 }
2032
2033 if (step.isTo()) {
2034 to = currentPosition;
2035 }
2036
2037 }
2038
2039 /***
2040 * Downloads a action and returns it in a <code>String</code>.
2041 * The configurations contained in <code>actioni</code>, if it is not
2042 * <code>null</code>, are used instead of the configurations contained in
2043 * <code>action</code>.
2044 *
2045 * @param page a {@link Page} instance.
2046 * @param action an {@link Action} instance.
2047 * @return a <code>String</code>.
2048 * @throws SinonException if some unhandled error ocurrs.
2049 * se algum erro não tratado ocorrer.
2050 */
2051 private String download(Page page, Action action) throws SinonException {
2052
2053 HttpMethod method;
2054 String url = null;
2055 PageConfiguration configuration = null;
2056 currentText = null;
2057 currentBytes = null;
2058
2059 if (action != null) {
2060
2061 url = action.getUrl();
2062
2063 if (url != null) {
2064
2065 configuration = action.getConfiguration();
2066
2067 } else {
2068
2069 url = page.getUrl();
2070 configuration = page.getConfiguration();
2071
2072 }
2073
2074 } else {
2075
2076 url = page.getUrl();
2077 configuration = page.getConfiguration();
2078
2079 }
2080
2081 url = url.trim();
2082
2083 if (url == null || url.length() == 0) {
2084
2085 throw new SinonException("No URL was given to action "
2086 + page.getId() + " in catalog " + currentCatalog.getId());
2087
2088 }
2089
2090 url = evaluate(url);
2091
2092 if (url.startsWith(FILE_PROTOCOL)) {
2093
2094 String filename = url.substring(
2095 FILE_PROTOCOL.length(), url.length());
2096
2097 currentText = readFile(filename);
2098
2099 return currentText;
2100
2101 } else if (url.startsWith(HTTP_PREFIX) == false
2102 && url.startsWith(HTTPS_PREFIX) == false) {
2103
2104 throw new SinonException(
2105 "Toda URL must begin with " + HTTP_PREFIX + ", " +
2106 HTTPS_PREFIX + ", or " + FILE_PROTOCOL +
2107 "(action: " + page + ", url : " + url + ")");
2108
2109 }
2110
2111 method = createHttpMethod(page, url, configuration);
2112
2113 final boolean followRedirects = page.getFollowRedirects();
2114
2115 setHttpHeaders(method);
2116
2117 if (log.isDebugEnabled()) {
2118 logDebuggingMessages(page, url, method);
2119 }
2120
2121 notifyPageListener(BEFORE_DOWNLOAD);
2122
2123 int response = -1;
2124 int retries = collector.getConfiguration().getRetries();
2125 Exception exception = null;
2126 GetMethod redirMethod = null;
2127 String exceptionMessage = null;
2128 boolean ok = false;
2129
2130 for (int i = 0; i < retries + 1; i++) {
2131
2132 if (log.isDebugEnabled() && i > 0) {
2133
2134 log.debug("");
2135 log.debug("Attempt " + i + " of " + retries);
2136
2137 }
2138
2139
2140
2141 if (pagesDownloaded > 0) {
2142 waitForThinkTime();
2143 }
2144
2145 long start = System.currentTimeMillis();
2146 long end;
2147
2148 try {
2149
2150 response = httpClient.executeMethod(method);
2151 boolean wasRedirected = false;
2152
2153 if (followRedirects && response >= 300 && response < 400) {
2154
2155 URI location = new URI(method.getURI(),
2156 method.getResponseHeader("location").getValue());
2157
2158 if (log.isDebugEnabled()) {
2159 log.debug("Redirecting to " + location.getURI());
2160 }
2161
2162 redirMethod = new GetMethod(location.getURI());
2163 response = httpClient.executeMethod(redirMethod);
2164 wasRedirected = true;
2165
2166 }
2167
2168 if (wasRedirected) {
2169 currentBytes = redirMethod.getResponseBody();
2170 } else {
2171 currentBytes = method.getResponseBody();
2172 }
2173
2174 currentText = new String(currentBytes);
2175
2176 if (response == 200 && currentText == null) {
2177
2178 if (log.isDebugEnabled()) {
2179 log.debug("Connection timeout expired.");
2180 }
2181
2182 method.releaseConnection();
2183
2184 if (redirMethod != null) {
2185 redirMethod.releaseConnection();
2186 }
2187
2188 method = createHttpMethod(page, url, configuration);
2189
2190 continue;
2191
2192 }
2193
2194 } catch (MalformedURLException e) {
2195
2196 exception = e;
2197 exceptionMessage = "Invalid URL : " + url;
2198
2199 if (log.isDebugEnabled()) {
2200
2201 log.debug("HTTP response : " + response);
2202 log.debug(exceptionMessage);
2203
2204 }
2205
2206 } catch (UnknownHostException e) {
2207
2208 exception = e;
2209 exceptionMessage = "URL not found : " + url;
2210
2211 if (log.isDebugEnabled()) {
2212
2213 log.debug("HTTP response : " + response);
2214 log.debug(exceptionMessage);
2215
2216 }
2217
2218 method = recreateMethodObject(method, redirMethod, page, url,
2219 configuration);
2220
2221 continue;
2222
2223 } catch (IOException e) {
2224
2225 exceptionMessage = "Problems transferring action "
2226 + page.getId() + " : " + e.getClass() + " : "
2227 + e.getMessage();
2228
2229 if (log.isDebugEnabled()) {
2230
2231 log.debug("HTTP response : " + response);
2232 log.debug(exceptionMessage);
2233
2234 }
2235
2236 exception = e;
2237
2238 method = recreateMethodObject(method, redirMethod, page, url,
2239 configuration);
2240
2241 continue;
2242
2243 }
2244
2245 ok = true;
2246
2247 end = System.currentTimeMillis();
2248
2249 if (log.isDebugEnabled()) {
2250 log.debug("Download elapsed time : " + (end - start) + " ms.");
2251 }
2252
2253 break;
2254
2255 }
2256
2257 if (log.isDebugEnabled()) {
2258 log.debug("HTTP response : " + response);
2259 }
2260
2261 if (response == -1 || response >= 400
2262 || (followRedirects && response >= 300) || ok == false) {
2263
2264 if (exceptionMessage == null && response == -1) {
2265 exceptionMessage = "Connection timeout expired";
2266 } else {
2267 exceptionMessage = "HTTP response : " + response;
2268 }
2269
2270 final SinonException sinonException = new SinonException(
2271 "Page " + page.getId()
2272 + " could not be downloaded : "
2273 + exceptionMessage, exception, this);
2274
2275 ErrorResponse errorResponse = notifyErrorListener(sinonException);
2276
2277 if (errorResponse.isStop()) {
2278 throw sinonException;
2279 } else if (errorResponse.isResumeNext()) {
2280
2281 currentText = null;
2282 return null;
2283
2284 } else if (errorResponse.isRetry()) {
2285 return download(page, action);
2286 }
2287
2288 }
2289
2290 if (currentText == null) {
2291
2292 final String message = "Page " + page.getId()
2293 + " could not be downloaded within timeout.";
2294
2295 if (log.isErrorEnabled()) {
2296 log.error(message);
2297 }
2298
2299 throw new SinonException(message);
2300
2301 }
2302
2303 notifyPageListener(BEFORE_PROCESSING);
2304
2305 bytesDownloaded += currentText.length();
2306 pagesDownloaded++;
2307
2308 return currentText;
2309
2310 }
2311
2312 /***
2313 * Recreates a <code>Method</code> object.
2314 *
2315 * @param method
2316 * @param redirMethod
2317 * @param page
2318 * @param url
2319 * @param configuration
2320 * @return
2321 * @throws SinonException
2322 */
2323 private HttpMethod recreateMethodObject(HttpMethod method,
2324 GetMethod redirMethod,
2325 Page page,
2326 String url,
2327 PageConfiguration configuration)
2328 throws SinonException {
2329
2330 method.releaseConnection();
2331
2332 if (redirMethod != null) {
2333 redirMethod.releaseConnection();
2334 }
2335
2336 method = createHttpMethod(page, url, configuration);
2337 return method;
2338
2339 }
2340
2341 /***
2342 * Halts the collector execution to simulate a think time.
2343 */
2344 private void waitForThinkTime() {
2345
2346 try {
2347
2348 final long millis = thinkTime.nextThinkTime();
2349 Thread.sleep(millis);
2350
2351 if (log.isDebugEnabled()) {
2352 log.debug("Think time interval: " + millis + " ms.");
2353 }
2354
2355 } catch (InterruptedException e) {
2356
2357 if (log.isWarnEnabled()) {
2358 log.warn(e.getClass() + " : " + e.getMessage());
2359 }
2360
2361 }
2362
2363 }
2364
2365 /***
2366 * Creates a <code>Method</code> instance to download a action.
2367 *
2368 * @param page a {@link Page} instance.
2369 * @param url a <code>String</code>.
2370 * @param configuration a {@link PageConfiguration} instance.
2371 * @return a <code>HttpMethod</code> instance.
2372 * @throws SinonException if some error ocurrs.
2373 */
2374 private HttpMethod createHttpMethod(Page page,
2375 String url,
2376 PageConfiguration configuration)
2377 throws SinonException {
2378
2379 HttpMethod method;
2380 ParameterSet parameterSet;
2381
2382 try {
2383
2384 if (page.getMethod() == Page.POST_METHOD) {
2385 method = new PostMethod(url);
2386 } else {
2387 method = new GetMethod(url);
2388 }
2389
2390 } catch (IllegalArgumentException e) {
2391 throw new SinonException("Invalid URL : " + url, e, this);
2392 }
2393
2394 if (configuration != null) {
2395 parameterSet = configuration.getParameterSet();
2396 } else {
2397 parameterSet = null;
2398 }
2399
2400
2401
2402 if (parameterSet != null && parameterSet.getParameters().size() > 0) {
2403
2404 String queryString = getQueryString(parameterSet);
2405
2406
2407
2408 if (page.getMethod() == Page.POST_METHOD) {
2409
2410 PostMethod postMethod = (PostMethod) method;
2411
2412 postMethod.setRequestBody(queryString);
2413
2414 } else {
2415
2416 method.setQueryString(queryString);
2417 }
2418
2419 }
2420
2421 return method;
2422
2423 }
2424
2425 /***
2426 * Returns a query string representing <code>parameterSet</code>.
2427 *
2428 * @param parameterSet a {@link ParameterSet} instance.
2429 * @return a <code>String</code>.
2430 * @throws SinonException if some error ocurrs.
2431 */
2432 private String getQueryString(ParameterSet parameterSet)
2433 throws SinonException {
2434
2435 StringBuffer buffer = new StringBuffer();
2436 Map parameters = parameterSet.getParameters();
2437 Iterator iterator = parameters.keySet().iterator();
2438
2439 while (iterator.hasNext()) {
2440
2441 String name = (String) iterator.next();
2442 Parameter parameter = (Parameter) parameters.get(name);
2443 String value = evaluate(parameter.getValue());
2444 String encoding = parameter.getEncoding();
2445
2446 try {
2447
2448 buffer.append(URLEncoder.encode(name, encoding));
2449 buffer.append("=");
2450 buffer.append(URLEncoder.encode(value, encoding));
2451 buffer.append("&");
2452
2453 } catch (UnsupportedEncodingException e) {
2454
2455 throw new SinonException("Unsupported character set : "
2456 + encoding, e, this);
2457
2458 }
2459
2460 }
2461
2462
2463 buffer.deleteCharAt(buffer.length() - 1);
2464
2465 return buffer.toString();
2466
2467 }
2468
2469 /***
2470 * Logs some debug messages about the {@link Page} about to be downloaded.
2471 *
2472 * @param page a {@link Page} instance.
2473 * @param url a <code>String</code>.
2474 * @param method a <code>HttpMethod</code> instance.
2475 */
2476 private void logDebuggingMessages(Page page,
2477 String url,
2478 HttpMethod method) {
2479
2480 log.debug("");
2481 log.debug("Starting download of action " + page.getId());
2482 log.debug("URL : " + url);
2483
2484 if (page.getMethod() == Page.POST_METHOD) {
2485
2486 PostMethod postMethod = (PostMethod) method;
2487
2488 try {
2489
2490 log.debug("Query string: "
2491 + postMethod.getRequestBodyAsString());
2492
2493 } catch (Exception e) {
2494 log.debug(e);
2495 }
2496
2497 log.debug("Using POST method to send parameters.");
2498
2499 } else {
2500
2501 log.debug("Query string: " + method.getQueryString());
2502 log.debug("Using GET method to send parameters.");
2503
2504 }
2505
2506 Cookie[] cookies = httpClient.getState().getCookies();
2507
2508 if (cookies.length > 0) {
2509
2510 log.debug("Cookies:");
2511
2512 for (int i = 0; i < cookies.length; i++) {
2513
2514 log.debug("\t" + cookies[i].getName() + " : "
2515 + cookies[i].getValue());
2516
2517 }
2518
2519 }
2520
2521 }
2522
2523 /***
2524 * Notifies the current action's {@link ErrorListener}, if it has one,
2525 * that some exception ocurred and asks what to do.
2526 *
2527 * @param exception a {@link SinonException} instance.
2528 * @return a {@link ErrorResponse} instance..
2529 */
2530 private ErrorResponse notifyErrorListener(SinonException exception) {
2531
2532 if (currentErrorListener != null) {
2533 return currentErrorListener.onError(exception);
2534 } else {
2535 return ErrorResponse.STOP;
2536 }
2537
2538 }
2539
2540 /***
2541 * Reads a text file and returns int in a <code>String</code>.
2542 *
2543 * @param filename a <code>String</code> containing the file name.
2544 * @return uma <code>String</code> containing the file contents.
2545 * @throws SinonException if some error ocurrs.
2546 */
2547 final private String readFile(String filename) throws SinonException {
2548
2549 StringBuffer buffer = null;
2550
2551 try {
2552
2553 FileReader fileReader = new FileReader(filename);
2554 BufferedReader reader = new BufferedReader(fileReader);
2555
2556 buffer = new StringBuffer();
2557
2558 String line = reader.readLine();
2559
2560 while (line != null) {
2561
2562 buffer.append(line);
2563 line = reader.readLine();
2564
2565 }
2566
2567 } catch (FileNotFoundException e) {
2568
2569 throw new SinonException("File not found : " + filename, e, this);
2570
2571 } catch (IOException e) {
2572
2573 throw new SinonException("Error reading file " + filename,
2574 e, this);
2575
2576 }
2577
2578 return buffer.toString();
2579
2580 }
2581
2582 /***
2583 * Configures the logger object and deletes the current directory's
2584 * <code>sinon.log</code> file if it already exists.
2585 */
2586 private void configureLogger() {
2587
2588 File file = new File(LOG_FILENAME);
2589
2590 if (file.exists()) {
2591 file.delete();
2592 }
2593
2594 Properties props = new Properties();
2595
2596 InputStream is = this.getClass().getResourceAsStream(
2597 "/" + LOG4J_PROPERTIES_FILENAME);
2598
2599 try {
2600 props.load(is);
2601 PropertyConfigurator.configure(props);
2602 log = LogFactory.getLog(CollectorExecutor.class);
2603 log.info("Loaded log4j properties");
2604 } catch (RuntimeException e1) {
2605 System.out.println("log4J.properties not found. " +
2606 "The logger will use default settings.");
2607 e1.printStackTrace();
2608 } catch (IOException e2) {
2609 System.out.println("Error loading log4j.properties");
2610 e2.printStackTrace();
2611 }
2612
2613
2614 }
2615
2616 /***
2617 * Configures the think time.
2618 */
2619 private void configureThinkTime() throws SinonException {
2620
2621 final CollectorConfiguration configuration =
2622 collector.getConfiguration();
2623
2624 String className = configuration.getThinkTimeImplementationClassName();
2625
2626 Class thinkTimeClass = null;
2627
2628 try {
2629
2630 thinkTimeClass = Class.forName(className);
2631
2632 } catch (ClassNotFoundException e) {
2633
2634 final String message = "Class " + className
2635 + ", eteg.sinon.executor.ThinkTime implementation, "
2636 + "was not found.";
2637
2638 throw new SinonException(message, e, this);
2639
2640 }
2641
2642 try {
2643
2644 thinkTime = (ThinkTime) thinkTimeClass.newInstance();
2645
2646 } catch (InstantiationException e) {
2647
2648 final String message = "Could not create instance of class " +
2649 className + ". Check if it is concrete and if it has "
2650 + "a public constructor with no parameters.";
2651
2652 throw new SinonException(message, e, this);
2653
2654 } catch (IllegalAccessException e) {
2655
2656 final String message = "Could not create instance of class " +
2657 className + ". Check if it is concrete and if it has "
2658 + "a public constructor with no parameters.";
2659
2660 throw new SinonException(message, e, this);
2661
2662 }
2663
2664 thinkTime.setConfiguration(configuration.getThinkTimeProperties());
2665
2666 }
2667
2668 /***
2669 * Sets the HTTP headers of <code>method</code> according to the
2670 * collector's configuration.
2671 *
2672 * @param method a <code>HttpMethod</code> instance.
2673 */
2674 private void setHttpHeaders(HttpMethod method) {
2675
2676 Enumeration keys = httpHeaders.keys();
2677
2678 while (keys.hasMoreElements()) {
2679
2680 String name = (String) keys.nextElement();
2681 String value = httpHeaders.getProperty(name);
2682 method.setRequestHeader(name, value);
2683
2684 }
2685
2686 }
2687
2688 /***
2689 * Stops the execution of this collector.
2690 */
2691 public void stop() {
2692
2693 if (log != null && log.isInfoEnabled()) {
2694 log.info("Stopping collector execution.");
2695 }
2696
2697 this.mustStop = true;
2698
2699 }
2700
2701 /***
2702 * Returns <code>true</code> if the execution of this collector
2703 * was stopped and <code>false</code> otherwise.
2704 * @return <code>true</code> if the execution of this collector
2705 * was stopped and <code>false</code> otherwise.
2706 */
2707 public boolean isStopped() {
2708 return this.mustStop;
2709 }
2710
2711 /***
2712 * Prints the content of <code>someContext</code>
2713 * to a String and returns it.
2714 *
2715 * @param someContext a <code>VelocityContext</code>.
2716 */
2717 private static String printContext(VelocityContext someContext) {
2718
2719 if (someContext == null) {
2720
2721 throw new IllegalArgumentException(
2722 "Parameter someContext can't be null");
2723
2724 }
2725
2726 StringBuffer buffer = new StringBuffer();
2727
2728 Object[] keys = someContext.getKeys();
2729 final int length = keys.length;
2730
2731 for (int i = 0; i < length; i++) {
2732
2733 final Object key = keys[i];
2734 buffer.append(key.toString().trim());
2735 buffer.append("=");
2736 buffer.append(someContext.get((String) key).toString().trim());
2737 buffer.append("");
2738
2739 if (i < length - 1) {
2740 buffer.append(", ");
2741 }
2742
2743 }
2744
2745 return buffer.toString();
2746
2747 }
2748
2749 /***
2750 * (Action, Velocity context) pair.
2751 */
2752 private final static class ActionContextPair {
2753
2754 /***
2755 * Action to be executed.
2756 */
2757 private Action action;
2758
2759 /***
2760 * Context under which the action will be executed.
2761 */
2762 private VelocityContext context;
2763
2764 /***
2765 * Single constructor.
2766 *
2767 * @param action an {@link Action} instance.
2768 * @param context a {@link VelocityContext} instance.
2769 */
2770 public ActionContextPair(Action action, VelocityContext context) {
2771
2772 if (action == null) {
2773
2774 throw new IllegalArgumentException(
2775 "Parameter action can't be null");
2776
2777 }
2778
2779 if (context == null) {
2780
2781 throw new IllegalArgumentException(
2782 "Parameter context can't be null");
2783
2784 }
2785
2786 this.action = action;
2787 this.context = context;
2788
2789 }
2790
2791 /***
2792 * Returns the context.
2793 *
2794 * @return a {@link VelocityContext} instance.
2795 */
2796 public VelocityContext getContext() {
2797 return context;
2798 }
2799
2800 /***
2801 * Returns the action.
2802 *
2803 * @return an {@link Action} instance.
2804 */
2805 public Action getAction() {
2806 return action;
2807 }
2808
2809 /***
2810 * Returns the action pageId and the contents of the context.
2811 *
2812 * @return a <code>String</code>.
2813 */
2814 public String toString() {
2815
2816 StringBuffer buffer = new StringBuffer();
2817
2818 buffer.append("Stack: page ");
2819 buffer.append(action.getPageId());
2820 buffer.append(", context ");
2821 buffer.append(printContext(context));
2822 buffer.append("");
2823
2824 return buffer.toString();
2825
2826 }
2827
2828 }
2829
2830 }