source: trunk/gsdl/src/phind/client/Phind.java@ 1640

Last change on this file since 1640 was 1640, checked in by paynter, 24 years ago

Resolve partial URLs like "phindcgi" and /cgi-bin/library" with respect to
the URL of the document containing the applet. Also lets us pass in extra
parameters to cgi scripts, which will let us keep the Greenstone settings
stored in the "e=...." argument to library.

  • Property svn:keywords set to Author Date Id Revision
File size: 19.6 KB
Line 
1/**********************************************************************
2 *
3 * Phind.java -- the Phind java applet
4 *
5 * Copyright 1997-2000 Gordon W. Paynter
6 * Copyright 2000 The New Zealand Digital Library Project
7 *
8 * A component of the Greenstone digital library software
9 * from the New Zealand Digital Library Project at the
10 * University of Waikato, New Zealand.
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *
26 *********************************************************************/
27
28
29/*********************************************************************
30
31To use the applet, you'll need to embed it in a web page as follows:
32
33<APPLET CODE="Phind.class" WIDTH=500 HEIGHT=500>
34 <PARAM NAME=phindcgi VALUE="http://kowhai/cgi-bin/phindcgi">
35 <PARAM NAME=library VALUE="http://kowhai/cgi-bin/library">
36 <PARAM NAME=backdrop VALUE="http://kowhai/~paynter/transfer/phindtest/green1.jpg">
37 <PARAM NAME=collection VALUE="fao.org">
38 <PARAM NAME=orientation VALUE="vertical">
39 <PARAM NAME=depth VALUE="2">
40 <PARAM NAME=fontsize VALUE="12">
41 <PARAM NAME=blocksize VALUE="10">
42 The Phind java applet.
43</APPLET>
44
45You may have problems with Java applet security. Java applet's can only
46open socket connections (including the HTTP connections the applet uses
47to get data) to the same server the applet was loaded from. This means
48that your phindcgi, library, and (optional) backdrop URLs must be on the
49same machine that your web page was loaded from.
50
51**********************************************************************
52
53The applet comprises several classes:
54
551. Phind (this file) is the applet class, loaded by the browser.
56 It also handles network connections.
572. ResultDisplay is a Panel that sits in the applet and displays
58 things; displays are connected in a doubly-linked list.
593. ResultBox holds the results of a query. Result boxes are shown
60 to the user through ResultDisplays. Another doubly-linked list.
614. ResultTitle acts as a caption to a ResultBox describing its contents.
625. ResultCanvas is what the ResultBox data is drawn on. It draws the
63 words on the screen, and handles user input from the mouse.
646. ResultItem represents a single result object (a phrase or document).
657. PhindTitle is for drawing backdrops in ResultDisplays.
66
67**********************************************************************/
68
69
70import java.awt.BorderLayout;
71import java.awt.Button;
72import java.awt.Choice;
73import java.awt.Color;
74import java.awt.Component;
75import java.awt.Dimension;
76import java.awt.Event;
77import java.awt.FlowLayout;
78import java.awt.Font;
79import java.awt.GridLayout;
80import java.awt.Image;
81import java.awt.Label;
82import java.awt.Panel;
83import java.awt.TextField;
84
85import java.net.URL;
86import java.io.DataInputStream;
87
88import java.net.Socket;
89import java.net.InetAddress;
90import java.net.UnknownHostException;
91import java.io.IOException;
92
93import java.util.Vector;
94import java.util.Date;
95
96public class Phind extends java.applet.Applet {
97
98 // elements of the page
99 Label titleLabel;
100 TextField wordField;
101 Button searchButton, prevButton, nextButton;
102
103 // Appearance parameters
104 public boolean vertical;
105 public int depth;
106 public int fontSize;
107 public Font plainFont, boldFont;
108
109 // Do we want a background image in the applet?
110 String backdrop_address;
111 public boolean showImage;
112 public Image backgroundImage;
113
114 // Holders for the downloaded data
115 Panel resultPanel;
116 ResultDisplay firstDisplay, lastDisplay;
117
118 // the mode of operation
119 int mode;
120 final int initMode = 0;
121 final int idleMode = 1;
122 final int searchMode = 2;
123
124 // what to search for
125 String collection;
126 int phraseBlockSize;
127
128 //int stopwordLevel = 1;
129 //int lowFrequencyCutoff = 0;
130 //int stemming = 0;
131
132 // where to search
133 Socket connection;
134 String library_address, phindcgi_address;
135 URL library, phindcgi;
136
137 String search_url;
138
139 // browser details
140 String localHost;
141
142 // The time at which the last query finished
143 Date lastQueryEndTime;
144
145 // lastQueryEndTime is stored to ensure a 1 second gap between a query
146 // returning and a new one beginning. It is needed because the FAO
147 // folks in Rome have a huge lag, and frquently click several times
148 // while they wait; these clicks are turned into new queries, which
149 // they await again. It is no elegant solution, but it seems like the
150 // easiest, given that I don't know threads.
151 // 1. The search button is easy to disable, and is disabled when a
152 // socket connection is in progress.
153 // 2. ResutCanvas widgets can'r be similarly disabled because the
154 // browser hides or wipes them, which looks very bad.
155 // 3. I cannot just ignore clicks on canvasses because the browser
156 // caches the clicks while the socket connection is going on, and then
157 // sends them through afterwards, when the canvas is accepting clicks
158 // again.
159 // 4. Current sequence of events is to record the time the last query
160 // ends, then whenever a click happens make sure a second has past. if
161 // you double-click the the first query is sent, returns, end-tie is
162 // set, and the second (and any others made during query time) is
163 // *immediately* processed, but since 1 second hasn't past it is
164 // ignored.
165
166
167 public String getAppletInfo() {
168 return "Phind by Gordon Paynter ([email protected]). Copyright 1997-2000.";
169 }
170
171 public void init() {
172
173 // default search variable values
174 mode = initMode;
175 lastQueryEndTime = new Date();
176
177 // What is this collection called?
178 try {
179 collection = new String(getParameter("collection"));
180 } catch (Exception e) {
181 System.err.println("Phind: You must specify a collection.");
182 stop();
183 }
184 System.out.println("Phind collection: " + collection);
185
186 // Where do we get the phind data
187 try {
188 phindcgi_address = new String(getParameter("phindcgi"));
189 } catch (Exception e) {
190 System.err.println("Phind: You must specify a URL for phindcgi.");
191 stop();
192 }
193 phindcgi_address = tidy_URL(phindcgi_address, true);
194 System.out.println("Phind phindcgi: " + phindcgi_address);
195
196 // Where is the collection served from
197 try {
198 library_address = new String(getParameter("library"));
199 } catch (Exception e) {
200 System.err.println("Phind: You must specify a URL for library.");
201 stop();
202 }
203 library_address = tidy_URL(library_address, true);
204 System.out.println("Phind library: " + library_address);
205
206 // Should we display a background image?
207 showImage = false;
208 try {
209 backdrop_address = new String(getParameter("backdrop"));
210 backdrop_address = tidy_URL(backdrop_address, false);
211 URL backdrop_url = new URL(backdrop_address);
212 backgroundImage = getImage(backdrop_url);
213 showImage = true;
214 System.out.println("Phind backdrop: " + backdrop_address);
215 } catch (Exception e) {
216 System.out.println("Phind backdrop not set");
217 }
218
219 // Is there a search URL for this collection?
220 try {
221 search_url = new String(getParameter("search_url"));
222 System.out.println("Phind search URL: " + search_url);
223 } catch (Exception e) {
224 System.out.println("Phind search_url not set.");
225 }
226
227 // Are the windows arranged vertically or horizontally
228 vertical = true;
229 try {
230 if (getParameter("orientation").toLowerCase().startsWith("h")) {
231 vertical = false;
232 System.out.println("Phind orientation: horizontal");
233 }
234 } catch (Exception e) {
235 System.out.println("Phind orientation: vertical");
236 }
237
238 // How many phind windows are there?
239 Integer temp;
240 try {
241 temp = Integer.valueOf(getParameter("depth"));
242 depth = temp.intValue();
243 } catch (Exception e) {
244 temp = null;
245 depth = 3;
246 }
247 System.out.println("Phind depth: " + depth);
248
249 // How many phrases should we fetch at any given time?
250 try {
251 temp = Integer.valueOf(getParameter("blocksize"));
252 phraseBlockSize = temp.intValue();
253 } catch (Exception e) {
254 temp = null;
255 phraseBlockSize = 10;
256 }
257 System.out.println("Phind phrase block size: " + phraseBlockSize);
258
259 // How large should the font be?
260 try {
261 temp = Integer.valueOf(getParameter("fontsize"));
262 fontSize = temp.intValue();
263 } catch (Exception e) {
264 temp = null;
265 fontSize = 12;
266 }
267 System.out.println("Phind font size: " + fontSize);
268
269 // browser details
270 try {
271 localHost = InetAddress.getLocalHost().toString();
272 } catch (Exception e) {
273 localHost = "Can't get client host.";
274 }
275 System.out.println("Phind client: " + localHost);
276 System.out.println("");
277
278 // The phind applet layout manager
279 setLayout(new BorderLayout());
280
281 // initialise the user interface
282 setBackground(Color.white);
283 plainFont = new Font("Helvetica", Font.PLAIN, fontSize);
284 boldFont = new Font("Helvetica", Font.BOLD, fontSize);
285
286 // the main Result area
287
288 // the Panel containing the displays is in the center of the display
289 resultPanel = new Panel();
290 if (vertical) {
291 resultPanel.setLayout(new GridLayout(depth,1,0,2));
292 } else {
293 System.out.println("horizontal");
294 resultPanel.setLayout(new GridLayout(1,depth,2,0));
295 }
296 add("Center", resultPanel);
297
298 // Create ResultDisplays and place in the interface
299 ResultDisplay d1, d2 = null;
300 firstDisplay = new ResultDisplay(this, null);
301 resultPanel.add(firstDisplay);
302
303 d1 = firstDisplay;
304 for (int i = 2; i <= depth; i++) {
305 d2 = new ResultDisplay(this, d1);
306 resultPanel.add(d2);
307 d1 = d2;
308 }
309
310 lastDisplay = d2;
311
312
313 // The "control panel"
314 Panel p1 = new Panel();
315 add("North", p1);
316 p1.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
317
318 searchButton = new Button("Search");
319 searchButton.setFont(boldFont);
320 p1.add(searchButton);
321
322 Label tempLabel = new Label(" for");
323 tempLabel.setFont(boldFont);
324 p1.add(tempLabel);
325
326 wordField = new TextField(12);
327 wordField.setFont(boldFont);
328 p1.add(wordField);
329
330 Label temp2 = new Label(" ");
331 p1.add(temp2);
332
333 prevButton = new Button("Previous");
334 prevButton.setFont(boldFont);
335 prevButton.disable();
336 p1.add(prevButton);
337
338 nextButton = new Button(" Next ");
339 nextButton.setFont(boldFont);
340 nextButton.disable();
341 p1.add(nextButton);
342
343 // lets get started then
344 setStatus("Welcome to Phind.");
345 mode = idleMode;
346 }
347
348
349 void setStatus(String status) {
350 showStatus(status);
351 }
352
353 public boolean action(Event evt, Object arg) {
354
355 if (evt.target == searchButton) {
356 searchForWord();
357 } else if (evt.target == wordField) {
358 searchForWord();
359 } else if (evt.target == prevButton) {
360 shiftPrevious();
361 } else if (evt.target == nextButton) {
362 shiftNext();
363 } else {
364 System.out.println("unknown action: " + evt.toString() + ", object: " + arg.toString());
365 }
366 return true;
367 }
368
369
370 // Search for a word
371 //
372 // called when the "Search" Button is pressed
373 void searchForWord() {
374
375 if (mode == idleMode) {
376
377 // transform the word to an appropriate search key
378 String searchWord = wordField.getText().trim().toLowerCase();
379 for (int i = 0; i < searchWord.length(); i++) {
380 if ((searchWord.charAt(i) < 'a') || (searchWord.charAt(i) > 'z')) {
381 searchWord = searchWord.substring(0, i);
382 }
383 }
384 wordField.setText(searchWord);
385
386 // look up the word
387 if (searchWord.length() > 1) {
388 setStatus("searching for \"" + searchWord + "\"");
389 firstDisplay.emptyContents();
390 ResultBox result = lookupPhraseOnServer(null, false, searchWord, searchWord, 2);
391
392 // if there is an error, return
393 if (result == null) {
394 setStatus("No results for \"" + searchWord + "\"");
395 return;
396 }
397
398 // display the result
399 result.display = firstDisplay.display(result);
400 result.resize(result.display.size());
401 result.paintAll(result.getGraphics());
402 }
403
404 enablePreviousAndNext();
405 }
406 }
407
408
409 // Search for a phrase
410 //
411 // If querymode is 2, the user has clicked on a phrase.
412 // If querymode is 3, the user has requested more phrases.
413 // If querymode is 4, the user has requested more documents.
414 void searchForPhrase(ResultBox source, String key, String phrase, int queryMode) {
415
416 // System.out.println("searchForPhrase: " + key + " " + phrase + " " + queryMode);
417
418 if (mode == idleMode) {
419
420 // If we are going to replace the next ResultDisplay, then empty it
421 if (queryMode <= 2) {
422 if (source.display.next != null) source.display.next.emptyContents();
423 }
424
425 // look up the word
426 setStatus("Searching for \"" + phrase + "\"");
427 ResultBox result = lookupPhraseOnServer(source, true, key, phrase, queryMode);
428 if (result == null) {
429 setStatus("No result for \"" + phrase + "\"");
430 return;
431 }
432
433 // If this is not already displayed, display it in the last free spot
434 if (queryMode <= 2) {
435 result.display = lastDisplay.display(result);
436 result.resize(result.display.size());
437 result.paintAll(result.getGraphics());
438 }
439
440 enablePreviousAndNext();
441 }
442 }
443
444
445 // Look up a phrase (or symbol) on the server
446 //
447 // Arguments are the source of the query (a ResultBox, or null if the
448 // query comes from hitting the search button), the key to search for
449 // (the text of a phrase or a symbol number), the phrase as a string,
450 // and the query mode.
451 // Query modes are:
452 // 0 = obsolete
453 // 1 = obsolete
454 // 2 = get first N phrases and URLs,
455 // 3 = get another N phrases into same window
456 // 4 = get another N documents into same window
457
458 ResultBox lookupPhraseOnServer(ResultBox source,
459 boolean keyKnown, String key, String phrase,
460 int queryMode) {
461 searchButton.disable();
462 mode = searchMode;
463 ResultBox r = null;
464
465 if (queryMode <= 2) {
466 r = new ResultBox(this, collection, key, phrase, source);
467 } else if ((queryMode == 3) || (queryMode == 4)) {
468 r = source;
469 }
470
471 try {
472 queryServer(keyKnown, key, queryMode, r);
473 } catch (Exception e) {
474 System.out.println("Phind query error: " + e.toString());
475 setStatus("Query error: " + e.toString());
476 mode = idleMode;
477 searchButton.enable();
478 return null;
479 }
480
481 // The query is finished
482 setStatus(r.c.numberOfPhrases + " results containing \"" + phrase + "\"");
483 mode = idleMode;
484 searchButton.enable();
485 lastQueryEndTime = new Date();
486
487 return r;
488 }
489
490
491 // Query the phindcgi program
492 //
493 // Send a query (a word or symbol number) to the server
494 // and pass the response to a ResultBox.
495
496 void queryServer(boolean keyKnown, String word, int queryMode, ResultBox area)
497 throws IOException {
498
499 // Build the query
500 String query = phindcgi_address + "x=1&c=" + collection;
501
502 if (keyKnown) {
503 query = query + "&n=" + word;
504 } else {
505 query = query + "&p=" + word;
506 }
507
508
509 // Specify the set of results to return
510 int first_e = 0;
511 int last_e = 0;
512 int first_d = 0;
513 int last_d = 0;
514
515 // the initial query
516 if (queryMode <= 2) {
517 last_e = phraseBlockSize;
518 last_d = phraseBlockSize;
519 }
520
521 // add phrases to an existing result set
522 else if (queryMode == 3) {
523 first_e = area.nextPhraseBlock * phraseBlockSize;
524 area.nextPhraseBlock++;
525 last_e = area.nextPhraseBlock * phraseBlockSize;
526 }
527
528 // add documents to existing result set
529 else if (queryMode == 4) {
530 first_d = area.nextDocumentBlock * phraseBlockSize;
531 area.nextDocumentBlock++;
532 last_d = area.nextDocumentBlock * phraseBlockSize;
533 }
534
535 query = query + "&f=" + first_d + "&d=" + last_d
536 + "&g=" + first_e + "&e=" + last_e;
537
538 // Send the query to the phindcgi program
539 System.out.println("sending query: " + query);
540 try {
541 phindcgi = new URL(query);
542 DataInputStream in = new DataInputStream(phindcgi.openStream());
543 byte[] buffer;
544 int availableBytes = 0;
545
546 while (!area.finished) {
547 availableBytes = in.available();
548 if (availableBytes == 0) {
549 // if i had threads i'd do a wait here for 1 second
550 } else {
551 buffer = new byte[availableBytes];
552 in.read(buffer);
553 area.parseBytes(buffer);
554 }
555 }
556
557 in.close();
558 } catch (Exception e) {
559 System.err.println( "Error sending query to phindcgi: " + e);
560 }
561 area.repaint();
562 }
563
564
565 // Previous and Next button functionality
566
567 // enable or disable the "Previous" and "Next" buttons
568 void enablePreviousAndNext() {
569
570 Component c = firstDisplay.current;
571 if (c.getClass().getName().equals("ResultBox")) {
572 if (((ResultBox) c).prevBoxExists()) {
573 prevButton.enable();
574 } else {
575 prevButton.disable();
576 }
577 }
578
579 c = lastDisplay.current;
580 if (c.getClass().getName().equals("ResultBox")) {
581 if (((ResultBox) c).nextBoxExists()) {
582 nextButton.enable();
583 } else {
584 nextButton.disable();
585 }
586 }
587 }
588
589 // Shift to previous box
590 //
591 // If the user clicks "Previous" then scroll up.
592 void shiftPrevious() {
593
594 Component c = firstDisplay.current;
595 if (c.getClass().getName().equals("ResultBox")) {
596
597 ResultBox b = (ResultBox) c;
598 if (b.prevBoxExists()) {
599 b = b.prev;
600
601 // empty all the displays
602 firstDisplay.emptyContents();
603
604 // add as many result boxes as there are displays
605 for (int i = 1 ; ((i <= depth) && (b != null)); i++) {
606 lastDisplay.display(b);
607 b.resize(b.display.size());
608 b.paintAll(b.getGraphics());
609 b = b.next;
610 }
611 }
612 }
613 enablePreviousAndNext();
614 }
615
616
617 // Shift to next box
618 //
619 // If the user clicks "Next" then scroll down if possible
620 void shiftNext() {
621
622 Component c = lastDisplay.current;
623 if (c.getClass().getName().equals("ResultBox")) {
624
625 ResultBox b = (ResultBox) c;
626 if (b.nextBoxExists()) {
627
628 // find the new "first" displayed box
629 c = firstDisplay.current;
630 b = (ResultBox) c;
631 b = b.next;
632
633 // empty all the displays
634 firstDisplay.emptyContents();
635
636 // add as many result boxes as there are displays
637 for (int i = 1 ; ((i <= depth) && (b != null)); i++) {
638 lastDisplay.display(b);
639 b.resize(b.display.size());
640 b.paintAll(b.getGraphics());
641 b = b.next;
642 }
643 }
644 }
645 enablePreviousAndNext();
646 }
647
648
649
650 // Tidy up URLs
651 //
652 // Ensure a URL address (as string) has a protocol, host, and file.
653 //
654 // If the URL is a CGI script URL, it should be tidied up so that it is
655 // appropriate to tage attrib=value pairs on the end. This means it
656 // must either end with a "?" or (if it contains a question-mark
657 // internally) end with a "&".
658 String tidy_URL(String address, boolean isCGI) {
659
660 System.out.println("tidy URL: " + address);
661
662 // make sure the URL has protocol, host, and file
663 if (address.startsWith("http")) {
664 // the address has all the necessary components
665 } else if (address.startsWith("/")) {
666 // there is not protocol and host
667 URL document = getDocumentBase();
668 address = "http://" + document.getHost() + address;
669 } else {
670 // this URL is relative to the dirwectory the document is in
671 URL document = getDocumentBase();
672 String directory = document.getFile();
673 int end = directory.lastIndexOf('/');
674 directory = directory.substring(0,end + 1);
675 address = "http://" + document.getHost() + directory + address;
676
677 }
678
679 // if the URL is a cgi script, make sure it has a "?"
680 if (isCGI) {
681 if (address.indexOf((int) '?') == -1) {
682 address = address + "?";
683 } else if (!address.endsWith("?")) {
684 address = address + "&";
685 }
686 }
687
688
689 System.out.println("tidy URL returning: " + address);
690 return address;
691 }
692
693}
Note: See TracBrowser for help on using the repository browser.