source: other-projects/gs3-webservices-java-client/trunk/src/GS3Fedora/org/greenstone/fedora/services/GSearchConnection.java@ 26309

Last change on this file since 26309 was 26309, checked in by ak19, 12 years ago
  1. Corrections to XML returned by FedoraGS3 to get the VList display for classifierBrowse to work correctly in Greenstone: classifierStyle attribute needs to be set on documents returned. Also added in further missing attributes for query and browse, in case these turn out to be important. 2. Replaced 3-line setAttributeNode() calls with 1-line setAttribute() calls.
File size: 23.3 KB
Line 
1/**
2 *#########################################################################
3 * GSearchConnection.java - works with the demo-client for Greenstone 3,
4 * of the Greenstone digital library suite from the New Zealand Digital
5 * Library Project at the * University of Waikato, New Zealand.
6 * <BR><BR>
7 * Copyright (C) 2008 New Zealand Digital Library Project
8 * <BR><BR>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 * <BR><BR>
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *########################################################################
19 */
20
21package org.greenstone.fedora.services;
22
23import java.util.Vector;
24import java.util.Iterator;
25import java.util.Map;
26import java.util.HashMap;
27
28import java.net.URL;
29import javax.xml.namespace.QName;
30import javax.xml.parsers.DocumentBuilder;
31import javax.xml.parsers.DocumentBuilderFactory;
32import javax.xml.rpc.ServiceException;
33import java.net.MalformedURLException;
34
35import org.apache.axis.client.Call;
36import org.apache.axis.client.Service;
37import org.apache.log4j.Logger;
38
39import javax.xml.parsers.ParserConfigurationException;
40import org.w3c.dom.Element;
41import org.w3c.dom.NodeList;
42
43
44/**
45 * Class GSearchConnection connects to FedoraGSearch's web services.
46 * FedorGSearch offers indexing and full-text search functionality for
47 * Fedora repositories. Its search web service (method gFindObjects)
48 * returns the response of a search as XML.
49 * GSearchConnection offers more convenient methods that extract just
50 * the parts of search results that FedoraGS3Connection needs and returns
51 * that.
52 * @author ak19
53*/
54public class GSearchConnection implements FedoraToGS3Interface.Constants {
55 /** Logger for this class. */
56 private static final Logger LOG = Logger.getLogger(
57 GSearchConnection.class.getName());
58
59 /* Accessing the web services of Fedora Generic Search */
60 protected static String NAMESPACE_URI = "http://server.fedoragsearch.defxws.dk";
61 protected static String SERVICE_NAME = "OperationsService";
62
63 /** The names of the methods we use of Fedora Generic Search's web services
64 * are declared here as static final Strings. */
65 protected static final String G_FIND_OBJECTS = "gfindObjects";
66
67 /* Some fixed string literals that will be encountered in the response XMLs
68 * that FedoraGSearch's method gFindObjects() returns. */
69 protected static final String PID = "PID";
70 protected static final String HIT_TOTAL = "hitTotal";
71 protected static final String OBJECT = "object";
72 protected static final String FIELD = "field";
73 protected static final String NAME = "name";
74 protected static final String DC_TITLE_FIELD = "dc.title";
75 protected static final String FULLTEXT_FIELD = "ds.fulltext";
76
77 /** separator used internally to separate values of a search field */
78 protected static final String SPACE = " ";
79
80 /** The name of the Index wherein FedoraGSearch has indexed all the GS3 docs.
81 * This final member is public here so that others may read the indexName
82 * that this GSearchConnection works with. */
83 public final String indexName;
84
85 /** The Service object used to connect to the FedoraGSearch web services */
86 protected final Service service;
87 /** The Call object used to connect to the FedoraGSearch web services */
88 protected final Call call;
89 /** The portName object used when connecting to FedoraGSearch's web services */
90 protected final QName portName;
91
92 /** A DocumentBuilder object used to construct and parse XML */
93 protected final DocumentBuilder builder;
94
95
96 /** Constructor that takes a String representing the url of the WSDL
97 * file for FedoraGSearch's web services, and tries to establish a
98 * connection to those web services.
99 * @param wsdlFileLocation is a String representing the url of the WSDL file
100 * @param indexName is the name of the index that Fedora Generic Search
101 * should work with (the index wherein the indexed GS3 documents have been
102 * placed).
103 */
104 public GSearchConnection(String wsdlFileLocation, String indexName)
105 throws MalformedURLException, ServiceException,
106 ParserConfigurationException
107 {
108 this.indexName = indexName;
109
110 URL wsdlURL = new URL(wsdlFileLocation);
111 service = new Service(wsdlURL, new QName(NAMESPACE_URI, SERVICE_NAME));
112 //call = (Call) service.createCall(new QName(NAMESPACE_URI, PORT_NAME));
113
114 Iterator i = service.getPorts();
115 // FIXME: can we just assume it's the first port of service SERVICE_NAME?
116 // Do we need to work out which port to get??? Remember, the port names
117 // vary between wsdls though!
118 if(i.hasNext()) {
119 portName = (QName)i.next();
120 call = (Call) service.createCall(portName);
121
122 String endpointLocation = call.getTargetEndpointAddress();
123 LOG.debug("Wsdl file url: " + wsdlURL
124 + "\nEndpoint location is: " + endpointLocation);
125 } else { // should never happen: a service without a port
126 // portName = null;
127 call = (Call)service.createCall();
128 // FIXME: possibly manually get the ports and choose
129 // one containing "FEDORA" and "API-A" in its name?
130 throw new ServiceException(this.getClass() + ": No port in wsdl file");
131 }
132
133 // we can set the portName which remains constant for the various methods
134 // call.setPortName(portName);
135
136 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
137 builder = factory.newDocumentBuilder(); // to create XML docs
138 }
139
140 /**
141 * Method to invoke gfindObjects operation of Fedora Generic Search
142 * web services.
143 *
144 * Parameter types, parameter order and return type of gFindObjects are as
145 * obtained from the wsdl file for the Fedora Generic Search web services
146 * located at:
147 * http://localhost:8080/fedoragsearch/services/FgsOperations?wsdl
148 * &lt;wsdl:message name="gfindObjectsRequest"&gt;
149 * &lt;wsdl:part name="query" type="xsd:string"/&gt;
150 * &lt;wsdl:part name="sort" type="xsd:string"/&gt;
151 * &lt;wsdl:part name="hitPageStart" type="xsd:int"/&gt;
152 * &lt;wsdl:part name="hitPageSize" type="xsd:int"/&gt;
153 * &lt;wsdl:part name="snippetsMax" type="xsd:int"/&gt;
154 * &lt;wsdl:part name="fieldMaxLength" type="xsd:int"/&gt;
155 * &lt;wsdl:part name="indexName" type="xsd:string"/&gt;
156 * &lt;wsdl:part name="resultPageXslt" type="xsd:string"/&gt;
157 * &lt;/wsdl:message&gt;
158 *
159 * &lt;wsdl:message name="gfindObjectsResponse"&gt;
160 * &lt;wsdl:part name="gfindObjectsReturn" type="xsd:string"/&gt;
161 * &lt;/wsdl:message&gt;
162 *
163 * &lt;wsdl:operation name="gfindObjects"
164 * parameterOrder="query sort hitPageStart hitPageSize snippetsMax
165 * fieldMaxLength indexName resultPageXslt"&gt;
166 *
167 * This method works: it searches the dc.title field of our FedoraIndex
168 * for the term (e.g. "interview") and the result returned is an XML String.
169 *
170 * There's no example on how to call gFindObjects with parameters. In
171 * particular, I don't know what values the parameter <b>sort</b> can take.
172 * But topazproject has an example on how to call updateIndex().
173 * @see <a href="http://www.topazproject.org/trac/wiki/FedoraSearch?format=txt">An example on how to call updateIndex() with parameters</a>
174 * @see <a href="http://ws.apache.org/axis/java/apiDocs/org/apache/axis/client/Service.html">Axis Service class</a>
175 * @see <a href="http://ws.apache.org/axis/java/apiDocs/javax/xml/rpc/Call.html">Axis RPC Call, for specification of interface Call</a>
176 * @see <a href="http://ws.apache.org/axis/java/apiDocs/org/apache/axis/client/Call.html">Axis client Call class, for implementation of interface Call</a>
177 */
178 protected String gFindObjects(String searchFieldedTerms, String sort,
179 int hitPageStart, int hitPageSize, int snippetsMax,
180 /*int fieldMaxLength,*/ String indexName, String resultPageXslt) throws Exception
181 {
182 // "Prefills as much info from the WSDL as it can. Right now it's SOAPAction,
183 // operation qname, parameter types and return type of the Web Service.
184 // This method considers that port name and target endpoint address have
185 // already been set. This is useful when you want to use the same Call instance
186 // for several calls on the same Port. NOTE: Not part of JAX-RPC specification."
187
188 //call.removeAllParameters(); // no need for this when using setOpName below
189 call.setOperationName(G_FIND_OBJECTS);
190
191 // Max num of chars in field vals returned. Since return values exceeding
192 // maxlength will be truncated, ensure length suffices for long PIDs returned.
193 // The only element of the response XML we'll be using is the PID of the document
194 // in which the searchTerm occurred.
195 final int fieldMaxLength = 100; // NOT TRUE: max length in words of field values
196 // returned. E.g. snippet sizes will be reduced to fieldMaxLength words too.
197
198 // This is the method call for Fedora 2's GSearch
199 //String valueFound =(String)call.invoke( new Object[] {
200 // searchFieldedTerms, sort, hitPageStart, hitPageSize, snippetsMax,
201 // fieldMaxLength, indexName, resultPageXslt} );
202
203 // The method call for GSearch 2.2 of Fedora 3 takes the args in a different order:
204 String valueFound =(String)call.invoke( new Object[] {
205 searchFieldedTerms, hitPageStart, hitPageSize, snippetsMax,
206 fieldMaxLength, indexName, sort, resultPageXslt} );
207
208 // for debugging
209 //javax.swing.JOptionPane.showMessageDialog(null, "GSearchConnection.gFindObjects:" + valueFound);
210 //LOG.error("gfindObjects result: " + valueFound);
211
212 return valueFound;
213 }
214
215 /**
216 * Method that performs a search for the given searchTerm inside the given
217 * indexed field.
218 * @param searchFieldName is the name of the indexed field within which the
219 * given searchTerm is to be searched for.
220 * @param searchTerm is the term to be searched for.
221 * @param hitPageStart is the page of search results to start returning.
222 * @param hitPageSize is the number of search result pages to return,
223 * starting from hitPageStart.
224 * @param snippetsMax is the maximum number of separate snippets containing
225 * the searchTerm that are to be returned. (snippetsMax or a fewer number of
226 * occurrences of the word in the text will be returned)
227 */
228 public String search(String searchFieldName, String searchTerm,
229 int hitPageStart, int hitPageSize, int snippetsMax) throws Exception
230 {
231 final String sort = ""; // returns results from highest to lowest rank
232 final String resultPageXslt = "";
233
234 // when a fieldname is given to search in (ds.fulltext, dc.title)
235 // then prepend that followed by a COLON to the searchTerm.
236 final String fullSearchTerm = searchFieldName.equals("") ?
237 searchTerm : (searchFieldName+":"+searchTerm);
238
239 return gFindObjects(fullSearchTerm, sort,
240 hitPageStart, hitPageSize, snippetsMax,
241 indexName, resultPageXslt);
242 }
243
244 /**
245 * FedoraGSearch accepts a query of the form:
246 * <code>&lt;"cyclone val" "Gender Inequalities" ds.fulltext:"cyclone val"
247 * ds.fulltext:"worst storm"&gt;</code>
248 * where the first two phrases are searched for in all indexed fields,
249 * (in this case dc.title and ds.fulltext), while the last two are
250 * searched for in the ds.fulltext field.
251 * Another example:
252 * <code>&lt;gender dc.title:interview ds.fulltext:"cyclone val"&gt;
253 * titles and fulltexts are searched for "gender", while title index
254 * is searched for "interview" and fulltexts are searched for the phrase
255 * "cyclone val"</code>
256 * @param fieldsToSearchTerms is a Hashmap of searchfields and
257 * associated search terms (words or phrases). The terms are in a
258 * comma-separated list. fieldsToSearchTerms is a Hashmap of
259 * (Searchfields, associated-searchTerms) pairs. It can contain 3
260 * searchfields: allfields, titles, text. The value for each is a
261 * comma-separated list of search terms in that field.
262 * Internally the field names get converted to what FedoraGSearch's
263 * gfindObjects understands: titles becomes dc.title:, text becomes
264 * ds.fulltext and allfields becomes nothing.
265 * @param hitPageStart is the page of search results to start returning.
266 * @param hitPageSize is the number of search result pages to return,
267 * starting from hitPageStart.
268 * @return the XML (in string format) returned from Fedora Generic Search's
269 * gfindObjects method
270 *
271 */
272 public String search(Map fieldsToSearchTerms,
273 int hitPageStart, int hitPageSize)
274 throws Exception
275 {
276 LOG.debug("In FedoraGS3's GSearchConnection.search(Map,...)");
277
278 // HashMap consists of several (key, value) entries, 3 of
279 // which will be dealt with here:
280 // - allfields, <comma separated list of search terms/phrases>
281 // - titles, <comma separated list of search terms/phrases>
282 // - (full)text, <comma separated list of search terms/phrases>
283 // We need to obtain each value and change the separator to space:
284 String allfields = (String)fieldsToSearchTerms.get(ALL_FIELDS);
285 String titles = (String)fieldsToSearchTerms.get(ALL_TITLES);
286 String fulltexts = (String)fieldsToSearchTerms.get(FULLTEXT);
287
288 // Each field is a comma separated list of terms that may be
289 // either a word OR a phrase.
290 // We're going to separate each term from the list,
291 // and put quotes around phrases, then combine all the terms
292 // together again with spaces to separate them.
293 allfields = formatSearchTermsInField(allfields, ALL_FIELDS); // search foxml.all.text
294 // ALL_FIELDS has no field name
295 titles = formatSearchTermsInField(titles, DC_TITLE_FIELD);
296 fulltexts = formatSearchTermsInField(fulltexts, FULLTEXT_FIELD);
297
298 String fullSearchTerm = allfields + titles + fulltexts;
299 if(fullSearchTerm.trim().equals("")) { // nothing to search on
300 return "";
301 }
302
303 // Finally, restrict the search to the Greenstone digital objects
304 // stored in Fedora
305 final String greenstonePID
306 = PID + FedoraGS3DL.COLON + FedoraGS3DL.GREENSTONE;
307 //"PID:\"greenstone\"";
308 fullSearchTerm += greenstonePID;
309 //! Everything after the colon in the pid is ignored by FedoraGSearch:
310 // "PID:\"greenstone:gs2mgdemo\""; // ignores "gs2mgdemo"
311
312 // <snippet> tags interfere when PID field is searched on, set it to 0
313 return search(fullSearchTerm, hitPageStart, hitPageSize, 0);
314 // return search(fullSearchTerm, hitPageStart, hitPageSize, snippetsMax);
315 }
316
317 /** Each field is a comma separated list of terms that may be either a word
318 * OR a phrase. We're going to separate each term from the list, and put
319 * quotes around phrases, then combine all the terms together again with
320 * spaces to separate them. Examples:
321 * <pre>dc.title:"a phrase" word
322 * dc.fulltext: "cyclone val"
323 * (ALL_FIELDS) interview gender</pre>
324 * This is required to facilitate fielded searching with fedoraGSearch.
325 * @param field is a comma separated list of search terms (corresponding
326 * to one fieldName) to be reorganised
327 * @param fieldName is the name of the field to prepend to the reorganised
328 * field value. FieldName ALL_FIELDS is ignored.
329 * @return parameter field reorganised such that terms that are phrases
330 * are in quotes and each term is separated by a space from the previous one.
331 */
332 protected String formatSearchTermsInField(String field, String fieldName)
333 {
334 if(field != null) { // check that the field isn't empty
335 //LOG.debug("field: " + field);
336 String[] terms = field.split(",");
337 field = ""; // we'll build it up again
338 for(int i = 0; i < terms.length; i++) {
339 // if it contains a space, then the term's a phrase,
340 // put it in quotes
341 if(terms[i].indexOf(SPACE) != -1) {
342 terms[i] = "\"" + terms[i] + "\"";
343 }
344 field = field + terms[i] + SPACE;
345 }
346
347 // Prefix it with the name of the field we want to search for
348 // the term in. Every field other than allfields has a prefix
349 if(!fieldName.equals(ALL_FIELDS)) {
350 field = fieldName + ":" + field;
351 }
352
353 } else field = "";
354 return field;
355 }
356
357 /**
358 * Uses FedoraGSearch to perform a search where the query is embedded in
359 * fieldedSearchTerms, which not only provides the terms to search on, but
360 * also the fields to search the (various) given terms in.
361 * @param fieldedSearchTerms is the String specifying all the search terms
362 * with their fields (or no field if it should search for the terms in
363 * all fields). The terms with no associated search-fields should come first.
364 * Search terms may be in quotes.
365 * @param snippetsMax is the maximum number of separate snippets containing
366 * the searchTerm (snippetsMax number of occurrences of the word in the text)
367 * returned.
368 * @param hitPageStart is the page of search results to start returning.
369 * @param hitPageSize is the number of search result pages to return,
370 * starting from hitPageStart.
371 * @return the XML (in string format) returned from Fedora Generic Search's
372 * gfindObjects method
373 */
374 public String search(String fieldedSearchTerms,
375 int hitPageStart, int hitPageSize, int snippetsMax) throws Exception
376 {
377 LOG.debug("In method search(String fieldedSearchTerms,...). "
378 + "Query is:\n" + fieldedSearchTerms);
379
380 final String sort = ""; // returns results from highest to lowest rank
381 final String resultPageXslt = "";
382 return gFindObjects(fieldedSearchTerms, sort,
383 hitPageStart, hitPageSize, snippetsMax,
384 indexName, resultPageXslt);
385 }
386
387 /** Call this method with the return value of calling search().
388 * Search results are returned in GSearch's XML response format,
389 * containing information that includes the PIDs of the documents that
390 * matched the search. These PIDs are returned in the array.
391 * @param collectionName is the name of the collection to restrict the
392 * search results by. If it's "", then results from all collections are
393 * returned. Generally, don't want to pass "", because, theoretically,
394 * all indexed collections in the repository could be considered and
395 * not all of them may be Greenstone collections. If all Greenstone
396 * collections should be searched for, pass "greenstone" as the
397 * collection name instead.
398 * @param searchResult is the Fedora Generic Search XML response returned
399 * from performing a gfindObjects() operations.
400 * @return an array of the pids of documents found for the search. */
401 public String[] getPIDsFromSearchResult(String collectionName,
402 String searchResult)
403 throws Exception
404 {
405 final String[] empty = {};
406 if(searchResult.equals("")) {
407 return empty;
408 }
409
410 // <?xml version="1.0" encoding="UTF-8"?>
411 // <resultPage xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:foxml="info:fedora/fedora-system:def/foxml#" xmlns:zs="http://www.loc.gov/zing/srw/" indexName="FedoraIndex" dateTime="Sat Feb 09 16:43:04 NZDT 2008">
412 // <gfindObjects hitTotal="1" resultPageXslt="" hitPageSize="10" hitPageStart="1" query="ds.fulltext:Cyclone">
413 // <objects>
414 // <object no="1" score="0.24639596">
415 // <field name="PID">greenstone:gs2mgdemo-HASH01d667303fe98545f03c14ae</field>
416 // <field name="repositoryName">Fedora</field>
417 // <field name="object.type">FedoraObject</field>
418 // <field name="object.state">Active</field>
419 // <field name="object.label">The Courier - N°159 - Sept- Oct 1996 Dossier Inves ... </field>
420 // <field name="object.createdDate">2007-11-23T04:23:15.363Z</field>
421 // <field name="object.lastModifiedDate">2008-01-15T04:37:49.518Z</field>
422 // <field name="dc.title">some title</field>
423 // <field name="dc.title">some title2</field>
424 // ...
425 // <field name="ds.fulltext" snippet="yes">(The 1993 <span class="highlight">cyclone</span>, although</field>
426 // <field name="ds.label">Metadata</field>
427 // ...
428 // </object>
429 // </objects>
430 // </gfindObjects>
431 // 1. Get documentElement, which is <resultPage>
432 Element resultPage = FedoraCommons.getResponseAsDOM(builder, searchResult);
433 // 2. find the hitTotal value which is the number of results
434 // it's an attribute of the sole compulsory <gFindObjects> element
435 int hitTotal = 0;
436 Element gfindObjectsEl
437 = (Element)resultPage.getElementsByTagName(G_FIND_OBJECTS).item(0);
438 String value = gfindObjectsEl.getAttribute(HIT_TOTAL);
439 hitTotal = Integer.parseInt(value);
440 if(hitTotal == 0) {
441 return new String[]{};
442 }
443
444 // Our resulting list of pids will be no more than hitTotal,
445 // but may be fewer if we constrain the results to a collection
446 Vector pidsInCollection = new Vector(hitTotal);
447
448 // Returns a NodeList of all descendant Elements with object tagname
449 NodeList objects = gfindObjectsEl.getElementsByTagName(OBJECT);
450 for(int i = 0; i < objects.getLength(); i++) {
451 // should be the case that pids.length == (digital)objects.getLength()
452 // get the PID of each object
453 Element object = (Element)objects.item(i);
454 NodeList fields = object.getElementsByTagName(FIELD);
455
456 for(int j = 0; j < fields.getLength(); j++) {
457 // find the sole <field> of <object> where NAME attribute == PID
458 Element field = (Element)fields.item(j);
459 if(field.getAttribute(NAME).equals(PID)) {
460 String pid = FedoraCommons.getValue(field);
461 // Either store only the pids which are part of the collection,
462 // or, if no collection is specified (=""),then store the pid too
463 if(collectionName.equals("") || pid.contains(collectionName)) {
464 pidsInCollection.add(pid);
465 }
466 break; // found pid field, meaning that we have
467 // finished for loop on <field>s of this <object>,
468 // consider next <object>
469 }
470 }
471 }
472 String[] pids = new String[pidsInCollection.size()];
473 pidsInCollection.toArray(pids);
474 return pids;
475 }
476
477 public static void main(String[] args) {
478 try {
479 GSearchConnection searcher = new GSearchConnection(
480 "http://localhost:8080/fedoragsearch/services/FgsOperations?wsdl", "FedoraIndex");
481
482
483 HashMap map = new HashMap();
484 map.put(GSearchConnection.ALL_FIELDS, "gender inequalities");
485 map.put(GSearchConnection.FULLTEXT, "cyclone val,worst storm");
486 //map.put(GSearchConnection.ALL_FIELDS, "\"gender inequalities\"");
487 //map.put(GSearchConnection.FULLTEXT, "\"cyclone val\",\"worst storm\"");
488 String searchResult = searcher.search(map, 1, 10); //snippetsMax: 3);
489 System.out.println(searchResult);
490
491 String[] pids = searcher.getPIDsFromSearchResult("gs2mgdemo", searchResult);
492 System.err.println("Found pids for search:\n");
493 for(int i = 0; i < pids.length; i++) {
494 System.out.println(pids[i]);
495 }
496
497 //searchResult = searcher.search("", "minh", 0, 50, 50);
498 //System.err.println(searchResult);
499
500 //String searchTerms = "cyclone dc.title:interview dc.title:gender";
501 String searchTerms="\"gender inequalities\" ds.fulltext:\"cyclone val\" ds.fulltext:\"worst storm\"";
502 searchResult = searcher.search(searchTerms, 1, 10, 3);
503 System.out.println(searchResult);
504
505 // Not restricting results to any collection (search results from
506 // all collections)
507 pids = searcher.getPIDsFromSearchResult("", searchResult);
508 System.err.println("Found pids for search: ");
509 for(int i = 0; i < pids.length; i++) {
510 System.out.println(pids[i]);
511 }
512
513 searchResult = searcher.search("ds.fulltext", "cyclone", 1, 10, 3);
514 //String searchResult = searcher.search("ds.label", "hierarchical", 1, 10, 3);
515 // System.out.println(searcher.search("ds.fulltext", "Pinky", 1, 10, 3));
516 System.out.println(searchResult);
517
518 pids = null;
519 pids = searcher.getPIDsFromSearchResult("", searchResult);
520 System.err.println("Found pids for search: ");
521 for(int i = 0; i < pids.length; i++) {
522 System.out.println(pids[i]);
523 }
524
525 }catch(Exception e) {
526 System.err.println(e.getMessage());
527 }
528
529 }
530
531}
Note: See TracBrowser for help on using the repository browser.