/*
* 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;
import java.beans.ConstructorProperties;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.scope.IBasicScope;
import org.red5.server.api.scope.IScope;
import org.red5.server.scope.Scope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base abstract class for connections. Adds connection specific functionality like work with clients
* to AttributeStore.
*/
public abstract class BaseConnection extends AttributeStore implements IConnection {
private static final Logger log = LoggerFactory.getLogger(BaseConnection.class);
/**
* Connection type
*/
protected final String type;
/**
* Connection host
*/
protected volatile String host;
/**
* Connection remote address
*/
protected volatile String remoteAddress;
/**
* Connection remote addresses
*/
protected volatile List<String> remoteAddresses;
/**
* Remote port
*/
protected volatile int remotePort;
/**
* Path of scope client connected to
*/
protected volatile String path;
/**
* Connection session identifier
*/
protected volatile String sessionId;
/**
* Number of read messages
*/
protected AtomicLong readMessages = new AtomicLong(0);
/**
* Number of written messages
*/
protected AtomicLong writtenMessages = new AtomicLong(0);
/**
* Number of dropped messages
*/
protected AtomicLong droppedMessages = new AtomicLong(0);
/**
* Connection params passed from client with NetConnection.connect call
*
* @see <a href='http://livedocs.adobe.com/fms/2/docs/00000570.html'>NetConnection in Flash Media Server docs (external)</a>
*/
@SuppressWarnings("all")
protected volatile Map<String, Object> params = null;
/**
* Client bound to connection
*/
protected volatile IClient client;
/**
* Scope that connection belongs to
*/
protected volatile Scope scope;
/**
* Set of basic scopes.
*/
protected Set<IBasicScope> basicScopes = new CopyOnWriteArraySet<IBasicScope>();
/**
* Is the connection closed?
*/
protected volatile boolean closed;
/**
* Used to protect mulit-threaded operations on write
*/
private final Semaphore writeLock = new Semaphore(1, true);
/**
* Used for generation of client ids that may be shared across the server
*/
private final static AtomicInteger clientIdGenerator = new AtomicInteger(0);
/**
* Creates a new persistent base connection
*/
@ConstructorProperties(value = { "persistent" })
public BaseConnection() {
log.debug("New BaseConnection");
this.type = PERSISTENT;
}
/**
* Creates a new base connection with the given type.
*
* @param type Connection type
*/
@ConstructorProperties({ "type" })
public BaseConnection(String type) {
log.debug("New BaseConnection - type: {}", type);
this.type = type;
}
/**
* Creates a new base connection with the given parameters.
*
* @param type Connection type
* @param host Host
* @param remoteAddress Remote address
* @param remotePort Remote port
* @param path Scope path on server
* @param sessionId Session id
* @param params Params passed from client
*/
@ConstructorProperties({ "type", "host", "remoteAddress", "remotePort", "path", "sessionId" })
public BaseConnection(String type, String host, String remoteAddress, int remotePort, String path, String sessionId, Map<String, Object> params) {
log.debug("New BaseConnection - type: {} host: {} remoteAddress: {} remotePort: {} path: {} sessionId: {}", new Object[] { type, host, remoteAddress, remotePort, path,
sessionId });
log.debug("Params: {}", params);
this.type = type;
this.host = host;
this.remoteAddress = remoteAddress;
this.remoteAddresses = new ArrayList<String>(1);
this.remoteAddresses.add(remoteAddress);
this.remoteAddresses = Collections.unmodifiableList(this.remoteAddresses);
this.remotePort = remotePort;
this.path = path;
this.sessionId = sessionId;
this.params = params;
}
/**
* Returns the next available client id.
*
* @return new client id
*/
public static int getNextClientId() {
return clientIdGenerator.incrementAndGet();
}
/**
* @return lock for changing state operations
*/
public Semaphore getLock() {
return writeLock;
}
/**
* Initializes client
* @param client Client bound to connection
*/
public void initialize(IClient client) {
if (this.client != null && this.client instanceof Client) {
// Unregister old client
((Client) this.client).unregister(this, false);
}
this.client = client;
if (this.client instanceof Client) {
// Register new client
((Client) this.client).register(this);
}
}
/**
*
* @return type
*/
public String getType() {
return type;
}
/**
*
* @return host
*/
public String getHost() {
return host;
}
/**
*
* @return remote address
*/
public String getRemoteAddress() {
return remoteAddress;
}
/**
* @return remote address
*/
public List<String> getRemoteAddresses() {
return remoteAddresses;
}
/**
*
* @return remote port
*/
public int getRemotePort() {
return remotePort;
}
/**
*
* @return path
*/
public String getPath() {
return path;
}
/**
*
* @return session id
*/
public String getSessionId() {
return sessionId;
}
/**
* Return connection parameters
* @return connection parameters
*/
public Map<String, Object> getConnectParams() {
return Collections.unmodifiableMap(params);
}
/**
*
* @return client
*/
public IClient getClient() {
return client;
}
/**
* Check whether connection is alive
* @return true if connection is bound to scope, false otherwise
*/
public boolean isConnected() {
return scope != null;
}
/**
* Connect to another scope on server
* @param newScope New scope
* @return true on success, false otherwise
*/
public boolean connect(IScope newScope) {
return connect(newScope, new Object[] {});
}
/**
* Connect to another scope on server with given parameters
* @param newScope New scope
* @param params Parameters to connect with
* @return true on success, false otherwise
*/
public boolean connect(IScope newScope, Object[] params) {
if (log.isDebugEnabled()) {
log.debug("Connect Params: {}", params);
for (Object e : params) {
log.debug("Param: {}", e);
}
}
final Scope oldScope = scope;
scope = (Scope) newScope;
// disconnect from old scope(s), then reconnect to new scopes.
// this is necessary because there may be an intersection between the hierarchies.
if (oldScope != null) {
oldScope.disconnect(this);
}
return scope.connect(this, params);
}
/**
*
* @return scope
*/
public IScope getScope() {
return scope;
}
/**
* Closes connection
*/
public void close() {
if (closed || scope == null) {
log.debug("Close, not connected nothing to do.");
return;
}
closed = true;
log.debug("Close, disconnect from scope, and children");
try {
// Unregister all child scopes first
for (IBasicScope basicScope : basicScopes) {
unregisterBasicScope(basicScope);
}
} catch (Exception err) {
log.error("Error while unregistering basic scopes.", err);
}
// Disconnect
try {
scope.disconnect(this);
} catch (Exception err) {
log.error("Error while disconnecting from scope: {}. {}", scope, err);
}
// Unregister client
if (client != null && client instanceof Client) {
((Client) client).unregister(this);
/* Following code should be unnecessary and was causing client to set its registry to null (via tmp.disconnect())
* resulting NPE + memory leaks in various cases
Client tmp = (Client) client;
tmp.unregister(this);
tmp.removeAttributes();
tmp.disconnect();
client = null;*/
}
scope = null;
}
/**
* Notified on event
* @param event Event
*/
public void notifyEvent(IEvent event) {
log.debug("Event notify was not handled: {}", event);
}
/**
* Dispatches event
* @param event Event
*/
public void dispatchEvent(IEvent event) {
log.debug("Event notify was not dispatched: {}", event);
}
/**
* Handles event
* @param event Event
* @return true if associated scope was able to handle event, false otherwise
*/
public boolean handleEvent(IEvent event) {
return getScope().handleEvent(event);
}
/**
*
* @return basic scopes
*/
public Iterator<IBasicScope> getBasicScopes() {
return basicScopes.iterator();
}
/**
* Registers basic scope
* @param basicScope Basic scope to register
*/
public void registerBasicScope(IBasicScope basicScope) {
basicScopes.add(basicScope);
basicScope.addEventListener(this);
}
/**
* Unregister basic scope
*
* @param basicScope Unregister basic scope
*/
public void unregisterBasicScope(IBasicScope basicScope) {
basicScopes.remove(basicScope);
basicScope.removeEventListener(this);
}
/**
*
* @return bytes read
*/
public abstract long getReadBytes();
/**
*
* @return bytes written
*/
public abstract long getWrittenBytes();
/**
*
* @return messages read
*/
public long getReadMessages() {
return readMessages.get();
}
/**
*
* @return messages written
*/
public long getWrittenMessages() {
return writtenMessages.get();
}
/**
*
* @return dropped messages
*/
public long getDroppedMessages() {
return droppedMessages.get();
}
/**
*
* @return pending messages
*/
public long getPendingMessages() {
return 0;
}
/**
*
* @param streamId the id you want to know about
* @return pending messages for this streamId
*/
public long getPendingVideoMessages(int streamId) {
return 0;
}
/** {@inheritDoc} */
public long getClientBytesRead() {
return 0;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
if (host != null) {
result = prime * result + host.hashCode();
}
if (remoteAddress != null) {
result = prime * result + remoteAddress.hashCode();
}
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
BaseConnection other = (BaseConnection) obj;
if (host != null && !host.equals(other.getHost())) {
return false;
}
if (remoteAddress != null && !remoteAddress.equals(other.getRemoteAddress())) {
return false;
}
return true;
}
}