/* license-start
*
* Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details, at <http://www.gnu.org/licenses/>.
*
* Contributors:
* Crispico - Initial API and implementation
*
* license-end
*/
package org.flowerplatform.communication.channel;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.security.auth.Subject;
import org.flowerplatform.common.log.AuditDetails;
import org.flowerplatform.common.log.LogUtil;
import org.flowerplatform.common.util.RunnableWithParam;
import org.flowerplatform.communication.CommunicationPlugin;
import org.flowerplatform.communication.IPrincipal;
import org.flowerplatform.communication.command.IServerCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Cristi
*/
public class CommunicationChannelManager {
public static Logger logger = LoggerFactory.getLogger(CommunicationChannelManager.class);
public static final String LOGOUT_AUDIT_CATEGORY = "LOGOUT";
/**
* It's synchronized because of the methods that iterate on it. Iterating this map
* should be done rarely, as this blocks the connection/disconnection of new clients.
*
* @author Cristi
* @see #iterateCommunicationChannels(RunnableWithParam)
*
*/
protected Map<Object, CommunicationChannel> idToCommunicationChannelMap = Collections.synchronizedMap(new HashMap<Object, CommunicationChannel>());
/**
*
*/
private List<ICommunicationChannelLifecycleListener> communicationLifecycleListeners = new ArrayList<ICommunicationChannelLifecycleListener>();
/**
* Invoked when a new client connects.
*
* <p>
* Creates a new {@link WebCommunicationChannel}, adds
* it in the map and registers this as a destroy listener.
*
*/
public void messageClientCreated(Object communicationChannelId, CommunicationChannel communicationChannel) {
AuditDetails auditDetails = new AuditDetails(logger, "LOGIN");
idToCommunicationChannelMap.put(communicationChannelId, communicationChannel);
if (logger.isDebugEnabled())
logger.debug("------->>>> CLIENT " + communicationChannelId + " ADDED size = " + idToCommunicationChannelMap.size());
// notify all listeners
for (ICommunicationChannelLifecycleListener listener : communicationLifecycleListeners) {
try {
listener.communicationChannelCreated(communicationChannel);
} catch (Throwable e) {
// We catch it but continue so that the system doesn't trip.
logger.error("Exception thrown by a webCommunicationChannelCreated() handler.", e);
}
}
LogUtil.audit(auditDetails);
}
/**
* Invoked when a client disconnects (probably due to a time out).
*
* @author Cristi
*
*/
public void messageClientDestroyed(Object communicationChannelId) {
CommunicationChannel communicationChannel = idToCommunicationChannelMap.remove(communicationChannelId);
communicationChannel.dispose();
AuditDetails auditDetails = new AuditDetails(logger, LOGOUT_AUDIT_CATEGORY, communicationChannel.getCachedUserId());
if (logger.isDebugEnabled())
logger.debug("------->>>> CLIENT " + communicationChannelId + " REMOVED size = " + idToCommunicationChannelMap.size());
// notify all listeners, with a copy
for (ICommunicationChannelLifecycleListener listener : communicationLifecycleListeners) {
try {
listener.communicationChannelDestroyed(communicationChannel);
} catch (Throwable e) {
// We catch it but continue so that the system doesn't trip.
logger.error("Exception thrown by a communicationChannelDestroyed() handler.", e);
}
}
LogUtil.audit(auditDetails);
}
protected void handleReceivedObject(CommunicationChannel communicationChannel, Object object) {
IServerCommand command = (IServerCommand) object;
command.setCommunicationChannel(communicationChannel);
command.executeCommand();
}
/**
* This method is involved in the behavior of responding to the client side with objects to be processed.
* When this method returns it will use the communication channel of the owner of the message in order to obtain deferred objects
* that can be sent as a http response.
*
* @see WebCommunicationChannel
* @see WebCommunicationChannel#sendStackedObjects()
*
*/
public Object handleReceivedObject(Object messageClientId, IPrincipal principal, final Object object) {
final CommunicationChannel communicationChannel = idToCommunicationChannelMap.get(messageClientId);
try {
CommunicationPlugin.tlCurrentPrincipal.set(principal);
// Message arrived too late, a channel for corresponding client does not exist anymore.
if (communicationChannel == null)
return null;
PrivilegedAction<Void> priviledgedAction = new PrivilegedAction<Void>() {
@Override
public Void run() {
handleReceivedObject(communicationChannel, object);
return null;
}
};
communicationChannel.handleReceivedObjectWillStart(object);
Object response = null;
try {
if (principal == null) {
priviledgedAction.run();
} else {
Subject.doAsPrivileged(principal.getSubject(), priviledgedAction, null);
}
} finally {
response = communicationChannel.handleReceivedObjectEnded(object);
}
return response;
} finally {
CommunicationPlugin.tlCurrentPrincipal.set(null);
}
}
public void addWebCommunicationLifecycleListener(ICommunicationChannelLifecycleListener listener) {
communicationLifecycleListeners.add(listener);
}
/**
*
*/
public void removeWebCommunicationLifecycleListener(ICommunicationChannelLifecycleListener listener) {
communicationLifecycleListeners.remove(listener);
}
/**
* Iterates on all the communication channels. This method should be used rarely, because it locks the
* {@link #idToCommunicationChannelMap}, meaning that will block user connection/disconnection.
* In most cases, there is for sure another way of achieving the task, than iterating on all communication
* channels.
*
* <p>
* At the moment of writing, this is used by JMX methods and when a User is modified, so it is not often used.
*
* @author Cristi
* @see #idToCommunicationChannelMap
* @param runnableWithParam - Takes the current {@link WebCommunicationChannel} as parameter. If the runnable
* returns a non <code>null</code> value => the iteration stops and this method returns it.
* @return What the runnable has found, or <code>null</code>.
*/
public <R> R iterateCommunicationChannels(RunnableWithParam<R, CommunicationChannel> runnableWithParam) {
synchronized (idToCommunicationChannelMap) {
for (CommunicationChannel channel : idToCommunicationChannelMap.values()) {
R result = runnableWithParam.run(channel);
if (result != null) {
return result;
}
}
return null;
}
}
public CommunicationChannel getCommunicationChannelById(Object communicationChannelId) {
return idToCommunicationChannelMap.get(communicationChannelId);
}
}