0001: /**********************************************************************************
0002: * $URL: https://source.sakaiproject.org/svn/search/tags/sakai_2-4-1/search-impl/impl/src/java/org/sakaiproject/search/component/service/impl/SearchServiceImpl.java $
0003: * $Id: SearchServiceImpl.java 29315 2007-04-20 14:28:12Z ajpoland@iupui.edu $
0004: ***********************************************************************************
0005: *
0006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
0007: *
0008: * Licensed under the Educational Community License, Version 1.0 (the "License");
0009: * you may not use this file except in compliance with the License.
0010: * You may obtain a copy of the License at
0011: *
0012: * http://www.opensource.org/licenses/ecl1.php
0013: *
0014: * Unless required by applicable law or agreed to in writing, software
0015: * distributed under the License is distributed on an "AS IS" BASIS,
0016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: * See the License for the specific language governing permissions and
0018: * limitations under the License.
0019: *
0020: **********************************************************************************/package org.sakaiproject.search.component.service.impl;
0021:
0022: import java.io.IOException;
0023: import java.io.PrintWriter;
0024: import java.io.StringWriter;
0025: import java.security.GeneralSecurityException;
0026: import java.security.MessageDigest;
0027: import java.text.MessageFormat;
0028: import java.util.ArrayList;
0029: import java.util.Date;
0030: import java.util.HashMap;
0031: import java.util.Iterator;
0032: import java.util.List;
0033: import java.util.Map;
0034:
0035: import org.apache.commons.httpclient.HostConfiguration;
0036: import org.apache.commons.httpclient.HttpClient;
0037: import org.apache.commons.httpclient.HttpConnectionManager;
0038: import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
0039: import org.apache.commons.httpclient.methods.PostMethod;
0040: import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
0041: import org.apache.commons.logging.Log;
0042: import org.apache.commons.logging.LogFactory;
0043: import org.apache.lucene.index.Term;
0044: import org.apache.lucene.index.TermFreqVector;
0045: import org.apache.lucene.queryParser.ParseException;
0046: import org.apache.lucene.queryParser.QueryParser;
0047: import org.apache.lucene.search.BooleanClause;
0048: import org.apache.lucene.search.BooleanQuery;
0049: import org.apache.lucene.search.Filter;
0050: import org.apache.lucene.search.Hits;
0051: import org.apache.lucene.search.IndexSearcher;
0052: import org.apache.lucene.search.Query;
0053: import org.apache.lucene.search.Sort;
0054: import org.apache.lucene.search.TermQuery;
0055: import org.sakaiproject.authz.cover.SecurityService;
0056: import org.sakaiproject.component.api.ComponentManager;
0057: import org.sakaiproject.component.cover.ServerConfigurationService;
0058: import org.sakaiproject.event.api.EventTrackingService;
0059: import org.sakaiproject.event.api.NotificationEdit;
0060: import org.sakaiproject.event.api.NotificationService;
0061: import org.sakaiproject.search.api.SearchIndexBuilder;
0062: import org.sakaiproject.search.api.SearchList;
0063: import org.sakaiproject.search.api.SearchResult;
0064: import org.sakaiproject.search.api.SearchService;
0065: import org.sakaiproject.search.api.SearchStatus;
0066: import org.sakaiproject.search.api.TermFrequency;
0067: import org.sakaiproject.search.component.Messages;
0068: import org.sakaiproject.search.filter.SearchItemFilter;
0069: import org.sakaiproject.search.index.IndexStorage;
0070: import org.sakaiproject.search.model.SearchWriterLock;
0071: import org.sakaiproject.tool.api.SessionManager;
0072: import org.sakaiproject.user.api.User;
0073: import org.sakaiproject.user.api.UserDirectoryService;
0074:
0075: /**
0076: * The search service
0077: *
0078: * @author ieb
0079: */
0080: public class SearchServiceImpl implements SearchService {
0081:
0082: private static Log log = LogFactory.getLog(SearchServiceImpl.class);
0083:
0084: /**
0085: * Optional dependencies
0086: */
0087: private List triggerFunctions;
0088:
0089: /**
0090: * the notification object
0091: */
0092: private NotificationEdit notification = null;
0093:
0094: /**
0095: * init completed
0096: */
0097: private boolean initComplete = false;
0098:
0099: /**
0100: * the currently running index searcher
0101: */
0102: private IndexSearcher runningIndexSearcher;
0103:
0104: /**
0105: * The index builder dependency
0106: */
0107: private SearchIndexBuilder searchIndexBuilder;
0108:
0109: private long reloadStart;
0110:
0111: private long reloadEnd;
0112:
0113: private NotificationService notificationService;
0114:
0115: private IndexStorage indexStorage = null;
0116:
0117: private SearchItemFilter filter;
0118:
0119: private Map luceneFilters = new HashMap();
0120:
0121: private Map luceneSorters = new HashMap();
0122:
0123: private String defaultFilter = null;
0124:
0125: private String defaultSorter = null;
0126:
0127: private EventTrackingService eventTrackingService;
0128:
0129: private UserDirectoryService userDirectoryService;
0130:
0131: private SessionManager sessionManager;
0132:
0133: private String sharedKey = null;
0134:
0135: private String searchServerUrl = null;
0136:
0137: private boolean searchServer = false;
0138:
0139: private ThreadLocal localSearch = new ThreadLocal();
0140:
0141: private HttpClient httpClient;
0142:
0143: private HttpConnectionManagerParams httpParams = new HttpConnectionManagerParams();
0144:
0145: private HttpConnectionManager httpConnectionManager = new MultiThreadedHttpConnectionManager();
0146:
0147: private boolean diagnostics;
0148:
0149: private long lastGet;
0150:
0151: private Object reloadObjectSemaphore = new Object();
0152:
0153: private boolean inreload = false;
0154:
0155: /**
0156: * Register a notification action to listen to events and modify the search
0157: * index
0158: */
0159: public void init() {
0160:
0161: ComponentManager cm = org.sakaiproject.component.cover.ComponentManager
0162: .getInstance();
0163: notificationService = (NotificationService) load(cm,
0164: NotificationService.class.getName());
0165: searchIndexBuilder = (SearchIndexBuilder) load(cm,
0166: SearchIndexBuilder.class.getName());
0167: eventTrackingService = (EventTrackingService) load(cm,
0168: EventTrackingService.class.getName());
0169: sessionManager = (SessionManager) load(cm, SessionManager.class
0170: .getName());
0171: userDirectoryService = (UserDirectoryService) load(cm,
0172: UserDirectoryService.class.getName());
0173:
0174: try {
0175: if (log.isDebugEnabled()) {
0176: log.debug("init start"); //$NON-NLS-1$
0177: log.debug("checking setup"); //$NON-NLS-1$
0178: }
0179: if (indexStorage == null) {
0180: log.error(" indexStorage must be set"); //$NON-NLS-1$
0181: throw new RuntimeException("Must set indexStorage"); //$NON-NLS-1$
0182:
0183: }
0184: if (searchIndexBuilder == null) {
0185: log.error(" searchIndexBuilder must be set"); //$NON-NLS-1$
0186: throw new RuntimeException(
0187: "Must set searchIndexBuilder"); //$NON-NLS-1$
0188: }
0189: if (filter == null) {
0190: log
0191: .error("filter must be set, even if its a null filter"); //$NON-NLS-1$
0192: throw new RuntimeException("Must set filter"); //$NON-NLS-1$
0193: }
0194: if (eventTrackingService == null) {
0195: log.error("Event Tracking Service was not found"); //$NON-NLS-1$
0196: throw new RuntimeException(
0197: "Event Tracking Service was not found"); //$NON-NLS-1$
0198: }
0199: if (sessionManager == null) {
0200: log.error("Session Manager was not found"); //$NON-NLS-1$
0201: throw new RuntimeException(
0202: "Session Manager was not found"); //$NON-NLS-1$
0203: }
0204: if (userDirectoryService == null) {
0205: log.error("User Directory Service was not found"); //$NON-NLS-1$
0206: throw new RuntimeException(
0207: "User Directory Service was not found"); //$NON-NLS-1$
0208: }
0209:
0210: // register a transient notification for resources
0211: notification = notificationService
0212: .addTransientNotification();
0213:
0214: // add all the functions that are registered to trigger search index
0215: // modification
0216:
0217: notification
0218: .setFunction(SearchService.EVENT_TRIGGER_SEARCH);
0219: if (triggerFunctions != null) {
0220: for (Iterator ifn = triggerFunctions.iterator(); ifn
0221: .hasNext();) {
0222: String function = (String) ifn.next();
0223: notification.addFunction(function);
0224: if (log.isDebugEnabled()) {
0225: log.debug("Adding Search Register " + function); //$NON-NLS-1$
0226: }
0227: }
0228: }
0229:
0230: // set the filter to any site related resource
0231: notification.setResourceFilter("/"); //$NON-NLS-1$
0232:
0233: // set the action
0234: notification.setAction(new SearchNotificationAction(
0235: searchIndexBuilder));
0236:
0237: // Configure params for the Connection Manager
0238: httpParams.setDefaultMaxConnectionsPerHost(20);
0239: httpParams.setMaxTotalConnections(30);
0240:
0241: // This next line may not be necessary since we specified default 2
0242: // lines ago, but here it is anyway
0243: httpParams.setMaxConnectionsPerHost(
0244: HostConfiguration.ANY_HOST_CONFIGURATION, 20);
0245:
0246: // Set up the connection manager
0247: httpConnectionManager.setParams(httpParams);
0248:
0249: // Finally set up the static multithreaded HttpClient
0250: httpClient = new HttpClient(httpConnectionManager);
0251:
0252: initComplete = true;
0253: if (log.isDebugEnabled()) {
0254: log.debug("init end"); //$NON-NLS-1$
0255: }
0256: } catch (Throwable t) {
0257: log.error("Failed to start ", t); //$NON-NLS-1$
0258: }
0259:
0260: }
0261:
0262: private Object load(ComponentManager cm, String name) {
0263: Object o = cm.get(name);
0264: if (o == null) {
0265: log.error("Cant find Spring component named " + name); //$NON-NLS-1$
0266: }
0267: return o;
0268: }
0269:
0270: /**
0271: * @return Returns the triggerFunctions.
0272: */
0273: public List getTriggerFunctions() {
0274: return triggerFunctions;
0275: }
0276:
0277: /**
0278: * @param triggerFunctions
0279: * The triggerFunctions to set.
0280: */
0281: public void setTriggerFunctions(List triggerFunctions) {
0282: if (initComplete)
0283: throw new RuntimeException(
0284: " use register function at runtime, setTriggerFucntions is for Spring IoC only"); //$NON-NLS-1$
0285: this .triggerFunctions = triggerFunctions;
0286: }
0287:
0288: /**
0289: * {@inheritDoc}
0290: */
0291: public void registerFunction(String function) {
0292: notification.addFunction(function);
0293: if (log.isDebugEnabled()) {
0294: log.debug("Adding Function " + function); //$NON-NLS-1$
0295: }
0296: }
0297:
0298: /**
0299: * {@inheritDoc}
0300: *
0301: * @param indexFilter
0302: */
0303: public SearchList search(String searchTerms, List contexts,
0304: int start, int end) {
0305: return search(searchTerms, contexts, start, end, defaultFilter,
0306: defaultSorter);
0307: }
0308:
0309: public SearchList search(String searchTerms, List contexts,
0310: int start, int end, String filterName, String sorterName) {
0311: try {
0312: BooleanQuery query = new BooleanQuery();
0313: BooleanQuery contextQuery = new BooleanQuery();
0314: for (Iterator i = contexts.iterator(); i.hasNext();) {
0315: // Setup query so that it will allow results from any
0316: // included site, not all included sites.
0317: contextQuery.add(
0318: new TermQuery(new Term(
0319: SearchService.FIELD_SITEID, (String) i
0320: .next())),
0321: BooleanClause.Occur.SHOULD);
0322: // This would require term to be in all sites :-(
0323: // contextQuery.add(new TermQuery(new Term(
0324: // SearchService.FIELD_SITEID, (String) i.next())),
0325: // BooleanClause.Occur.MUST);
0326: }
0327:
0328: QueryParser qp = new QueryParser(
0329: SearchService.FIELD_CONTENTS, indexStorage
0330: .getAnalyzer());
0331: Query textQuery = qp.parse(searchTerms);
0332: query.add(contextQuery, BooleanClause.Occur.MUST);
0333: query.add(textQuery, BooleanClause.Occur.MUST);
0334: log.info("Compiled Query is " + query.toString()); //$NON-NLS-1$
0335:
0336: if (localSearch.get() == null && searchServerUrl != null
0337: && searchServerUrl.length() > 0) {
0338: try {
0339: PostMethod post = new PostMethod(searchServerUrl);
0340: String userId = sessionManager
0341: .getCurrentSessionUserId();
0342: StringBuffer sb = new StringBuffer();
0343: for (Iterator ci = contexts.iterator(); ci
0344: .hasNext();) {
0345: sb.append(ci.next()).append(";"); //$NON-NLS-1$
0346: }
0347: String contextParam = sb.toString();
0348: post.setParameter(REST_CHECKSUM, digestCheck(
0349: userId, searchTerms));
0350: post.setParameter(REST_CONTEXTS, contextParam);
0351: post.setParameter(REST_END, String.valueOf(end));
0352: post
0353: .setParameter(REST_START, String
0354: .valueOf(start));
0355: post.setParameter(REST_TERMS, searchTerms);
0356: post.setParameter(REST_USERID, userId);
0357:
0358: int status = httpClient.executeMethod(post);
0359: if (status != 200) {
0360: throw new RuntimeException(
0361: "Failed to perform remote search, http status was " + status); //$NON-NLS-1$
0362: }
0363:
0364: String response = post.getResponseBodyAsString();
0365: return new SearchListResponseImpl(response,
0366: textQuery, start, end, indexStorage
0367: .getAnalyzer(), filter,
0368: searchIndexBuilder, this );
0369: } catch (Exception ex) {
0370:
0371: log.error("Remote Search Failed ", ex); //$NON-NLS-1$
0372: throw new IOException(ex.getMessage());
0373: }
0374:
0375: } else {
0376:
0377: IndexSearcher indexSearcher = getIndexSearcher(false);
0378: if (indexSearcher != null) {
0379: Hits h = null;
0380: Filter indexFilter = (Filter) luceneFilters
0381: .get(filterName);
0382: Sort indexSorter = (Sort) luceneSorters
0383: .get(sorterName);
0384: if (log.isDebugEnabled()) {
0385: log.debug("Using Filter " + filterName + ":" //$NON-NLS-1$ //$NON-NLS-2$
0386: + indexFilter
0387: + " and " + sorterName + ":" //$NON-NLS-1$ //$NON-NLS-2$
0388: + indexSorter);
0389: }
0390: if (indexFilter != null && indexSorter != null) {
0391: h = indexSearcher.search(query, indexFilter,
0392: indexSorter);
0393: } else if (indexFilter != null) {
0394: h = indexSearcher.search(query, indexFilter);
0395: } else if (indexSorter != null) {
0396: h = indexSearcher.search(query, indexSorter);
0397: } else {
0398: h = indexSearcher.search(query);
0399: }
0400: if (log.isDebugEnabled()) {
0401: log.debug("Got " + h.length() + " hits"); //$NON-NLS-1$ //$NON-NLS-2$
0402: }
0403: eventTrackingService
0404: .post(eventTrackingService.newEvent(
0405: EVENT_SEARCH, EVENT_SEARCH_REF
0406: + textQuery.toString(),
0407: true,
0408: NotificationService.PREF_IMMEDIATE));
0409: return new SearchListImpl(h, textQuery, start, end,
0410: indexStorage.getAnalyzer(), filter,
0411: searchIndexBuilder, this );
0412: } else {
0413: throw new RuntimeException(
0414: "Failed to start the Lucene Searche Engine"); //$NON-NLS-1$
0415: }
0416: }
0417:
0418: } catch (ParseException e) {
0419: throw new RuntimeException("Failed to parse Query ", e); //$NON-NLS-1$
0420: } catch (IOException e) {
0421: throw new RuntimeException("Failed to run Search ", e); //$NON-NLS-1$
0422: }
0423: }
0424:
0425: /**
0426: * {@inheritDoc}
0427: */
0428: public void reload() {
0429: getIndexSearcher(true);
0430: }
0431:
0432: public void forceReload() {
0433: reloadStart = 0;
0434: }
0435:
0436: public IndexSearcher getIndexSearcher(boolean reload) {
0437: long reloadWaitEnd = System.currentTimeMillis() + 10L * 1000L;
0438: if (inreload) {
0439: long waitStart = System.currentTimeMillis();
0440: log.info("Waiting for Index Reload to complete");
0441: while (inreload
0442: && (System.currentTimeMillis() < reloadWaitEnd)) {
0443: /*
0444: * When a reload is in progress we must wait here on a timeout
0445: * for it to have some chance of finishing. If it doesnt finish,
0446: * we will just grab the index searcher anyway It may cause
0447: * problems, but we cant throttle request threads for more than
0448: * 10 seconds.
0449: */
0450: Thread.yield();
0451: }
0452: log.info("Reload Complete, request paused for "
0453: + (System.currentTimeMillis() - waitStart) + "ms");
0454: if (System.currentTimeMillis() > reloadWaitEnd) {
0455: log.warn(" Index Reload Timeout, trying old Index ");
0456: }
0457: }
0458: if (runningIndexSearcher == null
0459: || (reload && !searchIndexBuilder.isLocalLock())) {
0460:
0461: long lastUpdate = indexStorage.getLastUpdate();
0462:
0463: if (lastUpdate > reloadStart
0464: || runningIndexSearcher == null) {
0465: synchronized (reloadObjectSemaphore) {
0466: /*
0467: * If we just go for reload, tehn there is a chance that
0468: * there will be an active search request depending on
0469: * search segments that are about to be deleted. on the
0470: * basis that a search typically < 100ms to complete and
0471: * release retrieve the results in fact it often takes <
0472: * 20ms, we should not try and reload if an index searcher
0473: * was retrieved in the last 100ms. That will prevent us
0474: * removing search index files from form an active search
0475: * load. The next problem that will happen is that searchers
0476: * will come in here when the index is being reloaded. This
0477: * is less of a problem, but we should probably throttle
0478: * while the load is going on to prevent a index sercher
0479: * that is in the process of being reloaded get into a
0480: * request cycle.
0481: */
0482:
0483: long endWait = System.currentTimeMillis() + 20L * 1000L;
0484: while ((System.currentTimeMillis() < endWait)
0485: && (System.currentTimeMillis() < lastGet + 100L)) {
0486: Thread.yield();
0487: }
0488:
0489: /*
0490: * Danger Zone, if something gets in here it may loose
0491: * segments.
0492: */
0493: inreload = true;
0494: try {
0495: /**
0496: * check again if we really need to reload, annother
0497: * thread could have been in progress
0498: */
0499: lastUpdate = indexStorage.getLastUpdate();
0500: if (lastUpdate > reloadStart
0501: || runningIndexSearcher == null) {
0502: reloadStart = System.currentTimeMillis();
0503: /*
0504: * Did we fail to get a reload lock, if so we just
0505: * have to ignore the reload request and wait for
0506: * the next one ?
0507: */
0508: if (System.currentTimeMillis() < endWait) {
0509:
0510: if (log.isDebugEnabled()) {
0511: log
0512: .debug("Reloading Index, force=" + reload); //$NON-NLS-1$
0513: }
0514: try {
0515:
0516: // dont leave closing the index searcher to
0517: // the
0518: // GC.
0519: // It
0520: // may
0521: // not happen fast enough.
0522:
0523: IndexSearcher newRunningIndexSearcher = indexStorage
0524: .getIndexSearcher();
0525:
0526: IndexSearcher oldRunningIndexSearcher = runningIndexSearcher;
0527: runningIndexSearcher = newRunningIndexSearcher;
0528:
0529: if (oldRunningIndexSearcher != null) {
0530: try {
0531: indexStorage
0532: .closeIndexSearcher(oldRunningIndexSearcher);
0533: } catch (Exception ex) {
0534: log
0535: .error(
0536: "Failed to close old searcher ", ex); //$NON-NLS-1$
0537: }
0538: }
0539:
0540: reloadEnd = System
0541: .currentTimeMillis();
0542: if (diagnostics) {
0543: log
0544: .info("Index Reloaded containing "
0545: + getNDocs()
0546: + " active documents and "
0547: + getPendingDocs()
0548: + " pending documents in "
0549: + (reloadEnd - reloadStart)
0550: + "ms");
0551: }
0552: } catch (IOException e) {
0553: reloadStart = reloadEnd;
0554: }
0555: } else {
0556: log
0557: .warn("Unable to reload index, there was too much search "
0558: + "activity on this node, will try again on the next "
0559: + "index reload event ");
0560: }
0561: }
0562: } finally {
0563: inreload = false;
0564: }
0565:
0566: }
0567: } else {
0568: if (log.isDebugEnabled()) {
0569: log.debug("No Reload lastUpdate " + lastUpdate //$NON-NLS-1$
0570: + " < lastReload " + reloadStart); //$NON-NLS-1$
0571: }
0572:
0573: }
0574: }
0575:
0576: lastGet = System.currentTimeMillis();
0577: return runningIndexSearcher;
0578: }
0579:
0580: public void refreshInstance() {
0581: searchIndexBuilder.refreshIndex();
0582:
0583: }
0584:
0585: public void rebuildInstance() {
0586: searchIndexBuilder.rebuildIndex();
0587: }
0588:
0589: public void refreshSite(String currentSiteId) {
0590: searchIndexBuilder.refreshIndex(currentSiteId);
0591: }
0592:
0593: public void rebuildSite(String currentSiteId) {
0594: searchIndexBuilder.rebuildIndex(currentSiteId);
0595:
0596: }
0597:
0598: public String getStatus() {
0599:
0600: String lastLoad = (new Date(reloadEnd)).toString();
0601: String loadTime = String
0602: .valueOf((double) (0.001 * (reloadEnd - reloadStart)));
0603: SearchWriterLock lock = searchIndexBuilder.getCurrentLock();
0604: List lockNodes = searchIndexBuilder.getNodeStatus();
0605:
0606: return Messages.getString("SearchServiceImpl.40") + lastLoad + Messages.getString("SearchServiceImpl.38") + loadTime + Messages.getString("SearchServiceImpl.37"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
0607: }
0608:
0609: public int getNDocs() {
0610: try {
0611: return getIndexSearcher(false).getIndexReader().numDocs();
0612: } catch (Exception e) {
0613: return -1;
0614: }
0615: }
0616:
0617: public int getPendingDocs() {
0618: return searchIndexBuilder.getPendingDocuments();
0619: }
0620:
0621: public List getAllSearchItems() {
0622: return searchIndexBuilder.getAllSearchItems();
0623: }
0624:
0625: public List getSiteMasterSearchItems() {
0626: return searchIndexBuilder.getSiteMasterSearchItems();
0627: }
0628:
0629: public List getGlobalMasterSearchItems() {
0630: return searchIndexBuilder.getGlobalMasterSearchItems();
0631: }
0632:
0633: public SearchStatus getSearchStatus() {
0634: String ll = Messages.getString("SearchServiceImpl.36"); //$NON-NLS-1$
0635: String lt = ""; //$NON-NLS-1$
0636: if (reloadEnd != 0) {
0637: ll = (new Date(reloadEnd)).toString();
0638: lt = String
0639: .valueOf((double) (0.001 * (reloadEnd - reloadStart)));
0640: }
0641: final String lastLoad = ll;
0642: final String loadTime = lt;
0643: final SearchWriterLock lock = searchIndexBuilder
0644: .getCurrentLock();
0645: final List lockNodes = searchIndexBuilder.getNodeStatus();
0646: final String pdocs = String.valueOf(getPendingDocs());
0647: final String ndocs = String.valueOf(getNDocs());
0648:
0649: return new SearchStatus() {
0650: public String getLastLoad() {
0651: return lastLoad;
0652: }
0653:
0654: public String getLoadTime() {
0655: return loadTime;
0656: }
0657:
0658: public String getCurrentWorker() {
0659: return lock.getNodename();
0660: }
0661:
0662: public String getCurrentWorkerETC() {
0663: if (SecurityService.isSuperUser()) {
0664: return MessageFormat
0665: .format(
0666: Messages
0667: .getString("SearchServiceImpl.35"), //$NON-NLS-1$
0668: new Object[] {
0669: lock.getExpires(),
0670: searchIndexBuilder
0671: .getLastDocument(),
0672: searchIndexBuilder
0673: .getLastElapsed(),
0674: searchIndexBuilder
0675: .getCurrentDocument(),
0676: searchIndexBuilder
0677: .getCurrentElapsed(),
0678: ServerConfigurationService
0679: .getServerIdInstance() });
0680: } else {
0681: return MessageFormat
0682: .format(
0683: Messages
0684: .getString("SearchServiceImpl.39"), new Object[] { lock //$NON-NLS-1$
0685: .getExpires() });
0686: }
0687: }
0688:
0689: public List getWorkerNodes() {
0690: List l = new ArrayList();
0691: for (Iterator i = lockNodes.iterator(); i.hasNext();) {
0692: SearchWriterLock swl = (SearchWriterLock) i.next();
0693: Object[] result = new Object[3];
0694: result[0] = swl.getNodename();
0695: result[1] = swl.getExpires();
0696: if (lock.getNodename().equals(swl.getNodename())) {
0697: result[2] = Messages
0698: .getString("SearchServiceImpl.47"); //$NON-NLS-1$
0699: } else {
0700: result[2] = Messages
0701: .getString("SearchServiceImpl.48"); //$NON-NLS-1$
0702: }
0703: l.add(result);
0704: }
0705: return l;
0706: }
0707:
0708: public String getNDocuments() {
0709: return ndocs;
0710: }
0711:
0712: public String getPDocuments() {
0713: return pdocs;
0714: }
0715:
0716: };
0717:
0718: }
0719:
0720: public boolean removeWorkerLock() {
0721: return searchIndexBuilder.removeWorkerLock();
0722:
0723: }
0724:
0725: /**
0726: * @return Returns the indexStorage.
0727: */
0728: public IndexStorage getIndexStorage() {
0729: return indexStorage;
0730: }
0731:
0732: /**
0733: * @param indexStorage
0734: * The indexStorage to set.
0735: */
0736: public void setIndexStorage(IndexStorage indexStorage) {
0737: this .indexStorage = indexStorage;
0738: }
0739:
0740: /**
0741: * @return Returns the filter.
0742: */
0743: public SearchItemFilter getFilter() {
0744: return filter;
0745: }
0746:
0747: /**
0748: * @param filter
0749: * The filter to set.
0750: */
0751: public void setFilter(SearchItemFilter filter) {
0752: this .filter = filter;
0753: }
0754:
0755: /**
0756: * @return Returns the defaultFilter.
0757: */
0758: public String getDefaultFilter() {
0759: return defaultFilter;
0760: }
0761:
0762: /**
0763: * @param defaultFilter
0764: * The defaultFilter to set.
0765: */
0766: public void setDefaultFilter(String defaultFilter) {
0767: this .defaultFilter = defaultFilter;
0768: }
0769:
0770: /**
0771: * @return Returns the defaultSorter.
0772: */
0773: public String getDefaultSorter() {
0774: return defaultSorter;
0775: }
0776:
0777: /**
0778: * @param defaultSorter
0779: * The defaultSorter to set.
0780: */
0781: public void setDefaultSorter(String defaultSorter) {
0782: this .defaultSorter = defaultSorter;
0783: }
0784:
0785: /**
0786: * @return Returns the luceneFilters.
0787: */
0788: public Map getLuceneFilters() {
0789: return luceneFilters;
0790: }
0791:
0792: /**
0793: * @param luceneFilters
0794: * The luceneFilters to set.
0795: */
0796: public void setLuceneFilters(Map luceneFilters) {
0797: this .luceneFilters = luceneFilters;
0798: }
0799:
0800: /**
0801: * @return Returns the luceneSorters.
0802: */
0803: public Map getLuceneSorters() {
0804: return luceneSorters;
0805: }
0806:
0807: /**
0808: * @param luceneSorters
0809: * The luceneSorters to set.
0810: */
0811: public void setLuceneSorters(Map luceneSorters) {
0812: this .luceneSorters = luceneSorters;
0813: }
0814:
0815: public List getSegmentInfo() {
0816: return indexStorage.getSegmentInfoList();
0817: }
0818:
0819: public TermFrequency getTerms(int documentId) throws IOException {
0820: final TermFreqVector tf = getIndexSearcher(false)
0821: .getIndexReader().getTermFreqVector(documentId,
0822: FIELD_CONTENTS);
0823: // TODO Auto-generated method stub
0824: return new TermFrequency() {
0825: public String[] getTerms() {
0826: if (tf != null) {
0827: return tf.getTerms();
0828: }
0829: return new String[0];
0830: }
0831:
0832: public int[] getFrequencies() {
0833: if (tf != null) {
0834: return tf.getTermFrequencies();
0835: }
0836: return new int[0];
0837: }
0838: };
0839: }
0840:
0841: public String searchXML(Map parameterMap) {
0842: String userid = null;
0843: String searchTerms = null;
0844: String checksum = null;
0845: String contexts = null;
0846: String ss = null;
0847: String se = null;
0848: try {
0849: if (!searchServer) {
0850: throw new Exception(Messages
0851: .getString("SearchServiceImpl.49")); //$NON-NLS-1$
0852: }
0853: String[] useridA = (String[]) parameterMap.get(REST_USERID);
0854: String[] searchTermsA = (String[]) parameterMap
0855: .get(REST_TERMS);
0856: String[] checksumA = (String[]) parameterMap
0857: .get(REST_CHECKSUM);
0858: String[] contextsA = (String[]) parameterMap
0859: .get(REST_CONTEXTS);
0860: String[] ssA = (String[]) parameterMap.get(REST_START);
0861: String[] seA = (String[]) parameterMap.get(REST_END);
0862:
0863: StringBuffer sb = new StringBuffer();
0864: sb.append("<?xml version=\"1.0\"?>"); //$NON-NLS-1$
0865:
0866: boolean requestError = false;
0867: if (useridA == null || useridA.length != 1) {
0868: requestError = true;
0869: } else {
0870: userid = useridA[0];
0871: }
0872: if (searchTermsA == null || searchTermsA.length != 1) {
0873: requestError = true;
0874: } else {
0875: searchTerms = searchTermsA[0];
0876: }
0877: if (checksumA == null || checksumA.length != 1) {
0878: requestError = true;
0879: } else {
0880: checksum = checksumA[0];
0881: }
0882: if (contextsA == null || contextsA.length != 1) {
0883: requestError = true;
0884: } else {
0885: contexts = contextsA[0];
0886: }
0887: if (ssA == null || ssA.length != 1) {
0888: requestError = true;
0889: } else {
0890: ss = ssA[0];
0891: }
0892: if (seA == null || seA.length != 1) {
0893: requestError = true;
0894: } else {
0895: se = seA[0];
0896: }
0897:
0898: if (requestError) {
0899: throw new Exception(Messages
0900: .getString("SearchServiceImpl.34")); //$NON-NLS-1$
0901:
0902: }
0903:
0904: int searchStart = Integer.parseInt(ss);
0905: int searchEnd = Integer.parseInt(se);
0906: String[] ctxa = contexts.split(";"); //$NON-NLS-1$
0907: List ctx = new ArrayList(ctxa.length);
0908: for (int i = 0; i < ctxa.length; i++) {
0909: ctx.add(ctxa[i]);
0910: }
0911:
0912: if (sharedKey != null && sharedKey.length() > 0) {
0913: String check = digestCheck(userid, searchTerms);
0914: if (!check.equals(checksum)) {
0915: throw new Exception(Messages
0916: .getString("SearchServiceImpl.53")); //$NON-NLS-1$
0917: }
0918: }
0919:
0920: org.sakaiproject.tool.api.Session s = sessionManager
0921: .startSession();
0922: User u = userDirectoryService.getUser("admin"); //$NON-NLS-1$
0923: s.setUserId(u.getId());
0924: sessionManager.setCurrentSession(s);
0925: localSearch.set("localsearch"); //$NON-NLS-1$
0926: try {
0927:
0928: SearchList sl = search(searchTerms, ctx, searchStart,
0929: searchEnd);
0930: sb.append("<results "); //$NON-NLS-1$
0931: sb.append(" fullsize=\"").append(sl.getFullSize()) //$NON-NLS-1$
0932: .append("\" "); //$NON-NLS-1$
0933: sb
0934: .append(" start=\"").append(sl.getStart()).append("\" "); //$NON-NLS-1$ //$NON-NLS-2$
0935: sb.append(" size=\"").append(sl.size()).append("\" "); //$NON-NLS-1$ //$NON-NLS-2$
0936: sb.append(" >"); //$NON-NLS-1$
0937: for (Iterator si = sl.iterator(); si.hasNext();) {
0938: SearchResult sr = (SearchResult) si.next();
0939: sr.toXMLString(sb);
0940: }
0941: sb.append("</results>"); //$NON-NLS-1$
0942: return sb.toString();
0943: } finally {
0944: sessionManager.setCurrentSession(null);
0945: localSearch.set(null);
0946: }
0947: } catch (Exception ex) {
0948: StringBuffer sb = new StringBuffer();
0949: sb.append("<?xml version=\"1.0\"?>"); //$NON-NLS-1$
0950: sb.append("<fault>"); //$NON-NLS-1$
0951: sb.append("<request>"); //$NON-NLS-1$
0952: sb.append("<![CDATA["); //$NON-NLS-1$
0953: sb
0954: .append(" userid = ").append(StringUtils.xmlEscape(userid)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
0955: sb
0956: .append(" searchTerms = ").append(StringUtils.xmlEscape(searchTerms)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
0957: sb
0958: .append(" checksum = ").append(StringUtils.xmlEscape(checksum)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
0959: sb
0960: .append(" contexts = ").append(StringUtils.xmlEscape(contexts)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
0961: sb
0962: .append(" ss = ").append(StringUtils.xmlEscape(ss)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
0963: sb
0964: .append(" se = ").append(StringUtils.xmlEscape(se)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
0965: sb.append("]]>"); //$NON-NLS-1$
0966: sb.append("</request>"); //$NON-NLS-1$
0967: sb.append("<error>"); //$NON-NLS-1$
0968: sb.append("<![CDATA["); //$NON-NLS-1$
0969: try {
0970: StringWriter sw = new StringWriter();
0971: PrintWriter pw = new PrintWriter(sw);
0972: ex.printStackTrace(pw);
0973: pw.flush();
0974: sb.append(sw.toString());
0975: pw.close();
0976: sw.close();
0977: } catch (Exception ex2) {
0978: sb
0979: .append(
0980: "Failed to serialize exception " + ex.getMessage()) //$NON-NLS-1$
0981: .append("\n"); //$NON-NLS-1$
0982: sb.append("Case: " + ex2.getMessage()); //$NON-NLS-1$
0983:
0984: }
0985: sb.append("]]>"); //$NON-NLS-1$
0986: sb.append("</error>"); //$NON-NLS-1$
0987: sb.append("</fault>"); //$NON-NLS-1$
0988: return sb.toString();
0989: }
0990: }
0991:
0992: private String digestCheck(String userid, String searchTerms)
0993: throws GeneralSecurityException, IOException {
0994: MessageDigest sha1 = MessageDigest.getInstance("SHA1"); //$NON-NLS-1$
0995: String chstring = sharedKey + userid + searchTerms;
0996: return byteArrayToHexStr(sha1
0997: .digest(chstring.getBytes("UTF-8"))); //$NON-NLS-1$
0998: }
0999:
1000: private static String byteArrayToHexStr(byte[] data) {
1001: char[] chars = new char[data.length * 2];
1002: for (int i = 0; i < data.length; i++) {
1003: byte current = data[i];
1004: int hi = (current & 0xF0) >> 4;
1005: int lo = current & 0x0F;
1006: chars[2 * i] = (char) (hi < 10 ? ('0' + hi)
1007: : ('A' + hi - 10));
1008: chars[2 * i + 1] = (char) (lo < 10 ? ('0' + lo)
1009: : ('A' + lo - 10));
1010: }
1011: return new String(chars);
1012: }
1013:
1014: /**
1015: * @return the sharedKey
1016: */
1017: public String getSharedKey() {
1018: return sharedKey;
1019: }
1020:
1021: /**
1022: * @param sharedKey
1023: * the sharedKey to set
1024: */
1025: public void setSharedKey(String sharedKey) {
1026: this .sharedKey = sharedKey;
1027: }
1028:
1029: /**
1030: * @return the searchServerUrl
1031: */
1032: public String getSearchServerUrl() {
1033: return searchServerUrl;
1034: }
1035:
1036: /**
1037: * @param searchServerUrl
1038: * the searchServerUrl to set
1039: */
1040: public void setSearchServerUrl(String searchServerUrl) {
1041: this .searchServerUrl = searchServerUrl;
1042: }
1043:
1044: /**
1045: * @return the searchServer
1046: */
1047: public boolean isSearchServer() {
1048: return searchServer;
1049: }
1050:
1051: /**
1052: * @param searchServer
1053: * the searchServer to set
1054: */
1055: public void setSearchServer(boolean searchServer) {
1056: this .searchServer = searchServer;
1057: }
1058:
1059: /*
1060: * (non-Javadoc)
1061: *
1062: * @see org.sakaiproject.search.api.Diagnosable#disableDiagnostics()
1063: */
1064: public void disableDiagnostics() {
1065: diagnostics = false;
1066: indexStorage.disableDiagnostics();
1067: searchIndexBuilder.disableDiagnostics();
1068:
1069: }
1070:
1071: /*
1072: * (non-Javadoc)
1073: *
1074: * @see org.sakaiproject.search.api.Diagnosable#enableDiagnostics()
1075: */
1076: public void enableDiagnostics() {
1077: diagnostics = true;
1078: indexStorage.enableDiagnostics();
1079: searchIndexBuilder.enableDiagnostics();
1080: }
1081:
1082: /*
1083: * (non-Javadoc)
1084: *
1085: * @see org.sakaiproject.search.api.Diagnosable#hasDiagnostics()
1086: */
1087: public boolean hasDiagnostics() {
1088: return diagnostics;
1089: }
1090:
1091: public boolean getDiagnostics() {
1092: return diagnostics;
1093: }
1094:
1095: public void setDiagnostics(boolean diagnostics) {
1096: if (diagnostics) {
1097: enableDiagnostics();
1098: } else {
1099: disableDiagnostics();
1100: }
1101: }
1102:
1103: }
|