/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <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 the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <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>
* Initial code contributed and copyrighted by<br>
* BPS Bildungsportal Sachsen GmbH, http://www.bps-system.de
* <p>
*/
package de.bps.onyx.util;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.Session;
import org.olat.core.id.Identity;
import org.olat.course.ICourse;
import org.olat.course.nodes.CourseNode;
import org.olat.ims.qti.QTIResultSet;
import de.bps.onyx.plugin.wsserver.TestState;
public class ExamPoolManagerProxy extends ExamPoolManager {
protected static final String JMS_RESPONSE_STATUS_PROPERTY_NAME = "response_status";
protected static final String JMS_RESPONSE_STATUS_OK = "ok";
protected static final String JMS_RESPONSE_STATUS_PARSE_EXCEPTION = "parse_exception";
protected static final String JMS_RESPONSE_STATUS_QUERY_EXCEPTION = "query_exception";
protected static final String JMS_RESPONSE_STATUS_SERVICE_NOT_AVAILABLE_EXCEPTION = "service_not_available";
private final AtomicLong messageCount = new AtomicLong(0); //counter for this cluster node
private ConnectionFactory connectionFactory;
private Queue examControllQueue;
private long receiveTimeout = 45000;
private long timeToLive = 45000;
private Connection connection;
private final LinkedList<Destination> tempQueues = new LinkedList<Destination>();
private final LinkedList<Session> sessions = new LinkedList<Session>();
private final static int DEFAULT_SLEEPTIME = 25;
public ExamPoolManagerProxy() {
super();
log.info("Created new ExamPoolManagerProxy");
}
public void setConnectionFactory(ConnectionFactory conFac) {
connectionFactory = conFac;
}
public void setSearchQueue(Queue searchQueue) {
this.examControllQueue = searchQueue;
}
public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
}
public void setTimeToLive(long timeToLive) {
this.timeToLive = timeToLive;
}
public void springInit() throws JMSException {
connection = connectionFactory.createConnection();
connection.start();
log.info("springInit: JMS connection started with connectionFactory=" + connectionFactory);
}
public void springStop() throws JMSException {
closeQueue();
}
private void closeQueue() {
if(connection != null) {
try {
connection.close();
} catch (JMSException e) {
log.error("", e);
}
}
}
/**
* Uses Request/reply mechanism for synchronous operation.
*
* This method should be called basically in all overriden methods /
* whenever a operation needs to make changes cluster-wide changes or to
* reload changes in the cluster [signaled by a recived {@link ExamEvent}]
*
* @param message
* the message to send
* @return the new or updated {@link ExamPool}
*/
private ExamPool callJMSExamPoolManager(JMSExamMessage message) {
long begin = System.currentTimeMillis();
ExamPool result = null;
if (log.isDebug()) {
log.debug("RequestMessage=" + message);
}
Session session = null;
try {
session = acquireSession();
if (log.isDebug()) {
log.debug("poolManagerSession=" + session);
}
Message requestMessage = session.createObjectMessage(message);
Message returnedMessage = callJMXExamPoolManagerProvider(session, requestMessage);
messageCount.incrementAndGet();
if (returnedMessage != null) {
String responseStatus = returnedMessage.getStringProperty(JMS_RESPONSE_STATUS_PROPERTY_NAME);
if (responseStatus.equalsIgnoreCase(JMS_RESPONSE_STATUS_OK)) {
result = (ExamPool) ((ObjectMessage) returnedMessage).getObject();
} else {
log.warn("poolManager: receive unkown responseStatus=" + responseStatus);
}
} else {
//null returnedMessage is ok ?!?
}
} catch (JMSException e) {
log.error("Unable to communicate in cluster", e);
} finally {
releaseSession(session);
}
if(log.isDebug()){
log.debug("Request took: " + (System.currentTimeMillis() - begin));
}
return result;
}
private Message callJMXExamPoolManagerProvider(Session session, Message message) throws JMSException {
Destination replyQueue = acquireTempQueue(session);
if (log.isDebug()) {
log.debug("doSearchRequest replyQueue=" + replyQueue);
}
try {
MessageConsumer responseConsumer = session.createConsumer(replyQueue);
message.setJMSReplyTo(replyQueue);
String correlationId = createRandomString();
message.setJMSCorrelationID(correlationId);
MessageProducer producer = session.createProducer(examControllQueue);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
producer.setTimeToLive(timeToLive);
if (log.isDebug()) {
log.debug("Sending search request message with correlationId=" + correlationId);
}
producer.send(message);
producer.close();
Message returnedMessage = null;
final long start = System.currentTimeMillis();
while (true) {
final long diff = (start + receiveTimeout) - System.currentTimeMillis();
if (diff <= 0) {
// timeout
log.info("Timeout in search. Remaining time zero or negative.");
break;
}
if (log.isDebug()) {
log.debug("doSearchRequest: call receive with timeout=" + diff);
}
returnedMessage = responseConsumer.receive(diff);
if (returnedMessage == null) {
// timeout case, we're stopping now with a reply...
log.info("Timeout in search. Repy was null.");
break;
} else if (!correlationId.equals(returnedMessage.getJMSCorrelationID())) {
// we got an old reply from a previous search request
log.info("Got a response with a wrong correlationId. Ignoring and waiting for the next");
try {
Thread.sleep(DEFAULT_SLEEPTIME);
} catch (InterruptedException e) {
log.warn("Waiting for message interrupted.", e);
}
continue;
} else {
// we got a valid reply
break;
}
}
responseConsumer.close();
if (log.isDebug()) {
log.debug("doSearchRequest: returnedMessage=" + returnedMessage);
}
return returnedMessage;
} finally {
releaseTempQueue(replyQueue);
}
}
private String createRandomString() {
Random random = new Random(System.currentTimeMillis());
long randomLong = random.nextLong();
return Long.toHexString(randomLong);
}
private synchronized Destination acquireTempQueue(Session session) throws JMSException {
if (tempQueues.size() == 0) {
if (session == null) {
Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination tempQ = s.createTemporaryQueue();
s.close();
return tempQ;
}
return session.createTemporaryQueue();
} else {
return tempQueues.removeFirst();
}
}
private synchronized Session acquireSession() throws JMSException {
if (sessions.size() == 0) {
return connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
} else {
return sessions.removeFirst();
}
}
private synchronized void releaseTempQueue(Destination tempQueue) {
if (tempQueue == null) {
return;
}
tempQueues.addLast(tempQueue);
}
private synchronized void releaseSession(Session session) {
if (session == null) {
return;
}
sessions.addLast(session);
}
@Override
protected ExamPool getExamPool(ICourse course, CourseNode courseNode) {
ExamPool pool = null;
JMSExamMessage message = new JMSExamMessage(JMSExamMessageCommand.GET_EXAMPOOL, null, course.getResourceableId(), courseNode.getIdent(), null, null,
null);
pool = callJMSExamPoolManager(message);
return pool;
}
@Override
protected ExamPool getExamPool(Long testSessionId) {
ExamPool pool = null;
JMSExamMessage message = new JMSExamMessage(JMSExamMessageCommand.GET_EXAMPOOL, testSessionId, null, null, null, null, null);
pool = callJMSExamPoolManager(message);
return pool;
}
@Override
public Long addStudentToExamPool(ICourse course, CourseNode courseNode, Identity student, TestState state, QTIResultSet resultSet) {
List<Identity> students = new ArrayList<Identity>();
students.add(student);
List<QTIResultSet> results = new ArrayList<QTIResultSet>();
results.add(resultSet);
JMSExamMessage message = new JMSExamMessage(JMSExamMessageCommand.ADD_STUDENT, null, course.getResourceableId(), courseNode.getIdent(), students,
results, state);
callJMSExamPoolManager(message);
return null;
}
@Override
public void controllExam(Long testSessionId, List<Identity> selectedIdentities, TestState state) {
JMSExamMessage message = new JMSExamMessage(JMSExamMessageCommand.CONTROLL_EXAM, testSessionId, null, null, selectedIdentities, null, state);
callJMSExamPoolManager(message);
}
@Override
public void changeExamState(Long testSessionId, List<Identity> identities, TestState state) {
JMSExamMessage message = new JMSExamMessage(JMSExamMessageCommand.CHANGE_STATE, testSessionId, null, null, identities, null, state);
callJMSExamPoolManager(message);
}
}
/*
history:
$Log: ExamPoolManagerProxy.java,v $
Revision 1.4 2012-06-13 10:18:46 blaw
OLATCE-2007
OLATCE-2290
OLATCE-2189
OLATCE-1425
* improved performance and logging for exam-control
Revision 1.3 2012-04-10 13:37:02 blaw
OLATCE-1425
* more logging
Revision 1.2 2012-04-05 13:49:41 blaw
OLATCE-1425
* added history
* better indention
* refactored referencess for ExamPoolManagers to the abstract class
* added yesNoDialog for StartExam-function
* added more gui-warnings and / or fallback-values if student- or exam-values are not available
*/