/*
* Copyright 2013, The Sporting Exchange Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.betfair.cougar.transport.nio;
import com.betfair.cougar.netutil.nio.*;
import com.betfair.cougar.netutil.nio.message.EventMessage;
import com.betfair.cougar.netutil.nio.message.RequestMessage;
import com.betfair.cougar.netutil.nio.message.ResponseMessage;
import com.betfair.cougar.transport.api.TransportCommandProcessor;
import com.betfair.cougar.transport.api.protocol.CougarObjectIOFactory;
import com.betfair.cougar.transport.api.protocol.CougarObjectInput;
import com.betfair.cougar.transport.api.protocol.CougarObjectOutput;
import com.betfair.cougar.transport.api.protocol.socket.SocketBindingDescriptor;
import com.betfair.cougar.transport.socket.SocketTransportCommand;
import com.betfair.cougar.transport.socket.SocketTransportCommandImpl;
import com.betfair.cougar.transport.socket.SocketTransportCommandProcessor;
import com.betfair.cougar.transport.socket.SocketTransportRPCCommandImpl;
import com.betfair.cougar.util.jmx.Exportable;
import com.betfair.cougar.util.jmx.JMXControl;
import org.apache.mina.common.IoHandler;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import static com.betfair.cougar.netutil.nio.NioLogger.LoggingLevel.ALL;
import static com.betfair.cougar.netutil.nio.NioLogger.LoggingLevel.SESSION;
@ManagedResource
public class ExecutionVenueServerHandler extends IoHandlerAdapter implements Exportable {
private final static Logger LOG = LoggerFactory.getLogger(ExecutionVenueServerHandler.class);
private TransportCommandProcessor<SocketTransportCommand> processor;
private final NioLogger sessionLogger;
private final AtomicLong requestsReceived = new AtomicLong();
private final AtomicLong eventsReceived = new AtomicLong();
private final AtomicLong sessionsOpened = new AtomicLong();
private final AtomicLong sessionsClosed = new AtomicLong();
private final AtomicLong otherExceptions = new AtomicLong();
private final AtomicLong ioExceptions = new AtomicLong();
private final CougarObjectIOFactory objectIOFactory;
private List<HandlerListener> listeners = new CopyOnWriteArrayList<HandlerListener>();
private final ConcurrentHashMap<IoSession, String> sessions = new ConcurrentHashMap<IoSession, String>();
public void addListener(HandlerListener listener) {
listeners.add(listener);
}
protected ExecutionVenueServerHandler() {
sessionLogger = null;
objectIOFactory = null;
}
public ExecutionVenueServerHandler(NioLogger sessionLogger, TransportCommandProcessor<SocketTransportCommand> processor,
CougarObjectIOFactory objectIOFactory) {
this.sessionLogger = sessionLogger;
this.processor = processor;
this.objectIOFactory = objectIOFactory;
export(sessionLogger.getJmxControl());
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
if (message instanceof ResponseMessage) {
((IoHandler) session.getAttribute(RequestResponseManager.SESSION_KEY)).messageReceived(session, message);
}
else if (message instanceof RequestMessage) {
RequestMessage req = (RequestMessage) message;
final CougarObjectOutput out = objectIOFactory.newCougarObjectOutput(new ByteArrayOutputStreamWithIoSession(session, req.getCorrelationId()), CougarProtocol.getProtocolVersion(session));
final CougarObjectInput in = objectIOFactory.newCougarObjectInput(new ByteArrayInputStream(req.getPayload()), CougarProtocol.getProtocolVersion(session));
final String remoteAddress = ((InetSocketAddress) session.getRemoteAddress()).getAddress().getHostAddress();
final SocketTransportCommandImpl command = new SocketTransportRPCCommandImpl(in, out, remoteAddress, session);
requestsReceived.incrementAndGet();
processor.process(command);
sessionLogger.log(ALL, session, "ExecutionVenueServerHandler - Message %s processed", req.getCorrelationId());
}
else if (message instanceof EventMessage) {
EventMessage em = (EventMessage) message;
final CougarObjectInput in = objectIOFactory.newCougarObjectInput(new ByteArrayInputStream(em.getPayload()), CougarProtocol.getProtocolVersion(session));
final String remoteAddress = ((InetSocketAddress) session.getRemoteAddress()).getAddress().getHostAddress();
final SocketTransportCommandImpl command = new SocketTransportCommandImpl(in, remoteAddress, session);
eventsReceived.incrementAndGet();
processor.process(command);
} else {
LOG.warn("ExecutionVenueServerHandler - Received unexpected message type: " + message + " - closing session");
sessionLogger.log(NioLogger.LoggingLevel.SESSION, session, "ExecutionVenueServerHandler - Received unexpected message type: %s - closing session", message);
session.close();
}
}
@Override
public void sessionClosed(IoSession session) throws Exception {
super.sessionClosed(session);
sessionsClosed.incrementAndGet();
sessions.remove(session);
sessionLogger.log(SESSION, session, "ExecutionVenueServerHandler: Session closed");
for (HandlerListener listener : listeners) {
listener.sessionClosed(session);
}
}
@Override
public void sessionOpened(IoSession session) throws Exception {
super.sessionOpened(session);
sessionsOpened.incrementAndGet();
sessions.put(session, "");
sessionLogger.log(SESSION, session, "ExecutionVenueServerHandler: Session opened");
for (HandlerListener listener : listeners) {
listener.sessionOpened(session);
}
}
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
if (cause instanceof IOException) {
ioExceptions.incrementAndGet();
// We arrive here when the output pipe is broken. Broken network connections are not
// really exceptional and should not be reported by dumping the stack trace.
// Instead a summary debug level log message with some relevant info
sessionLogger.log(ALL, session, "ExecutionVenueServerHandler: IOException received on session - closing");
} else {
otherExceptions.incrementAndGet();
sessionLogger.log(SESSION, session, "ExecutionVenueServerHandler: Unexpected exception from session - see main log for details");
LOG.warn("Unexpected exception from session " + NioUtils.getSessionId(session), cause);
}
session.close();
}
public void notify(SocketBindingDescriptor bindingDescriptor) {
processor.bind(bindingDescriptor);
}
/**
* Exports this service as an MBean, if the JMXControl is available
*/
@Override
public void export(JMXControl jmxControl) {
if (jmxControl != null) {
jmxControl.registerMBean("CoUGAR.socket.transport:name=handler", this);
}
}
@ManagedOperation
public String getSessionsDetails(boolean html) {
StringBuilder buffer = new StringBuilder();
if (html) buffer.append("<pre>");
for (IoSession session : sessions.keySet()) {
buffer.append("SessionId=").append(NioUtils.getSessionId(session)).append(",")
.append("remoteHost=").append(session.getRemoteAddress()).append(",")
.append("connected=").append(session.isConnected()).append(",")
.append("closing=").append(session.isClosing())
.append('\n');
}
if (html) buffer.append("</pre>");
return buffer.toString();
}
public long getOutstandingRequests() {
return ((SocketTransportCommandProcessor)processor).getOutstandingRequests();
}
@ManagedAttribute
public long getRequestsReceived() {
return requestsReceived.get();
}
@ManagedAttribute
public long getEventsReceived() {
return eventsReceived.get();
}
@ManagedAttribute
public long getSessionsOpened() {
return sessionsOpened.get();
}
@ManagedAttribute
public long getSessionsClosed() {
return sessionsClosed.get();
}
@ManagedAttribute
public long getIoExceptionsClosingSession() {
return ioExceptions.get();
}
@ManagedAttribute
public long getOtherExceptionsClosingSession() {
return otherExceptions.get();
}
}