/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.search.service.searcher;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Set;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.lucene.queryParser.ParseException;
import org.olat.basesecurity.ManagerFactory;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.commons.services.search.QueryException;
import org.olat.core.commons.services.search.SearchResults;
import org.olat.core.commons.services.search.ServiceNotAvailableException;
import org.olat.core.commons.taskExecutor.TaskExecutorManager;
import org.olat.core.id.Identity;
import org.olat.core.id.Roles;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
/**
*
* Description:<br>
* This is a server side search proxy - delegates the search to the searchWorker.
*
* <P>
* Initial Date: 02.06.2008 <br>
* @author Lavinia Dumitrescu
*/
public class ClusteredSearchProvider implements OLATSearcherProxy, MessageListener {
private OLog log_ = Tracing.createLoggerFor(this.getClass());
private OLATSearcher searchWorker_;
private ConnectionFactory connectionFactory_;
private Connection connection_;
private Queue searchQueue_;
private Session session_;
private MessageConsumer consumer_;
private LinkedList<Session> sessions_ = new LinkedList<Session>();
private long receiveTimeout = 60000;
/**
* [used by spring]
*
*/
public ClusteredSearchProvider() {
//default constructor
}
public void setConnectionFactory(ConnectionFactory conFac) {
connectionFactory_ = conFac;
}
public void setSearchQueue(Queue searchQueue) {
this.searchQueue_ = searchQueue;
}
public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
}
/**
* Delegates execution to the searchWorker.
* @see org.olat.search.service.searcher.OLATSearcher#doSearch(java.lang.String, org.olat.core.id.Identity, org.olat.core.id.Roles, boolean)
*/
public SearchResults doSearch(String queryString, Identity identity, Roles roles, boolean doHighlighting) throws ServiceNotAvailableException, ParseException, QueryException {
if (searchWorker_ == null) throw new AssertException("searchWorker in ClusteredSearchProvider is null, please check the search configuration!");
return searchWorker_.doSearch(queryString, identity, roles, doHighlighting);
}
public Set<String> spellCheck(String query) throws ServiceNotAvailableException {
if (searchWorker_ == null) throw new AssertException("searchWorker in ClusteredSearchProvider is null, please check the search configuration!");
return searchWorker_.spellCheck(query);
}
/**
*
* @see org.olat.search.service.searcher.OLATSearcher#getQueryCount()
*/
public long getQueryCount() {
if (searchWorker_ == null) throw new AssertException("searchWorker in ClusteredSearchProvider is null, please check the search configuration!");
return searchWorker_.getQueryCount();
}
/**
*
* @see org.olat.search.service.searcher.OLATSearcher#stop()
*/
public void stop() {
if (searchWorker_ == null) throw new AssertException("searchWorker in ClusteredSearchProvider is null, please check the search configuration!");
searchWorker_.stop();
try {
session_.close();
connection_.close();
log_.info("ClusteredSearchProvider stopped");
} catch (JMSException e) {
log_.warn("Exception in stop ClusteredSearchProvider, ",e);
}
}
/**
* [used by spring]
*
* @see org.olat.search.service.searcher.OLATSearcherProxy#setSearchWorker(org.olat.search.service.searcher.OLATSearcher)
*/
public void setSearchWorker(OLATSearcher searchWorker) {
this.searchWorker_ = searchWorker;
}
public void springInit() throws JMSException {
connection_ = connectionFactory_.createConnection();
session_ = connection_.createSession(false, Session.AUTO_ACKNOWLEDGE);
consumer_ = session_.createConsumer(searchQueue_);
consumer_.setMessageListener(this);
connection_.start();
log_.info("ClusteredSearchProvider JMS started");
}
public void onMessage(Message message) {
if ( log_.isDebug() ) {
log_.debug("onMessage, message=" + message);
}
try{
long sentTimestamp = message.getJMSTimestamp();
long currentTimestamp = System.currentTimeMillis();
// check if received message is not too old because in case of overload we could have old search-messages
if ( (currentTimestamp - sentTimestamp) < receiveTimeout ) {
final String correlationID = message.getJMSCorrelationID();
final Destination replyTo = message.getJMSReplyTo();
if (message instanceof ObjectMessage) {
ObjectMessage objectMessage = (ObjectMessage) message;
final SearchRequest searchRequest = (SearchRequest) objectMessage.getObject();
TaskExecutorManager.getInstance().runTask(new Runnable() {
public void run() {
onSearchMessage(searchRequest, correlationID, replyTo);
}
});
} else if (message instanceof TextMessage) {
TextMessage testMessage = (TextMessage)message;
final String spellText = testMessage.getText();
TaskExecutorManager.getInstance().runTask(new Runnable() {
public void run() {
onSpellMessage(spellText, correlationID, replyTo);
}
});
}
} else {
// JMS message is too old, discard it (do nothing)
log_.warn("JMS message was too old, discard message, timeout=" + receiveTimeout + "ms , received time=" + (currentTimestamp - sentTimestamp) + "ms");
}
} catch(JMSException e) {
log_.error("error when receiving jms messages", e);
return; //signal search not available
} catch (Error err) {
log_.warn("Error in onMessage, ",err);
// OLAT-3973: don't throw exceptions here
} catch (RuntimeException runEx) {
log_.warn("RuntimeException in onMessage, ",runEx);
// OLAT-3973: don't throw exceptions here
}
}
private synchronized Session acquireSession() throws JMSException {
if (sessions_.size()==0) {
return connection_.createSession(false, Session.AUTO_ACKNOWLEDGE);
} else {
return sessions_.getFirst();
}
}
private synchronized void releaseSession(Session session) {
if (session==null) {
return;
}
sessions_.addLast(session);
}
void onSearchMessage(SearchRequest searchRequest, String correlationID, Destination replyTo) {
if ( log_.isDebug() ) {
log_.debug("onSearchMessage, correlationID=" + correlationID + " , replyTo=" + replyTo + " , searchRequest=" + searchRequest);
}
Session session = null;
try{
Identity identity = ManagerFactory.getManager().loadIdentityByKey(searchRequest.getIdentityId());
SearchResults searchResults = this.doSearch(searchRequest.getQueryString(), identity, searchRequest.getRoles(), searchRequest
.isDoHighlighting());
if (log_.isDebug()) {
log_.debug("searchResults: " + searchResults.getLength());
}
if (searchResults != null) {
session = acquireSession();
Message responseMessage = session.createObjectMessage(searchResults);
responseMessage.setJMSCorrelationID(correlationID);
responseMessage.setStringProperty(ClusteredSearchRequester.JMS_RESPONSE_STATUS_PROPERTY_NAME, ClusteredSearchRequester.JMS_RESPONSE_STATUS_OK);
MessageProducer producer = session.createProducer(replyTo);
if ( log_.isDebug() ) {
log_.debug("onSearchMessage, send ResponseMessage=" + responseMessage + " to replyTo=" + replyTo);
}
producer.send(responseMessage);
producer.close();
return;
} else {
log_.info("onSearchMessage, no searchResults (searchResults=null)");
}
} catch (JMSException e) {
log_.error("error when receiving jms messages", e);
return; //signal search not available
// do not throw exceptions here throw new OLATRuntimeException();
} catch (ServiceNotAvailableException sex) {
sendErrorResponse(ClusteredSearchRequester.JMS_RESPONSE_STATUS_SERVICE_NOT_AVAILABLE_EXCEPTION, correlationID, replyTo);
} catch (ParseException pex) {
sendErrorResponse(ClusteredSearchRequester.JMS_RESPONSE_STATUS_PARSE_EXCEPTION, correlationID, replyTo);
} catch (QueryException qex) {
sendErrorResponse(ClusteredSearchRequester.JMS_RESPONSE_STATUS_QUERY_EXCEPTION, correlationID, replyTo);
} catch (Throwable th) {
log_.error("error at ClusteredSearchProvider.receive()", th);
return;// signal search not available
// do not throw exceptions throw new OLATRuntimeException();
} finally{
releaseSession(session);
DBFactory.getInstance(false).commitAndCloseSession();
}
}
private void sendErrorResponse(String jmsResponseStatus, String correlationID, Destination replyTo) {
Session session = null;
try {
session = acquireSession();
Message responseMessage = session.createObjectMessage();
responseMessage.setJMSCorrelationID(correlationID);
responseMessage.setStringProperty(ClusteredSearchRequester.JMS_RESPONSE_STATUS_PROPERTY_NAME, jmsResponseStatus);
MessageProducer producer = session.createProducer(replyTo);
if ( log_.isDebug() ) {
log_.debug("onSearchMessage, send ResponseMessage=" + responseMessage + " to replyTo=" + replyTo);
}
producer.send(responseMessage);
producer.close();
return;
} catch (JMSException e) {
log_.error("error when receiving jms messages", e);
return; //signal search not available
} finally{
releaseSession(session);
}
}
void onSpellMessage(String spellText, String correlationID, Destination replyTo) {
Session session = null;
try {
Set<String> spellStrings = this.spellCheck(spellText);
if(spellStrings!=null) {
ArrayList<String> spellStringList = new ArrayList<String>(spellStrings);
session = acquireSession();
Message responseMessage = session.createObjectMessage(spellStringList);
responseMessage.setJMSCorrelationID(correlationID);
MessageProducer producer = session.createProducer(replyTo);
producer.send(responseMessage);
producer.close();
return;
}
return; // signal search not available
} catch (JMSException e) {
log_.error("error when receiving jms messages", e);
return; //signal search not available
// do not throw exceptions here throw new OLATRuntimeException();
} catch (Throwable th) {
log_.error("error at ClusteredSearchProvider.receive()", th);
return;// signal search not available
// do not throw exceptions throw new OLATRuntimeException();
} finally{
releaseSession(session);
DBFactory.getInstance(false).commitAndCloseSession();
}
}
}