001: /*
002: * Copyright 2001 Sun Microsystems, Inc. All rights reserved.
003: * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to license terms.
004: */
005:
006: package com.sun.portal.search.demo;
007:
008: import com.sun.portal.search.soif.*;
009: import com.sun.portal.search.rdm.RDMHeader;
010: import com.sun.portal.search.util.Encoder;
011:
012: import java.net.*;
013: import java.io.*;
014:
015: /**
016: * Search encapsulation class.
017: */
018: public class Search {
019:
020: /** The fully formatted URL query string that will be sent to the server. */
021: protected String query;
022:
023: /** The query string. */
024: protected String scope;
025:
026: /** The comma delimited list of databases to be searched. */
027: protected String database;
028:
029: /** The RDM server URL */
030: protected String RDMServer;
031:
032: /** The comma delimited requested result attribute list. */
033: protected String viewAttributes;
034:
035: /**
036: * The comma delimited sort order w/ +ascending and -descending.
037: * <P>Eg, "-score,+title". Default value is "-score".
038: */
039: protected String viewOrder;
040:
041: /** Identity Server Single Sign On token. */
042: protected String SSOToken;
043:
044: /** The maximum number of results requested. */
045: protected int viewHits;
046:
047: /** The first hit to be returned (counting from 1). */
048: protected int firstHit;
049:
050: /** The RDM request type. */
051: protected String RDMType;
052:
053: /** The query language. */
054: protected String queryLanguage;
055:
056: /** The result set as a stream, including the rdm header. */
057: private boolean streamMode = false;
058: private SOIFInputStream resultStream;
059:
060: /** The result header. */
061: private RDMHeader rdmHeader;
062:
063: /** Automatically parse a SOIF string. */
064: private boolean doSOIFParse;
065:
066: private int RDM_Results = 0;
067: private int RDM_Hits = 0;
068: private int RDM_Documents = 0;
069: private String RDM_Response_Interpret = null;
070: public String proxyDN = null;
071:
072: /**
073: * Constructor.
074: */
075: public Search() {
076: scope = "";
077: viewAttributes = "";
078: viewOrder = "";
079: firstHit = 0;
080: viewHits = 0;
081: queryLanguage = "";
082: database = null;
083: query = "";
084: doSOIFParse = false;
085: RDMServer = "";
086: RDMType = "rd-request";
087: SSOToken = "";
088: }
089:
090: /**
091: * Constructor.
092: * <p>Default values implicit in this constructor are:<br>
093: * - viewAttributes: null. Return all attributes<br>
094: * - viewOrder: null. Return server default (ie, sorted by relevance)<br>
095: * - firsthit: 1. Start hits at hit number 1<br>
096: * - viewhits: 10. Return 10 hits only<br>
097: * - query language: search. Search documents<br>
098: * - database: null. Use the default database for the server<br>
099: *
100: * @param scope the query qualification
101: * @param RDMServer Search server URL, eg, http://portal.siroe.com:2222/portal/search
102: * @since 3.01C
103: */
104: public Search(String scope, String RDMServer) {
105: this (scope, "", "", 1, 10, "search", null, RDMServer, "");
106: }
107:
108: /**
109: * Constructor.
110: * @param scope the search query
111: * @param viewAttributes comma delimited desired result attributes
112: * @param viewOrder comma delimited sort order w/ +ascend and -descend
113: * @param firstHit first requested hit (numbered from 1)
114: * @param viewHits number of results requested (starting with the firstHit result)
115: * @param queryLanguage query language
116: * @param database name (can be null for server's default database)
117: * @param RDMServer Search server URL, eg, http://portal.siroe.com:2222/portal/search
118: * @param sessID Identity Server single sign-on session ID
119: */
120: public Search(String scope, String viewAttributes,
121: String viewOrder, int firstHit, int viewHits,
122: String queryLanguage, String database, String RDMServer,
123: String ssoToken) {
124: this .scope = scope;
125: this .viewAttributes = viewAttributes;
126: this .viewOrder = viewOrder;
127: this .firstHit = firstHit;
128: this .viewHits = viewHits;
129: this .queryLanguage = queryLanguage;
130: this .database = database;
131: this .RDMType = "rd-request";
132: this .doSOIFParse = true;
133: this .SSOToken = ssoToken;
134: this .RDMServer = RDMServer;
135: }
136:
137: /**
138: * Set whether SOIF parsing is to be done or not.
139: * By default, SOIF parsing is true.
140: */
141: public void setSOIFParse(boolean b) {
142: doSOIFParse = b;
143: }
144:
145: /**
146: * Sets whether streaming is enabled or disabled.
147: * By default, this is set to false.<br>
148: * When stream mode is enabled, the search results
149: * RDM header SOIF will still be parsed, so that
150: * result, hit and document counts are available, but
151: * the document hit data will not be parsed. Instead, use
152: * {@link #getResultStream} to access the document
153: * SOIF DataInputStream directly.
154: *
155: * @since 3.01C
156: */
157: public void setStreamMode(boolean m) {
158: streamMode = m;
159: }
160:
161: /**
162: * Get the starting hit offset.
163: * @see #setFirstHit
164: */
165: public int getFirstHit() {
166: return firstHit;
167: }
168:
169: /**
170: * Set the starting hit offset.
171: * @see #setFirstHit
172: */
173: public void setFirstHit(int firstHit) {
174: this .firstHit = firstHit;
175: }
176:
177: /**
178: * Get the maximum number of hits returned.
179: * @see #setViewHits
180: */
181: public int getViewHits() {
182: return viewHits;
183: }
184:
185: /**
186: * Set the maximum number of hits returned.
187: * @see #getViewHits
188: */
189: public void setViewHits(int viewHits) {
190: this .viewHits = viewHits;
191: }
192:
193: /**
194: * Returns the current RDMServer variable.
195: * @since 3.01C
196: */
197: public String getRDMServer() {
198: return RDMServer;
199: }
200:
201: /**
202: * Sets the RDMServer variable.
203: * @param RDMServer Search server URL, eg, http://portal.siroe.com:2222/portal/search (Portal server)
204: * http://compass.siroe.com:2222/rdm/incoming (Compass server)
205: * @since 3.01C
206: */
207: public void setRDMServer(String RDMServer) {
208: this .RDMServer = RDMServer;
209: }
210:
211: /**
212: * Returns the current scope (query) for the search.
213: * @see #setScope
214: * @since 3.01C
215: */
216: public String getScope() {
217: return scope;
218: }
219:
220: /**
221: * Sets the scope (query) for the search.
222: * @see #getScope
223: * @since 3.01C
224: */
225: public void setScope(String scope) {
226: this .scope = scope;
227: }
228:
229: /**
230: * Returns the current RDMType.
231: * @see #setRDMType
232: * @since 3.01C
233: */
234: public String getRDMType() {
235: return RDMType;
236: }
237:
238: /**
239: * Returns the current target DN for the search.
240: * @see #setProxyDN
241: * @since 6.3
242: */
243: public String getProxyDN() {
244: return proxyDN;
245: }
246:
247: /**
248: * Sets the target DN for the search.
249: * @see #getProxyDN
250: * @since 6.3
251: */
252: public void setProxyDN(String proxyDN) {
253: this .proxyDN = proxyDN;
254: }
255:
256: /**
257: * Sets the RDM Request type.
258: * @param RDMType Can be one of:
259: * <ul>
260: * <li>rd-request: The default request. Resource descriptions (documents).
261: * <li>taxonomy-request: Taxonomy.
262: * <li>schema-request: The schema.
263: * <li>server-request: Server information.
264: * <li>status-request: Server status information.
265: * </ul>
266: * @see #getRDMType
267: * @see #setQueryLanguage
268: * @since 3.01C
269: */
270: public void setRDMType(String RDMType) {
271: this .RDMType = RDMType;
272: }
273:
274: /**
275: * Returns the current query language.
276: * @see #setQueryLanguage
277: * @since 3.01C
278: */
279: public String getQueryLanguage() {
280: return queryLanguage;
281: }
282:
283: /**
284: * Sets the query language.
285: * @param queryLanguage Can be one of:
286: * <ul>
287: * <li>search: The default query language. Searches documents or the taxonomy.
288: * <li>taxonomy-basic: Used for requesting branches or parts of the taxonomy.
289: * <li>schema-basic: Queries the search schema.
290: * <li>url: Retrieves RDs by url (scope=url).
291: * </ul>
292: * @see #getQueryLanguage
293: * @see #setRDMType
294: * @since 3.01C
295: */
296: public void setQueryLanguage(String queryLanguage) {
297: this .queryLanguage = queryLanguage;
298: }
299:
300: /**
301: * Returns the SOIF attributes which are retrieved by a search.<BR>
302: * @return <code>viewAttributes</code> as set by setViewAttributes.
303: * A comma delimited list of attributes, returned by a search, eg<br>
304: * "score,title,description,url"<br>
305: * NB: a null string denotes that ALL SOIF attributes are returned.
306: * @see #setViewAttributes
307: * @since 3.01C
308: */
309: public String getViewAttributes() {
310: return viewAttributes;
311: }
312:
313: /**
314: * Sets the SOIF attributes which are returned for the search.
315: * @param viewAttributes A null string will return all attributes.
316: * A comma delimited list of attributes is accepted, eg<br>
317: * "score,title,description,url"
318: * @see #getViewAttributes
319: * @since 3.01C
320: */
321: public void setViewAttributes(String viewAttributes) {
322: this .viewAttributes = viewAttributes;
323: }
324:
325: /**
326: * Gets the sorting order for results.<BR>
327: * @param strVal A null string will return sorting according to
328: * the server default of -score (descending relevance). A comma
329: * delimited list of attributes is accepted, with + to denote
330: * ascending order and - to denote descending order, eg<br>
331: * "-score,+title"<br>
332: * @see #setViewOrder
333: * @since 3.01C
334: */
335: public String getViewOrder() {
336: return viewOrder;
337: }
338:
339: /**
340: * Sets the sorting order for results.<BR>
341: * @param viewOrder A null string will return sorting according to
342: * the server default of -score (descending relevance). A comma delimited
343: * list of attributes is accepted, with + to denote ascending order and - to
344: * denote descending order, eg<br>
345: * "-score,-title,+description"<br>
346: * @see #getViewOrder
347: * @since 3.01C
348: */
349: public void setViewOrder(String viewOrder) {
350: this .viewOrder = viewOrder;
351: }
352:
353: /**
354: * Return the fully formatted query url sent to the server.
355: */
356: public String getQuery() {
357: query = formatQuery();
358: return query;
359: }
360:
361: /**
362: * @param The database name
363: */
364: public void setDatabase(String database) {
365: this .database = database;
366: }
367:
368: /**
369: * @returns The database
370: */
371: public String getDatabase() {
372: return database;
373: }
374:
375: /**
376: * @param The access token from the portal server
377: */
378: public void setSessionID(String sessID) {
379: this .SSOToken = sessID;
380: }
381:
382: /**
383: * @returns The access token if it exists
384: */
385: public String getSessionID() {
386: return SSOToken;
387: }
388:
389: /**
390: * Execute the query.
391: * <p>Note: Query results are concatenated into a single StringBuffer.
392: * Use {@link #doQuery(int,int)} to process search results iteratively.
393: */
394: public void doQuery() {
395: try {
396: doQuery(true);
397: } catch (Exception e) {
398: //ignore exception
399: }
400: }
401:
402: public void doQuery(boolean catchException) throws Exception {
403:
404: // reset result info
405: rdmHeader = null;
406: RDM_Response_Interpret = null;
407:
408: query = formatQuery();
409:
410: try {
411: /* set up the input stream and parse the rdm header */
412: URLConnection rc = new URL(query).openConnection();
413: rc.setAllowUserInteraction(true);
414: resultStream = new SOIFInputStream(rc.getInputStream());
415: rdmHeader = new RDMHeader(resultStream);
416: String rdmEMsg = rdmHeader.getErrorMessage();
417: if (rdmEMsg != null) {
418: if (!rdmEMsg.equalsIgnoreCase("0 results")) {
419: if (catchException) {
420: System.out.println("RDM Error: " + rdmEMsg);
421: return;
422: } else {
423: throw new Exception("RDM Error: " + rdmEMsg);
424: }
425: }
426: }
427: } catch (Exception e) {
428: if (catchException) {
429: System.out.println("Error: " + e);
430: return;
431: } else {
432: throw e;
433: }
434: }
435:
436: }
437:
438: /**
439: * Execute the query, returning viewHits hits starting at firstHit.
440: */
441: public void doQuery(int firstHit, int viewHits) {
442: setFirstHit(firstHit);
443: setViewHits(viewHits);
444: doQuery();
445: }
446:
447: /**
448: * Return the SOIF RDM result header.
449: */
450: public SOIF getRDMHeaderSOIF() {
451: return rdmHeader.getSOIF();
452: }
453:
454: /**
455: * Return results as a DataInputStream.
456: * Can only be used if stream mode is enabled.
457: * @see #setStreamMode
458: */
459: public SOIFInputStream getResultStream() {
460: return resultStream;
461: }
462:
463: /**
464: * Parses the rdm response interpret attribute.
465: *
466: * NB: Currently only makes sense for RD, taxonomy and url responses
467: */
468: private void parseResponseHeader() {
469: if (RDM_Response_Interpret == null && rdmHeader != null) {
470: RDM_Response_Interpret = rdmHeader.getResponseInterpret();
471: }
472:
473: if (RDM_Response_Interpret == null)
474: return;
475:
476: // RD and TAX responses looks like
477: // "3 results out of 10 hits across 157 documents"
478: // Some other responses, eg url, look like
479: // "3 results"
480: //
481: // Here we look for "[n result] [... n hit] [... n document]"
482: //
483: String resp = RDM_Response_Interpret;
484: //System.out.println(RDM_Response_Interpret);
485: try {
486: RDM_Results = getKeyedInt(resp, "result");
487: } catch (Exception e) {
488: }
489:
490: try {
491: RDM_Hits = getKeyedInt(resp, "hit");
492: } catch (Exception e) {
493: }
494:
495: try {
496: RDM_Documents = getKeyedInt(resp, "document");
497: } catch (Exception e) {
498: }
499: }
500:
501: /**
502: * Parses a keyed int out of a string, ie, "... nnn key ..."
503: * throws NumberFormatException on failure
504: */
505: private int getKeyedInt(String str, String rawkey)
506: throws NumberFormatException {
507: int res = 0;
508: try {
509: int i, n1, n2;
510: String key = " " + rawkey; // need space(s) before key
511: if ((i = str.indexOf(key)) <= 0)
512: throw (new Exception());
513: for (--i; i >= 0 && str.charAt(i) == ' '; --i)
514: ;
515: n2 = i + 1;
516: for (; i >= 0 && str.charAt(i) != ' '; --i)
517: ;
518: n1 = i + 1;
519: String s1 = str.substring(n1, n2);
520: //System.out.println("dbg: " + s1 + " " + n1 + " " + n2);
521: res = Integer.parseInt(s1);
522: } catch (Exception e) {
523: throw new NumberFormatException();
524: }
525: return res;
526: }
527:
528: /**
529: * The number of results returned by the search.
530: * Returns -1 on error.
531: *
532: * The result count is based on the RDM header SOIF,
533: * so if doSOIFParse is false, -1 will be returned.
534: * @since 3.01C
535: */
536: public int getResultCount() {
537: parseResponseHeader();
538: return RDM_Results;
539: }
540:
541: /**
542: * The total number of results that matched the search.
543: * @since 3.01C
544: */
545: public int getHitCount() {
546: parseResponseHeader();
547: return RDM_Hits;
548: }
549:
550: /**
551: * The total number of documents available to be searched.
552: * @since 3.01C
553: */
554: public int getDocumentCount() {
555: parseResponseHeader();
556: return RDM_Documents;
557: }
558:
559: /**
560: * Gets the last hit value being displayed
561: * @return firstHit+resultCount-1
562: */
563: public int getToHit() {
564: return firstHit + getResultCount() - 1;
565: }
566:
567: /**
568: * @return true if no matches were found and false otherwise
569: */
570: public boolean noHits() {
571: if (getResultCount() <= 0)
572: return true;
573: else
574: return false;
575: }
576:
577: /**
578: * Return debug string version of Search instance.
579: */
580: public String toString() {
581: return "Search instance:\n" + "\tRDMType = [" + RDMType
582: + "]\n" + "\tqueryLanguage = [" + queryLanguage
583: + "]\n" + "\tscope = [" + scope + "]\n"
584: + "\tviewAttributes = [" + viewAttributes + "]\n"
585: + "\tviewOrder = [" + viewOrder + "]\n"
586: + "\tviewHits = [" + firstHit + ".."
587: + (firstHit + viewHits - 1) + "]\n"
588: + "\tdatabase = [" + database + "]\n"
589: + "\tRDMServer = [" + RDMServer + "]\n"
590: + "\tSSOToken = [" + SSOToken + "]\n";
591: }
592:
593: /**
594: * Format the query string using passed view-hits. This enables
595: * you to break large queries into multiple smaller queries.
596: */
597: private String formatQuery() {
598: StringBuffer url = new StringBuffer();
599: try {
600: if (RDMServer != null)
601: url.append(RDMServer);
602: if (RDMType != null)
603: url.append("?type=" + RDMType);
604: if (queryLanguage != null)
605: url.append("&ql=" + queryLanguage);
606: if (scope != null)
607: url.append("&scope=").append(
608: Encoder.urlEncode(scope, "UTF-8"));
609: if (viewAttributes != null)
610: url.append("&view-attributes=").append(
611: Encoder.urlEncode(viewAttributes, "UTF-8"));
612: if (viewOrder != null)
613: url.append("&view-order=").append(
614: Encoder.urlEncode(viewOrder, "UTF-8"));
615: if (viewHits != 0) {
616: if (viewHits < 0) {
617: url.append("&view-hits=-1");
618: } else {
619: url
620: .append("&view-hits=")
621: .append(
622: firstHit + ".."
623: + (firstHit + viewHits - 1));
624: }
625: }
626: if (database != null)
627: url.append("&database=").append(
628: Encoder.urlEncode(database, "UTF-8"));
629: if (SSOToken != null)
630: url.append("&stoken=").append(
631: Encoder.urlEncode(SSOToken, "UTF-8"));
632: if (proxyDN != null)
633: url.append("&proxyDN=").append(
634: Encoder.urlEncode(proxyDN, "UTF-8"));
635: } catch (UnsupportedEncodingException e) {
636: } // won't happen
637: return url.toString();
638: }
639:
640: }
|