source: main/trunk/greenstone3/src/java/org/greenstone/applet/GsdlCollageApplet/GsdlCollageApplet.java

Last change on this file was 38968, checked in by anupama, 6 weeks ago

GsdlCollageApplet. 1. On stopRunning, DisplayImages may be looping in the graphics thread and in that case would through an exception when interrupted. I know check isStopped() within the loop to more tidily exit in such a case. 2. Finally remembered to handle the case of if there's no GS3 running server or no internet connection of any kind. It used to be the case that GsdlCollageApplet would display the Downloading message forever, noticeable when run as a commandline application and if I forgot to run the GS3 server. Now if downloading fails it will say so and come to halt instead of waiting for downloads and displaying the message Downloading forever. 3. Some useful local changes of debugging output on an exception in CURL.

File size: 40.1 KB
Line 
1package org.greenstone.applet.GsdlCollageApplet;
2
3import org.webswing.toolkit.api.WebswingUtil;
4import org.webswing.toolkit.api.action.WebActionEvent;
5import org.webswing.toolkit.api.action.WebActionListener;
6
7import org.webswing.toolkit.api.lifecycle.WebswingShutdownListener;
8import org.webswing.toolkit.api.lifecycle.OnBeforeShutdownEvent;
9
10import org.webswing.toolkit.api.url.*;
11
12//import java.applet.Applet;
13import javax.swing.JApplet;
14import java.awt.event.ComponentListener;
15import java.awt.event.ComponentEvent;
16import java.awt.event.MouseAdapter;
17import java.awt.event.MouseEvent;
18import java.awt.event.MouseListener;
19import java.awt.event.WindowAdapter;
20import java.awt.event.WindowEvent;
21import java.awt.*;
22import java.net.*;
23
24
25import javax.swing.JFrame;
26import javax.swing.JLabel;
27import java.util.Map;
28import java.util.HashMap;
29
30
31/**
32 *
33 * @author Katrina Edgar
34 * @author David Bainbridge
35 * adjusted for GS3 in 2024 (Anu)
36 *
37 * Main class for the GsdlCollageApplet<br>
38 * Processes the parameters passed through the Applet<br>
39 * Builds an appropriate starting url from these parameters<br>
40 * Creates a storage class for downloaded images<br>
41 * Starts thread to download images and their associated url<br>
42 * Starts thread to display downloaded images on the applet screen<br>
43 */
44
45public class GsdlCollageApplet extends JApplet implements ComponentListener
46{
47 static final int EXTRA_HEIGHT = 30; // for status bar or any extra elements
48 private int X_DIM = 600;
49 private int Y_DIM = 300;
50
51 boolean isWebswing = false; // if run as webswing
52 // To run this GsdlCollageApplet as Application instead of as Applet
53 boolean isRunAsApplet = true;
54 // set only if JPhind object is run as an application
55 URL docBaseURL = null;
56 JLabel statusBar = null;
57 Map<String,String> appParams;
58
59 // package access, used mainly for GS3
60 int gsdlversion = 2;
61 String baseURL = null;
62
63 /** When asked to stop running, this variable will be set to true */
64 private boolean stop_running = false;
65
66 /** Amount of error checking output produced <br>
67 * Ranges from 0 - no output to 3 - maximum output */
68 protected int verbosity_ = 0;
69 /** Indicates whether java2 functionality should be used <br>
70 * If true will allow advanced image processing techniques to occur,
71 * such as fading and colouring using pixel manipulation <br>
72 * If false, images will maintain an alpha value of 1 (appear solid),
73 * newer images will simply be pasted on top of existing images <br> */
74 protected boolean isJava2_ = true;
75 /** Number of nested links to follow When used with greenstone,
76 * controls to which level of the document links will be followed For
77 * example a maximum depth of 1 will mean only the top page of a
78 * document will be examined, while a depth of 2 will include sections
79 * within this document, 3 includes sections within sections, and so
80 * on */
81 protected int maxDepth_ = 3;
82
83 /** Maximum number of images to display on the canvas */
84 protected int maxDisplay_ = 25;
85
86 /** Maximum number of downloaded images to store <br> Prevents applet
87 * from using excessive amounts of bandwidth by downloading too many
88 * images simulataneously*/
89 protected int maxDownloads_ = Integer.MAX_VALUE;
90
91 /** Types of images permitted in the collage, for example gif, jpg,
92 * png... */
93 protected String imageType_ = ".jpg%.png";
94
95 /** Caption of the collage */
96 protected String caption_ = "";
97
98 /** Background color of applet screen */
99 protected Color bgcolor_ = new Color(150, 193, 155);
100
101 /** Time lapse between repainting of the applet */
102 protected int refreshDelay_ = 2000;
103
104 /** Stores an image and url pair and provides associated methods */
105 protected DownloadImages download_images_ = null;
106 /** Downloads images from a starting url, recursively follows nested
107 * links */
108 protected DownloadUrls download_thread_ = null;
109 /** Image processing and placement on applet screen */
110 protected DisplayImages display_thread_ = null;
111
112
113 /** Gets the set, calculated or default width (x dimension) of the applet/application */
114 public int X_DIM() {
115 return X_DIM;
116 }
117 /** Gets the set, calculated or default height (y dimension) of the applet/application */
118 public int Y_DIM() {
119 return Y_DIM;
120 }
121
122 /** Gets verbosity */
123 public int verbosity() { return verbosity_; }
124 /** Gets maximum depth for nested links */
125 public int maxDepth() { return maxDepth_; }
126
127 /** Gets maximum number of images to display */
128 public int maxDisplay() { return maxDisplay_;}
129
130 /** Gets maximum number of images to store during downloading */
131 public int maxDownloads() { return maxDownloads_; }
132 /** Gets the refresh delay used between repaint */
133 public int refreshDelay() { return refreshDelay_; }
134
135 /** Gets parameters from the applet code and stores locally.
136 * Forms a starting url for image retrieval to begin from.
137 * The starting url is formed either by:
138 * * Using the image_url parameter as provided, which is assumed to
139 * be a complete url
140 * * If this parameter does not exist, the assumption is that the
141 * collage is being incorporated with the Greenstone Digital Library
142 * Software. The starting url is formed by concatenating
143 * the gwcgi, collection and classifier parameters as provided.
144 * Then starts downloading and displaying images */
145
146 Thread paint ;
147
148 public GsdlCollageApplet() {
149
150 super();
151
152 // status bar if run as application or as webswing applet,
153 // mimicing running as regular applet which gives us a status bar by default
154 // Useful for Phind, not for Collage where there are layout issues even if hidden
155 //setLayout(new BorderLayout());
156 //if(!isRunAsApplet) {
157 statusBar = null; //new JLabel();
158
159 //this.add(statusBar, BorderLayout.SOUTH);
160
161 ///Window win = SwingUtilities.getWindowAncestor(this);
162 ///if(win instanceof JFrame) {
163 /// JFrame topFrame = (JFrame)win;
164 /// topFrame.add(statusBar, BorderLayout.SOUTH);
165 ///} else { //AppletViewer or browser
166 /// this.add(statusBar, BorderLayout.SOUTH);
167 ///}
168
169 //Dimension d = statusBar.getSize();
170 //d.height = EXTRA_HEIGHT;
171 //statusBar.setPreferredSize(d);
172 //}
173
174 this.addMouseListener(new CollageMouseAdapter());
175 }
176
177 /**
178 * This method is called (by main()) only when this class is run
179 * as an application, not as applet. It prepares the variables and
180 * commandline parameters to be set up, so the rest of the
181 * initialisation can be done in the usual way (with the
182 * Applet.getParameters() method) in the init() method called
183 * hereafter for both application and applet mode.
184 */
185 public void applicationPreInit(String[] args) {
186 // I copied the contents of this contructor for my code for the JPhind.java constructor
187 // This constructor will be used when this JApplet is run as an application
188 // instead of as applet
189 this.isRunAsApplet = false;
190
191 appParams = new HashMap<String,String>((args.length+1)/2);
192 // For GS2, gwcgi = param gwcgi
193 // For GS3, gwcgi = param gwcgi + param library
194 // docBaseURL = starting_url/image_url = param gwcgi
195
196 String key = null;
197 String value = null;
198 for(int i = 0; i < args.length; i++) {
199 if(i%2==0) {
200 key = args[i].substring(2); // remove -- prefix of paramname
201 //System.err.println("got key: " + key);
202 } else {
203 value = args[i];
204 appParams.put(key, value);
205 //System.err.println("got value: " + value);
206
207
208 // HttpUtils.parseQueryString() deprecated, so hacking decode xtra key-value pairs
209 // https://stackoverflow.com/questions/13592236/parse-a-uri-string-into-name-value-collection?page=1&tab=scoredesc#tab-top
210
211 if(key.equals("xtraParams")) {
212 value = value.replace("&amp;", "&"); // just in case we have html entities
213 parseXtraParams(value, "=", "&", appParams);
214 }
215
216 key = value = null;
217 }
218 }
219
220 // Need these parameters set before init(), so that the display works
221 int w = this.getWidth();
222 if(appParams.get("width") != null) {
223 w = Integer.parseInt(appParams.get("width"));
224 }
225 int h = this.getHeight();
226 if(appParams.get("height") != null) {
227 h = Integer.parseInt(appParams.get("height"));
228 }
229 // For applet, w and h may now be set to a positive value. But if GsdlCollageApplet
230 // is run as an application, it won't yet be visible and not have a non-zero dimension.
231 // So use defaults here:
232 this.X_DIM = (w <= 0) ? X_DIM : w;
233 this.Y_DIM = (h <= 0) ? Y_DIM : h;
234
235 // Attempting hack to circumvent division by zero error in MyAffineTransform line 39
236 //DisplayImages.app_x_dim_ = X_DIM;
237 //DisplayImages.app_y_dim_ = Y_DIM;
238
239 try {
240 this.docBaseURL = new URL(appParams.get("baseurl"));
241 } catch(MalformedURLException mue) {
242 mue.printStackTrace();
243 System.err.println("*** Unable to instantiate URL from parameter baseurl: " + appParams.get("baseurl"));
244 System.exit(-1);
245 }
246
247 }
248
249 /**
250 * Overriding (J)Applet method getParameter to first check appParams map
251 * if GsdlCollage was run as an application.
252 * If run as an applet, we still check the appParams first for if the param-name
253 * exists in any xtraParams manually parsed into appParams, before finally
254 * checking the Applet method getParameter().
255 * https://stackoverflow.com/questions/15905127/overridden-methods-in-javadoc
256 */
257 @Override
258 public String getParameter(String name) {
259 if(!isRunAsApplet) {
260 return appParams.get(name);
261 }
262 else {
263 if(appParams != null) {
264 String value = appParams.get(name);
265 if(value != null) {
266 return value;
267 }
268 }
269 return super.getParameter(name);
270 }
271 }
272
273 @Override
274 public URL getDocumentBase() {
275 if(!isRunAsApplet) { // launched as application
276 //System.err.println("*** docBaseURL: " + docBaseURL);
277 return this.docBaseURL;
278 } else {
279 return super.getDocumentBase();
280 }
281 }
282
283 @Override
284 public void showStatus(String msg) {
285 // Either firefox doesn't provide a status bar window for applets any more or webswing
286 // doesn't provide a status window, so we don't see Applet.showStatus() output appear
287 // in webswing-phind and in fact don't even see any Applet statusBar in webswing.
288 // However, since we print useful and interesting information to the status bar,
289 // we'll now always show and print to our manually added statusBar now
290 // not only if(!isRunAsApplet) when we needed to manually create a statusBar.
291 if(this.statusBar != null) {
292 this.statusBar.setText(msg);
293 }
294 if(isRunAsApplet) {
295 super.showStatus(msg);
296 }
297 }
298
299 // Given a string xtraParams of key-value pairs separatod by pairSeparators,
300 // parses out each (key, value) and puts them into the given map, allocating it if
301 // necessary, and returns this map.
302 Map parseXtraParams(String xtraParams, String kvSeparator, String kvPairSeparator, Map map) {
303
304 // String.split() is preferred over Tokenizer but 2nd parameter behaves differently
305 // than I expected.
306 // https://docs.oracle.com/javase/6/docs/api/java/lang/String.html#split%28java.lang.String,%20int%29
307 String[] param_list = xtraParams.split(kvPairSeparator, 0);// 0 means for all occurrences
308
309 if(map == null) {
310 map = new HashMap<String,String>(param_list.length);
311 }
312
313 for(String key_val : param_list) {
314 String[] paramPair = key_val.split(kvSeparator, 2); // get first 2 strings, key and val
315 //System.err.println("key_val: " + key_val);
316 if(paramPair.length == 2) {
317 String xtraParamsKey = paramPair[0];
318 String xtraParamsVal = paramPair[1];
319 // Let's remove any bookending quotes from value, this is necessary for some
320 // values when run as a webswing applet
321 if(xtraParamsVal.startsWith("\"") && xtraParamsVal.endsWith("\"")) {
322 xtraParamsVal = xtraParamsVal.substring(1, xtraParamsVal.length()-1);
323 }
324
325 if (verbosity_ >= 4) {
326 System.err.println("*** xtraParams key - val: " + xtraParamsKey + " - " + xtraParamsVal);
327 }
328 map.put(xtraParamsKey, xtraParamsVal);
329 }
330 }
331
332 return map;
333 }
334
335 public void init()
336 {
337 if (verbosity_ >= 4) {
338 System.err.println("Attempting to retrieve parameters...");
339 }
340
341 // This section is to help *webswing* *applet* use dynamic params in the config file.
342 // Extract anything that's in xtraParams (key=value&k2=v2&... string) into
343 // appParams, so all is ready to call getParameter and get expected params out by name
344 // thereafter.
345 // Basically, this section allows us to support any dynamic parameters getting added
346 // by Javascript to the existing applet parameters defined in webswing.config.in
347 // Here we unpack the xtraParams sent and set them up as regular applet parameters, so
348 // that the rest of the applet init() function can read them in as regular applet params.
349 String xtraParams = getParameter("xtraParams");
350 if(xtraParams != null) {
351 // will optionally create appParams and add the key,value pairs in xtraParams into it
352 // after splitting key::value;;k2::v2 etc
353 appParams = parseXtraParams(xtraParams, "::", ";;", appParams);
354 }
355
356
357 // gets the parameters specified
358 String showStatusBar = getParameter("statusbar");
359 if(statusBar == null) {
360 System.err.println("### Status bar code turned off. Ignoring statusbar settings.");
361 } else {
362 if(showStatusBar == null || showStatusBar.equals("0")) {
363 System.err.println("### Hiding status bar (default)");
364 statusBar.setVisible(false);
365 }
366 }
367 String verbosity_param = getParameter("verbosity");
368 String is_java2_param = getParameter("isJava2");
369 String max_depth_param = getParameter("maxDepth");
370 String max_downloads_param = getParameter("maxDownloads");
371 String max_display_param = getParameter("maxDisplay");
372 String refresh_delay_param = getParameter("refreshDelay");
373 String image_type_param = getParameter("imageType");
374 String bgcolor_param = getParameter("bgcolor");
375 String caption_param = getParameter("caption");
376 String document_root = getParameter("documentroot");
377 String gwcgi = getParameter("gwcgi"); // for GS2
378 String baseurl = getParameter("baseurl"); // for GS3, URL before /library
379
380 String webswing = getParameter("webswing");
381 isWebswing = (webswing == null) ? false : webswing.equals("1");
382
383 String gsdlVersionStr = getParameter("gsdlversion");
384 if(gsdlVersionStr != null) {
385 System.err.println("*** gsdl version: " + gsdlVersionStr);
386 this.gsdlversion = Integer.parseInt(gsdlVersionStr);
387 }
388
389 // Check it isn't null
390 if ((verbosity_param!=null) && (!verbosity_param.startsWith("_"))) {
391 verbosity_ = Integer.parseInt(verbosity_param);
392 }
393 else {
394 verbosity_ = 1;
395 }
396
397
398 if (verbosity_ >= 4) {
399 System.err.println("Got parameters.");
400 }
401
402 if (caption_param != null && !caption_param.startsWith("_")) {
403 caption_ = caption_param;
404 }
405 else {
406 if (verbosity_ >= 4) {
407 System.err.println("No Caption: setting to a space.");
408 }
409 caption_ = " ";
410 }
411
412 if ((bgcolor_param != null) && (!bgcolor_param.startsWith("_"))) {
413 if (bgcolor_param.startsWith("#")) {
414 bgcolor_ = Color.decode(bgcolor_param);
415 }
416 else {
417 String [] c = bgcolor_param.split(",");
418 if (c.length == 3)
419 bgcolor_ = new Color(Integer.parseInt(c[0]), Integer.parseInt(c[1]), Integer.parseInt(c[2]));
420 }
421 if (verbosity_ >= 4){
422 System.err.println("Set BG to be " + bgcolor_.toString());
423 }
424 }
425 else {
426 if (verbosity_ >= 4) {
427 System.err.println("No BG: setting to NZDL green.");
428 }
429 bgcolor_ = new Color(150, 193, 155);
430 }
431
432 if ((image_type_param != null) && (!image_type_param.startsWith("_"))) {
433 imageType_ = image_type_param;
434 }
435
436 if ((is_java2_param == null) || (is_java2_param.equals("auto")) || (is_java2_param.startsWith("_"))) {
437 String version = System.getProperty("java.version");
438 version = version.substring(0, version.lastIndexOf("."));
439 System.err.println("VERSION: " + version);
440 float ver = (new Float(version)).floatValue();
441 isJava2_ = (ver >= 1.2) ? true : false;
442 }
443 else {
444 isJava2_ = (is_java2_param.equals("true")) ? true : false;
445 }
446
447 if ((max_depth_param != null) && (!max_depth_param.startsWith("_"))) {
448 // System.err.println("maxDepth = " + max_depth_param);
449 maxDepth_ = Integer.parseInt(max_depth_param);
450 }
451
452 if ((max_downloads_param!=null) && (!max_downloads_param.startsWith("_"))) {
453 maxDownloads_ = Integer.parseInt(max_downloads_param);
454 System.out.println(" maxDownloads " + maxDownloads_ );
455 maxDownloads_=50;
456 }
457
458 if ((max_display_param!=null) && (!max_display_param.startsWith("_"))) {
459 maxDisplay_ = Integer.parseInt(max_display_param);
460
461 }
462
463 if ((refresh_delay_param!=null) && (!refresh_delay_param.startsWith("_"))) {
464 refreshDelay_ = Integer.parseInt(refresh_delay_param);
465 }
466
467
468 if (document_root !=null){ // e.g. "/greenstone/web/images" for GS2
469 if(document_root.indexOf("/") == 0) {
470 document_root = document_root.substring(1); // takes off first slash
471 }
472 if (document_root.indexOf("/") > 0 ){ // e.g we end up with "greenstone" for GS2, "greenstone3" for GS3
473 document_root = document_root.substring(0, document_root.indexOf("/"));
474 }
475 }
476
477 String image_url = getParameter("imageURL");
478 String image_ignore = getParameter("imageIgnorePrefix");
479 String href_musthave = getParameter("hrefMustHave");
480 String image_mustnothave = getParameter("imageMustNotHave");
481
482 // builds starting url when incorporated with Greenstone
483 if (image_url==null)
484 {
485 String collection_param = getParameter("collection");
486 String classifier_param = getParameter("classifier");
487
488 if(gsdlversion == 2) {
489 // GS2 way
490 String gwcgi_param = gwcgi;
491 gwcgi_param = tidy_URL(gwcgi_param, true); //true to append ? to URL
492 image_url = gwcgi_param + "a=d";
493 image_url += "&c=" + collection_param;
494 image_url += "&cl=" + classifier_param;
495 } else {
496 // Try GS3 way
497 String library = getParameter("library");
498 //String site = getParameter("sitename");
499
500 // starting URL (image_url) might not be base_url. We need to store base_url separately
501 baseURL = tidy_URL(baseurl, false);
502 if(!baseURL.endsWith("/")) {
503 baseURL += "/";
504 }
505
506 // building up to baseURL/LIBNAME/collection/COLNAME/browse/
507 image_url = baseURL + library + "/collection/" + collection_param + "/browse/";
508
509 int index = classifier_param.indexOf(".");
510 if(index == -1) {
511 image_url += classifier_param;
512 } else {
513
514 //"CL2#" + classifier_param;
515 //String superClassifier = classifier_param.substring(0, index);
516 //image_url = image_url + superClassifier + "#" + classifier_param;
517
518 // I'm guessing we want something like
519 //http://localhost:8383/greenstone3/library/collection/smallbea/browse/CL2/3, when classifier_param is CL2.3
520 // to get the classifier page containing the images that are to be turned into a collage?
521 String classifierSuffix = classifier_param.replace(".", "/");
522 image_url = image_url + classifierSuffix;
523 }
524 }
525 }
526
527 // This block was a nice idea had it worked, but it doesn't
528 // work. It's here as sample syntax. Handle getting called by
529 // webswing JavaScript (but only if run in webswing mode):
530 // when the user navigates away from the webswing app's web
531 // page, do cleanup
532 // https://www.webswing.org/docs/23.2/integrate/jslink.html#invoking-java-from-javascript
533 // https://vaadin.com/directory/component/webswing-vaadin1
534 if(isWebswing) {
535
536 //System.err.println("#### isWebswing, about to add listeners");
537
538 WebswingUtil.getWebswingApi().addBrowserActionListener(new WebActionListener() {
539 //@Override
540 public void actionPerformed(WebActionEvent actionEvent) {
541
542 System.err.println("***JavaScript to Java comms: actionPerformed called");
543 //System.err.println("actionEvent = ");
544
545 switch (actionEvent.getActionName()) {
546 case "navigatingAway":
547 // do cleanup
548 System.err.println("@@@@ In Java collage addBrowserActionListener - on navigatingAway called");
549 GsdlCollageApplet.this.stopRunning();
550 break;
551 case "testJavaCall":
552 System.err.println("@@@@ In GsdlCollageApplet's addBrowserActionListener - testJavaCall called");
553 WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", "GsdlCollageApplet got TEST call !!", null);
554 //WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", "GsdlCollageApplet - testjavaCall handler got your message", null);
555 }
556 }
557 });
558 }
559
560 if(isWebswing && isRunAsApplet) {
561
562 // When webswing runs Collage in Applet mode, it's not
563 // shutdown the way we'd like on the javascript kill()
564 // command when the user navigates away from the collage
565 // page, as that only generates a Java WindowClosingEvent
566 // by default if no shutdown handlers registered. And a
567 // WindowClosingEvent only has any effect on applications,
568 // not applets. But we can write our own custom handler to
569 // mimic the applet shutdown sequence of stop() then
570 // destroy() see
571 // https://www.webswing.org/docs/20.2/integrate/api.html
572 // This code also written with the help of webswing's
573 // source code to know the methods in interface
574 // WebswingShutdownListener and the import statements
575 // required for these methods.
576 // After stop() and destroy(), we call System.exit(0) on
577 // top of usual applet behaviour to make sure we get rid
578 // of the applet "application" that webswing launched for
579 // us (it took the applet code and ran it, presumably like
580 // a java program, so a System.exit call may be warranted,
581 // whereas System.exit() isn't called on a regular applet).
582 WebswingUtil.getWebswingApi().addShutdownListener(new WebswingShutdownListener() {
583 // These are official webswing API comments for info
584 /**
585 * Invoked before Webswing requests application to exit.
586 * Do not execute long operations in this listener - listener execution will be interrupted if blocking for &gt; 3 seconds and the application will exit without delay.
587 * Connection to server is still open when this callback is triggered.
588 * This method can delay the shutdown. Calling {@link WebswingApi#resetInactivityTimeout()} within the delay period will cancel the shutdown sequence.
589 *
590 * This method is not called on the event dispatch thread.
591 *
592 * @param event Event contains the reason of this shutdown - either triggered from Admin console's rest interface or by inactivity
593 * @return number of seconds to delay the shutdown, returning 0 will cause shutdown without delay (even if {@link WebswingApi#resetInactivityTimeout()} has been called)
594 */
595 public int onBeforeShutdown(OnBeforeShutdownEvent event) {
596 return 0; // seconds to delay before starting shutdown procedure
597 }
598
599 /**
600 * Invoked when Webswing requests swing application to exit.
601 * This method should cause this process to exit (not necessarily in the same thread).
602 * When this method is called, connection to server is already closed
603 *
604 * This method is not called on the event dispatch thread.
605 */
606 public void onShutdown() {
607 // https://docs.oracle.com/javase%2F7%2Fdocs%2Fapi%2F%2F/java/applet/Applet.html#destroy()
608 // destroy(): "Called by the browser or
609 // applet viewer to inform this applet that
610 // it is being reclaimed and that it should
611 // destroy any resources that it has
612 // allocated. The stop method will always be
613 // called before destroy."
614 // We're like the browser now and so we should
615 // honour the above contract.
616 if (verbosity_ >= 3) {
617 System.err.println("------- GsdlCollageApplet WebswingShutdownListener.onShutdown() - our custom behaviour");
618
619 // This message is sent to JavaScript too late: webpage has already
620 // finished unloading and the JS webswingInstance can't receive msgs.
621 // if(isWebswing && isRunAsApplet) {
622 // WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", "GsdlCollageApplet running as webswing applet - quitting now", null);
623 // }
624 }
625 stop();
626 destroy();
627 // Even though we're an applet and applets
628 // shouldn't system.exit(0) we're running our
629 // applet version of the code through
630 // webswing. And this handler/webswing stands
631 // in place of a browser and controls what
632 // *ought* to happen if a browser doesn't take
633 // care of the applet life cycle and we ought
634 // to do it (here in the webswing handler).
635 System.exit(0);
636 }
637 });
638 }
639
640
641 MediaTracker trk = new MediaTracker(this);
642
643 // creates a class to store the image and its associated url
644 download_images_ = new DownloadImages( this, verbosity_,isJava2_ );
645 // starts the download image thread with the starting url
646
647 download_thread_ = new DownloadUrls(this, download_images_,
648 image_url, href_musthave, image_mustnothave,
649 image_ignore, imageType_,document_root,verbosity_,trk);
650 // If GsdlCollageApplet is launched as a *webswing applet*, it
651 // doesn't have dimensions, isn't showing nor is it even
652 // displayable at this point in the code. Only when webswing
653 // resizes the GsdlCollageApplet Component, does it have
654 // dimensions and do we know for sure it is showing.
655 if(isShowing() && isDisplayable()) { // case of: if(!(isRunAsApplet && isWebswing)) {
656 if (verbosity_ >= 4) {
657 System.err.println("**** Creating display thread");
658 }
659 // starts the display image thread with the currently downloaded images
660 display_thread_ = new DisplayImages(this, download_images_,isJava2_, bgcolor_);
661 } else {
662 if (verbosity_ >= 4) {
663 System.err.println("**** GsdlCollageApplet.init() - display_thread_ not yet showing.");
664 System.err.println("**** Will create and start display_thread_ later, in ComponentListener.resize()");
665 }
666 }
667 // The display_thread_ needs the component showing before it can be started up.
668 // It was showing already by this point for the applet in non-webswing mode (when
669 // using the appletviewer),
670 // and I got it showing by this point when run as a cmdline or webswing application.
671 // But when the applet is run in webswing mode, it's not yet showing
672 // at this point! A componentListener allows us to handle componentShown events on
673 // the JComponent of this JApplet. However, the applet in webswing mode doesn't trigger
674 // that, it only triggers componentResized. So maybe webswing starts it off as 0 size?
675 this.addComponentListener(this);
676
677
678 }
679
680 public void componentHidden(ComponentEvent e) {
681 if (verbosity_ >= 4) {
682 System.err.println(e.getComponent().getClass().getName() + " --- Hidden");
683 }
684 }
685
686 public void componentMoved(ComponentEvent e) {
687 if (verbosity_ >= 4) {
688 System.err.println(e.getComponent().getClass().getName() + " --- Moved");
689 }
690 }
691
692 public void componentResized(ComponentEvent e) {
693 if(display_thread_ == null) {
694 if (verbosity_ >= 4) {
695 System.err.println(e.getComponent().getClass().getName() + " --- Resized");
696 System.err.println("Ready to instantiate display thread");
697 }
698 display_thread_ = new DisplayImages(this, download_images_,isJava2_, bgcolor_);
699 display_thread_.start();
700 }
701 }
702
703 public void componentShown(ComponentEvent e) {
704 if(display_thread_ == null) {
705 if (verbosity_ >= 4) {
706 System.err.println("@@@ " + e.getComponent().getClass().getName() + " --- Shown");
707 System.err.println("GsdlCollageApplet ready to instantiate display thread");
708 }
709 display_thread_ = new DisplayImages(this, download_images_,isJava2_, bgcolor_);
710 display_thread_.start();
711 }
712 }
713
714 public JLabel getStatusBar() {
715 return this.statusBar;
716 }
717
718 // TODO: Do I need to follow
719 // https://stackoverflow.com/questions/5861894/how-to-synchronize-or-lock-upon-variables-in-java
720 // I just followed the Java API's recommendations about what to do
721 // as replacement around Thread.stop() and Thread.destroy() being
722 // deprecated (note: not Applet.stop() and
723 // Applet.destroy(). https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#stop--
724 public void stopRunning() {
725 if(verbosity_ >= 3) {
726 System.err.println("**** GsdlCollageApplet.stopRunning() called");
727 }
728
729 stop_running = true;
730 if(download_thread_ != null) {
731 download_thread_.stopRunning();
732 }
733 if(display_thread_ != null) {
734 display_thread_.stopRunning();
735 }
736
737 // The Display Thread?
738 //if(!Thread.currentThread().isInterrupted()) {
739 // Thread.currentThread().interrupt();
740 //}
741 }
742
743 public boolean isStopping() {
744 return stop_running;
745 }
746
747 // Applet's mouseDown() is deprecated, so we implement mousePressed
748 protected class CollageMouseAdapter extends MouseAdapter {
749
750 /** Goes to the url associated with the image that is clicked on screen<br>
751 * Displays the url containing the image in a new window */
752 //public boolean mouseDown(Event event, int x, int y)
753 public void mousePressed(MouseEvent e)
754 {
755 int x = e.getX();
756 int y = e.getY();
757
758 // determines which image was clicked on
759 CollageImage cimage = GsdlCollageApplet.this.display_thread_.clickedOnImage(x,y);
760 // if they were clicking on an image (as opposed to background)
761 if (cimage != null)
762 {
763 System.err.println("Click on image: from url = " + cimage.from_url_);
764 try {
765 // displays the associated url in a new window
766 URL from_url = null;
767
768 java.util.regex.Pattern p = java.util.regex.Pattern.compile("cl=CL\\d(\\.\\d)*\\s");
769 java.util.regex.Matcher m = p.matcher(cimage.from_url_.trim()+" ");
770
771 if (m.find() ){
772 from_url = cimage.url_;
773 }
774 else{
775 from_url = new URL(cimage.from_url_ + "#" + cimage.name_);
776 }
777
778 if(isRunAsApplet) {
779 GsdlCollageApplet.this.getAppletContext().showDocument(from_url,"gsdlDoc");
780 } else if(isWebswing) {
781 WebswingUtil.getWebswingApi().sendActionEvent("openURL",
782 from_url.toString() + " - " +"gsdlDoc",
783 null); // window name is gsdlDoc
784 } else {
785 System.err.println("@@@GsdlCollageApplet.CollageMouseAdapter.mousePressed()"
786 + "\n\topening url " + from_url + "\n\t"
787 + "for non-applets and non-webswing applications is not yet implemented.");
788 }
789 }
790 catch (MalformedURLException ex) {
791 ex.printStackTrace();
792 }
793
794 }
795 }
796 }
797
798 /** Start threads for downloading and displaying images */
799 public void start()
800 {
801 download_thread_.start();
802
803 if(display_thread_ != null) {
804 //if(!(isRunAsApplet && isWebswing)) { // then display_thread_ is instantiated at this point
805 // start it
806 display_thread_.start();
807 }
808
809 paint = new Thread(new Runnable(){
810 public void run() {
811 try {
812
813 Thread curr_thread = Thread.currentThread();
814
815 while (curr_thread == paint) {
816
817 repaint();
818
819 Thread.sleep(2000);
820 if(download_thread_.wasUnableToDownload()) {
821 repaint();
822 Thread.sleep(2000);
823 String msg = "@@@ GsdlCollageApplet is unable to download. (No internet?) Stopping.";
824 System.err.println(msg);
825 if(isWebswing) {
826 WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", msg, null);
827 }
828 if(!isRunAsApplet) {
829 System.exit(-1);
830 } else {
831 stopRunning();
832 return;
833 }
834 }
835 curr_thread = Thread.currentThread();
836 }
837
838
839 } catch (Exception e) {
840
841 }
842 }
843
844
845 });
846
847 paint.start();
848
849 }
850
851 /** Stops threads for downloading and displaying images */
852 public void stop()
853 {
854 if(verbosity_ >= 3) {
855 System.err.println("\n\n@@@@ GsdlCollageApplet.stop() called: stopping threads...");
856 }
857 //download_thread_.stop();
858 //display_thread_.stop();
859 stopRunning();
860 // TODO: maybe we want to unset the interrupt variable on the threads to be able to
861 // restart/resume on start() getting called? No: because we system.exit() in the
862 // webswing shutdown handler we implemented.
863 }
864
865 /** Destroys threads for downloading and displaying images.
866 * Java API method specification for destroy(): "Called by the
867 * browser or applet viewer to inform this applet that it is being
868 * reclaimed and that it should destroy any resources that it has
869 * allocated. The stop method will always be called before destroy."
870 */
871 public void destroy()
872 {
873 if(verbosity_ >= 3) {
874 System.err.println("@@@@ GsdlCollageApplet.destroy() called");
875 }
876 download_thread_ = null;
877
878 display_thread_ = null;
879
880 }
881
882
883 /** Repaints the applet */
884 public void update(Graphics g)
885 {
886 // System.err.println("Update called");
887 paint(g);
888 }
889
890 /** Repaints the applet using the paint method of the thread that is
891 * currently displaying images */
892 public void paint(Graphics g) {
893
894 if (display_thread_!=null)
895 {
896
897 //display_thread_.display_collage();
898 display_thread_.paint(g);
899 }
900 else
901 {
902 System.err.println("Applet still trying to paint!!");
903 }
904
905 }
906
907
908 /** Ensures a URL address (as string) has a protocol, host, and file.
909 *
910 * If the URL is a CGI script URL, it should be tidied up so that it is
911 * appropriate to tag attrib=value pairs on the end. This means it
912 * must either end with a "?" or (if it contains a question-mark
913 * internally) end with a "&". */
914 String tidy_URL(String address, boolean isCGI) {
915
916 // System.err.println("tidy URL: " + address);
917
918 // make sure the URL has protocol, host, and file
919 if (address.startsWith("http")) {
920 // the address has all the necessary components
921 } else if (address.startsWith("/")) {
922 // there is not protocol and host
923 URL document = getDocumentBase();
924 String port = "";
925 if (document.getPort()!=-1) {
926 port = ":" + document.getPort();
927 }
928 address = "http://" + document.getHost() + port + address;
929 } else {
930 // this URL is relative to the directory the document is in
931 URL document = getDocumentBase();
932 String directory = document.getFile();
933 int end = directory.lastIndexOf('/');
934 String port = "";
935 if (document.getPort()!=-1) {
936 port = ":" + document.getPort();
937 }
938 directory = directory.substring(0,end + 1);
939 address = "http://" + document.getHost() + port + directory + address;
940
941 }
942
943 // if the URL is a cgi script, make sure it has a "?" in ti,
944 // and that it ends with a "?" or "&"
945 if (isCGI) {
946 if (address.indexOf((int) '?') == -1) {
947 address = address + "?";
948 } else if (!address.endsWith("?")) {
949 address = address + "&";
950 }
951 }
952
953 return address;
954 }
955
956
957 public static void printUsage() {
958 System.err.println("Params needed include: --gsdlversion <3/2> [--statusbar 0/1] [--baseurl <GS3 DL baseURL>/--gwcgi <library.cgi URL>] --library l --collection c --classifier cl --imageType \".jpg%.png\" --imageMustNotHave \"interfaces/\" [--hrefMustHave \"LIBRARYNAME/sites/SITENAME/collect/COLNAME\"] -documentroot greenstone3 [--webswing <1/0>] [--verbosity <v>] --maxDepth 500 --maxDisplay 25 --refreshDelay 1500 --isJava2 auto --bgcolor \"#96c29a\" [--xtraParams <key1=value1&key2=value2&...]");
959 System.err.println("To run as webswing application, additionally pass in --webswing 1");
960 }
961
962 // To also be able to run this applet as an application, need a main method
963 /**
964 * After building the GS3 Multimedia collection, try running this Application as:
965 *
966 java -cp ./web/applet/GsdlCollageApplet.jar:./web/WEB-INF/lib/log4j-1.2.8.jar:./web/WEB-INF/classes:./web/ext/webswing/api/webswing-api.jar org.greenstone.applet.GsdlCollageApplet.GsdlCollageApplet --statusbar 0 --baseurl "http://localhost:8383/greenstone3/" --library library --collection smallbea --gsdlversion 3 --hrefMustHave "library/collection/smallbea/browse/CL3" --documentroot greenstone3 --verbosity 3 --imageType ".jpg%.png" --imageMustNotHave "interfaces/" --classifier "CL3.1" --maxDepth 500 --maxDisplay 25 --refreshDelay 1500 --isJava2 auto --bgcolor "#96c29a" --width 645 --height 780
967 *
968 * If wanting to run this with the appletviewer, need to ensure a copy of webswing-api.jar exists in web/applet
969 * as specified in classifier.xsl's applet element's tag (because of the webswing import statements I think).
970 * Then ensure the GS3 server is running and you've built your collection with the collage classifier
971 * and run the appletviewer with the collage classifier's URL embedded in quotes, e.g:
972 * appletviewer "http://localhost:8383/greenstone3/library/collection/smallbea/browse/CL3"
973 * Note that appletviewer is being deprecated in Java and may not be available in future Java downloads.
974 * Sadly, the appletviewer runs in a sort of sandbox that doesn't allow downloading from URLs, so the applet window
975 * appears and shows an infinite message it's trying to download, but fails to do so because of security exceptions.
976 * But at least it can confirm for us the applet is being loaded in and that the applet lifecycle has started.
977 */
978 public static void main(String[] args) {
979 if(args.length == 0) {
980 printUsage();
981 } else if(args[0].equals("--help") || args[0].equals("-h")) {
982 printUsage();
983 } else {
984 JFrame frame = new JFrame("Collage Applet as Application");
985 GsdlCollageApplet collageApp = new GsdlCollageApplet();
986 frame.getContentPane().add(collageApp, BorderLayout.CENTER);
987 //frame.setSize(X_DIM, Y_DIM); //Y_DIM+EXTRA_HEIGHT);
988 frame.setVisible(true);
989 // https://stackoverflow.com/questions/19433358/difference-between-dispose-and-exit-on-close-in-java
990 // default: https://docs.oracle.com/javase/8/docs/api/javax/swing/JFrame.html#EXIT_ON_CLOSE
991 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Running as an application. But don't do EXIT_ON_CLOSE in Applet mode!
992
993 // prepare to run the collage applet as an application
994 collageApp.applicationPreInit(args);
995 // It's now figured out the dimensions based on anything specified, so set frame size
996 frame.setSize(collageApp.X_DIM(), collageApp.Y_DIM()); //Y_DIM+EXTRA_HEIGHT);
997
998 // status bar code. Not ideal, but had to add it here to get relative dimensions right
999 JLabel statusBar = collageApp.getStatusBar();
1000 if(statusBar != null) {
1001 collageApp.setLayout(new BorderLayout());
1002
1003 frame.add(statusBar, BorderLayout.SOUTH);
1004 Dimension d = statusBar.getSize();
1005 d.height = EXTRA_HEIGHT;
1006 d.width = collageApp.getWidth();
1007 statusBar.setPreferredSize(d);
1008 //statusBar.setPreferredSize(new Dimension(collageApp.getWidth(), EXTRA_HEIGHT));
1009 }
1010
1011 // Run it at last: manually calling (J)Applet methods init() and start()
1012 collageApp.init();
1013
1014 collageApp.showStatus("Collage application running");
1015 collageApp.start();
1016
1017 // When we terminate the application, need to manually call the applet method stop()
1018 // except we're already doing the same things here.
1019 //https://docs.oracle.com/javase/tutorial/uiswing/events/windowlistener.html#windowfocuslistener
1020 frame.addWindowListener(new WindowAdapter() {
1021 public void windowClosing(WindowEvent e) {
1022 if(collageApp.isWebswing) {
1023 WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", "GsdlCollageApplet run as application - quitting now", null);
1024 }
1025 collageApp.showStatus("Stopping threads");
1026 System.err.println("\n\n@@@ Closing collage Application: stopping threads...");
1027 collageApp.stopRunning();
1028
1029 //collageApp.stop();
1030 //collageApp.destroy();
1031 }
1032 });
1033
1034 }
1035 }
1036}
Note: See TracBrowser for help on using the repository browser.