/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright 2006-2012 by respective authors (see below). All rights reserved.
*
* 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 org.red5.server.net.rtmp;
import java.beans.ConstructorProperties;
import java.lang.management.ManagementFactory;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Semaphore;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.filterchain.IoFilterChain;
import org.apache.mina.core.future.CloseFuture;
import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.session.IoSession;
import org.red5.server.api.scope.IScope;
import org.red5.server.jmx.mxbeans.RTMPMinaConnectionMXBean;
import org.red5.server.net.protocol.ProtocolState;
import org.red5.server.net.rtmp.codec.RTMP;
import org.red5.server.net.rtmp.event.ClientBW;
import org.red5.server.net.rtmp.event.ServerBW;
import org.red5.server.net.rtmp.message.Packet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jmx.export.annotation.ManagedResource;
/**
* Represents an RTMP connection using Mina.
*
* @see "http://mina.apache.org/report/trunk/apidocs/org/apache/mina/core/session/IoSession.html"
*
* @author Paul Gregoire
*/
@ManagedResource
public class RTMPMinaConnection extends RTMPConnection implements RTMPMinaConnectionMXBean {
protected static Logger log = LoggerFactory.getLogger(RTMPMinaConnection.class);
/**
* MINA I/O session, connection between two end points
*/
private volatile IoSession ioSession;
/**
* MBean object name used for de/registration purposes.
*/
private volatile ObjectName oName;
protected int defaultServerBandwidth = 10000000;
protected int defaultClientBandwidth = 10000000;
protected boolean bandwidthDetection = true;
/** Constructs a new RTMPMinaConnection. */
@ConstructorProperties(value = { "persistent" })
public RTMPMinaConnection() {
super(PERSISTENT);
}
@SuppressWarnings("cast")
@Override
public boolean connect(IScope newScope, Object[] params) {
log.debug("Connect scope: {}", newScope);
boolean success = super.connect(newScope, params);
if (success) {
// tell the flash player how fast we want data and how fast we shall send it
getChannel(2).write(new ServerBW(defaultServerBandwidth));
// second param is the limit type (0=hard,1=soft,2=dynamic)
getChannel(2).write(new ClientBW(defaultClientBandwidth, (byte) limitType));
//if the client is null for some reason, skip the jmx registration
if (client != null) {
// perform bandwidth detection
if (bandwidthDetection && !client.isBandwidthChecked()) {
client.checkBandwidth();
}
} else {
log.warn("Client was null");
}
registerJMX();
}
return success;
}
/** {@inheritDoc} */
@Override
public void close() {
super.close();
if (ioSession != null) {
// accept no further incoming data
ioSession.suspendRead();
// clear the filter chain
IoFilterChain filters = ioSession.getFilterChain();
//check if it exists and remove
if (filters.contains("bandwidthFilter")) {
ioSession.getFilterChain().remove("bandwidthFilter");
}
// update our state
if (ioSession.containsAttribute(ProtocolState.SESSION_KEY)) {
RTMP rtmp = (RTMP) ioSession.getAttribute(ProtocolState.SESSION_KEY);
if (rtmp != null) {
log.debug("RTMP state: {}", rtmp);
rtmp.setState(RTMP.STATE_DISCONNECTING);
}
}
// close now, no flushing, no waiting
CloseFuture future = ioSession.close(true);
// wait for one second
future.addListener(new IoFutureListener<CloseFuture>() {
public void operationComplete(CloseFuture future) {
if (future.isClosed()) {
log.debug("Connection is closed");
} else {
log.debug("Connection is not yet closed");
}
//de-register with JMX
unregisterJMX();
}
});
}
}
/**
* Return MINA I/O session.
*
* @return MINA O/I session, connection between two end-points
*/
public IoSession getIoSession() {
return ioSession;
}
/**
* @return the defaultServerBandwidth
*/
public int getDefaultServerBandwidth() {
return defaultServerBandwidth;
}
/**
* @param defaultServerBandwidth the defaultServerBandwidth to set
*/
public void setDefaultServerBandwidth(int defaultServerBandwidth) {
this.defaultServerBandwidth = defaultServerBandwidth;
}
/**
* @return the defaultClientBandwidth
*/
public int getDefaultClientBandwidth() {
return defaultClientBandwidth;
}
/**
* @param defaultClientBandwidth the defaultClientBandwidth to set
*/
public void setDefaultClientBandwidth(int defaultClientBandwidth) {
this.defaultClientBandwidth = defaultClientBandwidth;
}
/**
* @return the limitType
*/
public int getLimitType() {
return limitType;
}
/**
* @param limitType the limitType to set
*/
public void setLimitType(int limitType) {
this.limitType = limitType;
}
/**
* @return the bandwidthDetection
*/
public boolean isBandwidthDetection() {
return bandwidthDetection;
}
/**
* @param bandwidthDetection the bandwidthDetection to set
*/
public void setBandwidthDetection(boolean bandwidthDetection) {
this.bandwidthDetection = bandwidthDetection;
}
/** {@inheritDoc} */
@Override
public long getPendingMessages() {
if (ioSession != null) {
return ioSession.getScheduledWriteMessages();
}
return 0;
}
/** {@inheritDoc} */
@Override
public long getReadBytes() {
if (ioSession != null) {
return ioSession.getReadBytes();
}
return 0;
}
/** {@inheritDoc} */
@Override
public long getWrittenBytes() {
if (ioSession != null) {
return ioSession.getWrittenBytes();
}
return 0;
}
public void invokeMethod(String method) {
invoke(method);
}
/** {@inheritDoc} */
@Override
public boolean isConnected() {
// XXX Paul: not sure isClosing is actually working as we expect here
return super.isConnected() && (ioSession != null && ioSession.isConnected());
}
/** {@inheritDoc} */
@Override
protected void onInactive() {
this.close();
}
/**
* Setter for MINA I/O session (connection).
*
* @param protocolSession Protocol session
*/
public void setIoSession(IoSession protocolSession) {
SocketAddress remote = protocolSession.getRemoteAddress();
if (remote instanceof InetSocketAddress) {
remoteAddress = ((InetSocketAddress) remote).getAddress().getHostAddress();
remotePort = ((InetSocketAddress) remote).getPort();
} else {
remoteAddress = remote.toString();
remotePort = -1;
}
remoteAddresses = new ArrayList<String>(1);
remoteAddresses.add(remoteAddress);
remoteAddresses = Collections.unmodifiableList(remoteAddresses);
this.ioSession = protocolSession;
}
/** {@inheritDoc} */
@Override
public void write(Packet out) {
if (ioSession != null) {
final Semaphore lock = getLock();
log.trace("Write lock wait count: {}", lock.getQueueLength());
while (!closed) {
try {
lock.acquire();
} catch (InterruptedException e) {
log.warn("Interrupted while waiting for write lock", e);
continue;
}
try {
log.debug("Writing message");
writingMessage(out);
ioSession.write(out);
break;
} finally {
lock.release();
}
}
}
}
/** {@inheritDoc} */
@Override
public void writeRaw(IoBuffer out) {
if (ioSession != null) {
final Semaphore lock = getLock();
while (!closed) {
try {
lock.acquire();
} catch (InterruptedException e) {
log.warn("Interrupted while waiting for write lock", e);
continue;
}
try {
log.debug("Writing raw message");
ioSession.write(out);
break;
} finally {
lock.release();
}
}
}
}
protected void registerJMX() {
// register with jmx
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try {
String cName = this.getClass().getName();
if (cName.indexOf('.') != -1) {
cName = cName.substring(cName.lastIndexOf('.')).replaceFirst("[\\.]", "");
}
String hostStr = host;
int port = 1935;
if (host != null && host.indexOf(":") > -1) {
String[] arr = host.split(":");
hostStr = arr[0];
port = Integer.parseInt(arr[1]);
}
// Create a new mbean for this instance
oName = new ObjectName(String.format("org.red5.server:type=%s,connectionType=%s,host=%s,port=%d,clientId=%s", cName, type, hostStr, port, client.getId()));
mbs.registerMBean(new StandardMBean(this, RTMPMinaConnectionMXBean.class, true), oName);
} catch (Exception e) {
log.warn("Error on jmx registration", e);
}
}
protected void unregisterJMX() {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
if (oName != null && mbs.isRegistered(oName)) {
try {
mbs.unregisterMBean(oName);
} catch (Exception e) {
log.warn("Exception unregistering: {}", oName, e);
}
oName = null;
}
}
}