/* * Copyright 2014, 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.netutil.nio; import com.betfair.cougar.netutil.nio.message.*; import com.betfair.cougar.netutil.nio.message.ProtocolMessage.ProtocolMessageType; import com.betfair.cougar.util.jmx.Exportable; import com.betfair.cougar.util.jmx.JMXControl; import org.apache.mina.common.ByteBuffer; import org.apache.mina.common.IoSession; import org.apache.mina.filter.codec.ProtocolEncoderAdapter; import org.apache.mina.filter.codec.ProtocolEncoderOutput; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedResource; 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.PROTOCOL; @ManagedResource public class CougarProtocolEncoder extends ProtocolEncoderAdapter implements Exportable { private final NioLogger nioLogger; private final AtomicLong badMessagesRequested = new AtomicLong(); private final AtomicLong acceptsSent = new AtomicLong(); private final AtomicLong connectsSent = new AtomicLong(); private final AtomicLong rejectsSent = new AtomicLong(); private final AtomicLong keepAlivesSent = new AtomicLong(); private final AtomicLong messageRequestsSent = new AtomicLong(); private final AtomicLong messageResponsesSent = new AtomicLong(); private final AtomicLong disconnectsSent = new AtomicLong(); private final AtomicLong eventsSent = new AtomicLong(); private final AtomicLong suspendsSent = new AtomicLong(); private final AtomicLong tlsRequestsSent = new AtomicLong(); private final AtomicLong tlsResponsesSent = new AtomicLong(); public CougarProtocolEncoder(NioLogger nioLogger) { this.nioLogger = nioLogger; export(nioLogger.getJmxControl()); } public static ByteBuffer encode(ProtocolMessage pm, byte protocolVersion) { final ByteBuffer buffer; switch (pm.getProtocolMessageType()) { case ACCEPT: buffer = NioUtils.createMessageHeader(1, pm); buffer.put(((AcceptMessage) pm).getAcceptedVersion()); break; case CONNECT: ConnectMessage cm = (ConnectMessage) pm; buffer = NioUtils.createMessageHeader(1 + cm.getApplicationVersions().length, pm); NioUtils.writeVersionSet(buffer, cm.getApplicationVersions()); break; case REJECT: RejectMessage rm = (RejectMessage) pm; buffer = NioUtils.createMessageHeader(2 + rm.getAcceptableVersions().length, pm); buffer.put(rm.getRejectReason().getReasonCode()); NioUtils.writeVersionSet(buffer, rm.getAcceptableVersions()); break; case KEEP_ALIVE: buffer = NioUtils.createMessageHeader(0, pm); break; case DISCONNECT: buffer = NioUtils.createMessageHeader(0, pm); break; case MESSAGE_REQUEST: RequestMessage req = (RequestMessage) pm; ProtocolMessageType reqMsgType = protocolVersion == CougarProtocol.TRANSPORT_PROTOCOL_VERSION_CLIENT_ONLY_RPC ? ProtocolMessageType.MESSAGE : ProtocolMessageType.MESSAGE_REQUEST; buffer = NioUtils.createMessageHeader(req.getPayload().length + 8, reqMsgType); buffer.putLong(req.getCorrelationId()); buffer.put(req.getPayload()); break; case MESSAGE_RESPONSE: ResponseMessage resp = (ResponseMessage) pm; // backwards compatibility with version 1 protocol ProtocolMessageType responseType = protocolVersion == CougarProtocol.TRANSPORT_PROTOCOL_VERSION_CLIENT_ONLY_RPC ? ProtocolMessageType.MESSAGE : ProtocolMessageType.MESSAGE_RESPONSE; buffer = NioUtils.createMessageHeader(resp.getPayload().length + 8, responseType); buffer.putLong(resp.getCorrelationId()); buffer.put(resp.getPayload()); break; case EVENT: EventMessage em = (EventMessage) pm; if (protocolVersion < CougarProtocol.TRANSPORT_PROTOCOL_VERSION_BIDIRECTION_RPC) { return null; } buffer = NioUtils.createMessageHeader(em.getPayload().length, em); buffer.put(em.getPayload()); break; case SUSPEND: if (protocolVersion < CougarProtocol.TRANSPORT_PROTOCOL_VERSION_BIDIRECTION_RPC) { return null; } buffer = NioUtils.createMessageHeader(0, pm); break; case START_TLS_REQUEST: if (protocolVersion < CougarProtocol.TRANSPORT_PROTOCOL_VERSION_START_TLS) { return null; } buffer = NioUtils.createMessageHeader(1, pm); buffer.put(((StartTLSRequestMessage) pm).getRequirement().getValue()); break; case START_TLS_RESPONSE: if (protocolVersion < CougarProtocol.TRANSPORT_PROTOCOL_VERSION_START_TLS) { return null; } buffer = NioUtils.createMessageHeader(1, pm); buffer.put(((StartTLSResponseMessage) pm).getResult().getValue()); break; default: throw new IllegalArgumentException("Unknown ProtocolMessage [" + pm.getProtocolMessageType() + "] received"); } return buffer; } public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { final ByteBuffer buffer; if (message instanceof ProtocolMessage) { ProtocolMessage pm = (ProtocolMessage) message; nioLogger.log(PROTOCOL, session, "CougarProtocolEncoder: Writing protocol message %s", pm.getProtocolMessageType()); Byte version = (Byte) session.getAttribute(CougarProtocol.PROTOCOL_VERSION_ATTR_NAME); // go for lowest likely common denominator, since this will likely only occur for RejectMessages if (version == null) { version = CougarProtocol.TRANSPORT_PROTOCOL_VERSION_MIN_SUPPORTED; } buffer = pm.getSerialisedForm(version); if (buffer == null) { badMessagesRequested.incrementAndGet(); throw new IllegalArgumentException("Couldn't serialise ProtocolMessage [" + ((ProtocolMessage) message).getProtocolMessageType() + "]"); } switch (pm.getProtocolMessageType()) { case ACCEPT: acceptsSent.incrementAndGet(); break; case CONNECT: connectsSent.incrementAndGet(); break; case REJECT: rejectsSent.incrementAndGet(); break; case KEEP_ALIVE: keepAlivesSent.incrementAndGet(); break; case DISCONNECT: disconnectsSent.incrementAndGet(); break; case MESSAGE_REQUEST: messageRequestsSent.incrementAndGet(); nioLogger.log(ALL, session, "CougarProtocolEncoder: Writing message of length %s", (((RequestMessage) pm).getPayload().length + 8)); break; case MESSAGE_RESPONSE: messageRequestsSent.incrementAndGet(); nioLogger.log(ALL, session, "CougarProtocolEncoder: Writing message of length %s", ((ResponseMessage) pm).getPayload().length); break; case EVENT: eventsSent.incrementAndGet(); nioLogger.log(ALL, session, "CougarProtocolEncoder: Writing event of length %s", ((EventMessage) pm).getPayload().length); break; case SUSPEND: suspendsSent.incrementAndGet(); break; case START_TLS_REQUEST: tlsRequestsSent.incrementAndGet(); break; case START_TLS_RESPONSE: tlsResponsesSent.incrementAndGet(); break; default: badMessagesRequested.incrementAndGet(); throw new IllegalArgumentException("Unknown ProtocolMessage [" + ((ProtocolMessage) message).getProtocolMessageType() + "] received"); } } else { throw new IllegalArgumentException("Unknown message type " + message); } buffer.flip(); out.write(buffer); out.flush(); } /** * 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=encoder", this); } } @ManagedAttribute public long getMessageRequestsSent() { return messageRequestsSent.get(); } @ManagedAttribute public long getMessageResponsesSent() { return messageResponsesSent.get(); } @ManagedAttribute public long getEventsSent() { return eventsSent.get(); } @ManagedAttribute public long getKeepAlivesSent() { return keepAlivesSent.get(); } @ManagedAttribute public long getRejectsSent() { return rejectsSent.get(); } @ManagedAttribute public long getConnectsSent() { return connectsSent.get(); } @ManagedAttribute public long getAcceptsSent() { return acceptsSent.get(); } @ManagedAttribute public long getBadMessagesRequested() { return badMessagesRequested.get(); } @ManagedAttribute public long getSuspendsSent() { return suspendsSent.get(); } @ManagedAttribute public long getTlsRequestsSent() { return tlsRequestsSent.get(); } @ManagedAttribute public long getTlsResponsesSent() { return tlsResponsesSent.get(); } }