/*
* 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.remoting;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.red5.server.Client;
import org.red5.server.api.IAttributeStore;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.remoting.IRemotingConnection;
import org.red5.server.api.remoting.IRemotingHeader;
import org.red5.server.api.scope.IBasicScope;
import org.red5.server.api.scope.IScope;
import org.red5.server.net.remoting.message.RemotingPacket;
import org.red5.server.net.servlet.ServletUtils;
/**
* Connection class so the Red5 object works in methods invoked through
* remoting. Attributes are stored in the session of the implementing
* servlet container.
*
* @author The Red5 Project (red5@osflash.org)
* @author Joachim Bauch (jojo@struktur.de)
* @author Paul Gregoire (mondain@gmail.com)
*/
public class RemotingConnection implements IRemotingConnection {
/**
* Session attribute holding an IClient object for this connection.
*/
private final static String CLIENT = "red5.client";
/**
* Scope
*/
protected IScope scope;
/**
* Servlet request
*/
protected HttpServletRequest request;
/**
* Remoting packet that triggered the connection.
*/
protected RemotingPacket packet;
/**
* Session used to store properties.
*/
protected HttpSession session;
/**
* Headers to be returned to the client.
*/
protected List<IRemotingHeader> headers = new ArrayList<IRemotingHeader>();
/**
* Create servlet connection from request and scope.
*
* @param request Servlet request
* @param scope Scope
* @param packet packet
*/
public RemotingConnection(HttpServletRequest request, IScope scope, RemotingPacket packet) {
this.request = request;
this.scope = scope;
this.packet = packet;
this.session = request.getSession();
RemotingClient client = (RemotingClient) session.getAttribute(CLIENT);
if (client == null) {
client = new RemotingClient(session.getId());
session.setAttribute(CLIENT, client);
}
client.register(this);
}
/**
* Return string representation of the connection.
*
* @return string
*/
public String toString() {
return getClass().getSimpleName() + " from " + getRemoteAddress() + ':' + getRemotePort() + " to " + getHost()
+ " (session: " + session.getId() + ')';
}
/**
* Update the current packet.
*
* @param packet
*/
protected void setPacket(RemotingPacket packet) {
this.packet = packet;
}
/**
* Throws Not supported runtime exception
*/
private void notSupported() {
throw new RuntimeException("Not supported for this type of connection");
}
/**
* Return encoding (AMF0 or AMF3).
*
* @return Encoding, currently AMF0
*/
public Encoding getEncoding() {
return packet.getEncoding();
}
/** {@inheritDoc} */
public String getType() {
return IConnection.TRANSIENT;
}
/** {@inheritDoc} */
public void initialize(IClient client) {
notSupported();
}
/** {@inheritDoc} */
public boolean connect(IScope scope) {
notSupported();
return false;
}
/** {@inheritDoc} */
public boolean connect(IScope scope, Object[] params) {
notSupported();
return false;
}
/** {@inheritDoc} */
public boolean isConnected() {
return false;
}
/** {@inheritDoc} */
public void close() {
session.invalidate();
}
/** {@inheritDoc} */
public Map<String, Object> getConnectParams() {
return packet.getHeaders();
}
/** {@inheritDoc} */
public IClient getClient() {
return (IClient) session.getAttribute(CLIENT);
}
/** {@inheritDoc} */
public String getHost() {
return request.getLocalName();
}
/** {@inheritDoc} */
public String getRemoteAddress() {
return request.getRemoteAddr();
}
/** {@inheritDoc} */
public List<String> getRemoteAddresses() {
return ServletUtils.getRemoteAddresses(request);
}
/** {@inheritDoc} */
public int getRemotePort() {
return request.getRemotePort();
}
/** {@inheritDoc} */
public String getPath() {
String path = request.getContextPath();
if (request.getPathInfo() != null) {
path += request.getPathInfo();
}
if (path.charAt(0) == '/') {
path = path.substring(1);
}
return path;
}
/** {@inheritDoc} */
public String getSessionId() {
return null;
}
/** {@inheritDoc} */
public long getReadBytes() {
return request.getContentLength();
}
/** {@inheritDoc} */
public long getWrittenBytes() {
return 0;
}
/** {@inheritDoc} */
public long getPendingMessages() {
return 0;
}
/**
* Return pending video messages number.
*
* @return Pending video messages number
*/
public long getPendingVideoMessages() {
return 0;
}
/** {@inheritDoc} */
public long getReadMessages() {
return 1;
}
/** {@inheritDoc} */
public long getWrittenMessages() {
return 0;
}
/** {@inheritDoc} */
public long getDroppedMessages() {
return 0;
}
/** {@inheritDoc} */
public void ping() {
notSupported();
}
/** {@inheritDoc} */
public int getLastPingTime() {
return -1;
}
/** {@inheritDoc} */
public IScope getScope() {
return scope;
}
/** {@inheritDoc} */
public Iterator<IBasicScope> getBasicScopes() {
notSupported();
return null;
}
public void dispatchEvent(Object event) {
notSupported();
}
/** {@inheritDoc} */
public void dispatchEvent(IEvent event) {
notSupported();
}
/** {@inheritDoc} */
public boolean handleEvent(IEvent event) {
notSupported();
return false;
}
/** {@inheritDoc} */
public void notifyEvent(IEvent event) {
notSupported();
}
/** {@inheritDoc} */
public Boolean getBoolAttribute(String name) {
return (Boolean) getAttribute(name);
}
/** {@inheritDoc} */
public Byte getByteAttribute(String name) {
return (Byte) getAttribute(name);
}
/** {@inheritDoc} */
public Double getDoubleAttribute(String name) {
return (Double) getAttribute(name);
}
/** {@inheritDoc} */
public Integer getIntAttribute(String name) {
return (Integer) getAttribute(name);
}
/** {@inheritDoc} */
public List<?> getListAttribute(String name) {
return (List<?>) getAttribute(name);
}
/** {@inheritDoc} */
public Long getLongAttribute(String name) {
return (Long) getAttribute(name);
}
/** {@inheritDoc} */
public Map<?, ?> getMapAttribute(String name) {
return (Map<?, ?>) getAttribute(name);
}
/** {@inheritDoc} */
public Set<?> getSetAttribute(String name) {
return (Set<?>) getAttribute(name);
}
/** {@inheritDoc} */
public Short getShortAttribute(String name) {
return (Short) getAttribute(name);
}
/** {@inheritDoc} */
public String getStringAttribute(String name) {
return (String) getAttribute(name);
}
/** {@inheritDoc} */
public Object getAttribute(String name) {
if (name == null) {
return null;
}
return session.getAttribute(name);
}
/** {@inheritDoc} */
public Object getAttribute(String name, Object defaultValue) {
if (name == null) {
return null;
}
// Synchronize so default value doesn't override other default value
synchronized (session) {
Object result = session.getAttribute(name);
if (result == null && defaultValue != null) {
session.setAttribute(name, defaultValue);
result = defaultValue;
}
return result;
}
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
public Set<String> getAttributeNames() {
final Set<String> result = new HashSet<String>();
// Synchronize to prevent parallel modifications
synchronized (session) {
final Enumeration<String> names = session.getAttributeNames();
while (names.hasMoreElements()) {
result.add(names.nextElement());
}
}
return Collections.unmodifiableSet(result);
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
public Map<String, Object> getAttributes() {
final Map<String, Object> result = new HashMap<String, Object>();
// Synchronize to prevent parallel modifications
synchronized (session) {
final Enumeration<String> names = session.getAttributeNames();
while (names.hasMoreElements()) {
final String name = names.nextElement();
result.put(name, session.getAttribute(name));
}
}
return Collections.unmodifiableMap(result);
}
/** {@inheritDoc} */
public boolean hasAttribute(String name) {
if (name == null) {
return false;
}
return (getAttribute(name) != null);
}
/** {@inheritDoc} */
public boolean removeAttribute(String name) {
if (name == null) {
return false;
}
// Synchronize to prevent parallel modifications
synchronized (session) {
if (!hasAttribute(name)) {
return false;
}
session.removeAttribute(name);
}
return true;
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
public void removeAttributes() {
// Synchronize to prevent parallel modifications
synchronized (session) {
final Enumeration<String> names = session.getAttributeNames();
while (names.hasMoreElements()) {
session.removeAttribute(names.nextElement());
}
}
}
/** {@inheritDoc} */
public boolean setAttribute(String name, Object value) {
if (name == null) {
return false;
}
if (value == null) {
session.removeAttribute(name);
} else {
session.setAttribute(name, value);
}
return true;
}
/** {@inheritDoc} */
public void setAttributes(Map<String, Object> values) {
for (Map.Entry<String, Object> entry : values.entrySet()) {
final String name = entry.getKey();
final Object value = entry.getValue();
if (name != null && value != null) {
session.setAttribute(name, value);
}
}
}
/** {@inheritDoc} */
public void setAttributes(IAttributeStore values) {
setAttributes(values.getAttributes());
}
/** {@inheritDoc} */
public void setBandwidth(int mbits) {
throw new UnsupportedOperationException("Not supported in this class");
}
/** {@inheritDoc} */
public void addHeader(String name, Object value) {
addHeader(name, value, false);
}
/** {@inheritDoc} */
public void addHeader(String name, Object value, boolean mustUnderstand) {
synchronized (headers) {
headers.add(new RemotingHeader(name, mustUnderstand, value));
}
}
/** {@inheritDoc} */
public void removeHeader(String name) {
addHeader(name, null, false);
}
/** {@inheritDoc} */
public Collection<IRemotingHeader> getHeaders() {
return headers;
}
/** {@inheritDoc} */
public long getClientBytesRead() {
// This is not supported for Remoting connections
return 0;
}
/**
* Cleans up the remoting connection client from the HttpSession and client
* registry.
* This should also fix APPSERVER-328
*/
public void cleanup() {
if (session != null) {
RemotingClient rc = (RemotingClient) session.getAttribute(CLIENT);
session.removeAttribute(CLIENT);
if (rc != null) {
rc.unregister(this);
}
}
}
/** Internal class for clients connected through Remoting. */
private static class RemotingClient extends Client {
private RemotingClient(String id) {
super(id, null);
}
/** {@inheritDoc} */
@Override
protected void register(IConnection conn) {
// We only have one connection per client
for (IConnection c : getConnections()) {
unregister(c);
}
super.register(conn);
}
/** {@inheritDoc} */
@Override
protected void unregister(IConnection conn) {
super.unregister(conn);
}
}
}