/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * *** * * Community License: GPL 3.0 * * This file 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, either version 3 of the License, * or (at your option) any later version. * * This file 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * *** * * Available Commercial License: GraniteDS SLA 1.0 * * This is the appropriate option if you are creating proprietary * applications and you are not prepared to distribute and share the * source code of your application under the GPL v3 license. * * Please visit http://www.granitedataservices.com/license for more * details. */ package org.granite.client.tide.server; import java.lang.reflect.Constructor; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Observer; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.inject.Named; import org.granite.client.configuration.Configuration; import org.granite.client.messaging.ClientAliasRegistry; import org.granite.client.messaging.Consumer; import org.granite.client.messaging.Producer; import org.granite.client.messaging.RemoteService; import org.granite.client.messaging.ResultFaultIssuesResponseListener; import org.granite.client.messaging.ServerApp; import org.granite.client.messaging.TopicAgent; import org.granite.client.messaging.TopicSubscriptionListener; import org.granite.client.messaging.channel.Channel; import org.granite.client.messaging.channel.ChannelBuilder; import org.granite.client.messaging.channel.ChannelException; import org.granite.client.messaging.channel.ChannelFactory; import org.granite.client.messaging.channel.ChannelStatusListener; import org.granite.client.messaging.channel.ChannelStatusNotifier; import org.granite.client.messaging.channel.MessagingChannel; import org.granite.client.messaging.channel.ReauthenticateCallback; import org.granite.client.messaging.channel.RemotingChannel; import org.granite.client.messaging.channel.SessionAwareChannel; import org.granite.client.messaging.channel.UsernamePasswordCredentials; import org.granite.client.messaging.codec.MessagingCodec.ClientType; import org.granite.client.messaging.events.Event; import org.granite.client.messaging.events.FaultEvent; import org.granite.client.messaging.events.IncomingMessageEvent; import org.granite.client.messaging.events.IssueEvent; import org.granite.client.messaging.events.ResultEvent; import org.granite.client.messaging.messages.ResponseMessage; import org.granite.client.messaging.messages.responses.FaultMessage; import org.granite.client.messaging.messages.responses.FaultMessage.Code; import org.granite.client.messaging.messages.responses.ResultMessage; import org.granite.client.messaging.transport.Transport; import org.granite.client.messaging.transport.TransportException; import org.granite.client.messaging.transport.TransportStatusHandler; import org.granite.client.platform.Platform; import org.granite.client.tide.ApplicationConfigurable; import org.granite.client.tide.Context; import org.granite.client.tide.ContextAware; import org.granite.client.tide.Identity; import org.granite.client.tide.impl.FaultHandler; import org.granite.client.tide.impl.ResultHandler; import org.granite.client.validation.InvalidValue; import org.granite.config.ConvertersConfig; import org.granite.logging.Logger; import org.granite.util.ContentType; import org.granite.util.TypeUtil; /** * ServerSession provides an API to manage all communications with the server * It can be setup as a managed bean with Spring or CDI or created manually and attached to a Tide context * * <pre> * {@code * ServerSession serverSession = tideContext.set(new ServerSession("/myapp", "localhost", 8080)); * } * </pre> * * @author William DRAI */ @ApplicationConfigurable @Named public class ServerSession implements ContextAware { private static Logger log = Logger.getLogger(ServerSession.class); public static final String SERVER_TIME_TAG = "org.granite.time"; public static final String SESSION_ID_TAG = "org.granite.sessionId"; public static final String SESSION_EXP_TAG = "org.granite.sessionExp"; public static final String CONTEXT_RESULT = "org.granite.tide.result"; public static final String CONTEXT_FAULT = "org.granite.tide.fault"; public static final String LOGIN = "org.granite.client.tide.login"; public static final String LOGOUT = "org.granite.client.tide.logout"; public static final String SESSION_EXPIRED = "org.granite.client.tide.sessionExpired"; private ServerApp serverApp; private ContentType contentType = ContentType.JMF_AMF; private ClientAliasRegistry aliasRegistry; private Class<? extends ChannelFactory> channelFactoryClass = null; private Transport remotingTransport = null; private Transport messagingTransport = null; private Map<String, Transport> messagingTransports = new HashMap<String, Transport>(); private Context context = null; private Status status = new DefaultStatus(); private String sessionId = null; private Long defaultMaxReconnectAttempts = null; private boolean logoutOnSessionExpiration = true; private LogoutState logoutState = new LogoutState(1500); private String destination = "server"; private Object platformContext = null; private ChannelBuilder defaultChannelBuilder = null; private String defaultChannelType = null; private ChannelFactory channelFactory; private RemotingChannel remotingChannel = null; private Map<String, MessagingChannel> messagingChannelsByType = new HashMap<String, MessagingChannel>(); protected Map<String, RemoteService> remoteServices = new HashMap<String, RemoteService>(); protected Map<String, TopicAgent> topicAgents = new HashMap<String, TopicAgent>(); private Set<String> packageNames = new HashSet<String>(); public ServerSession() throws Exception { // Used for testing/proxying } /** * Create a server session for the specified context root and server * @param contextRoot context root * @param serverName server host name * @param serverPort server port * @throws Exception */ public ServerSession(String contextRoot, String serverName, int serverPort) { this(contextRoot, false, serverName, serverPort); } /** * Create a server session for the specified context root and server * @param contextRoot context root * @param secure server app is secured (https/wss/...) * @param serverName server host name * @param serverPort server port * @throws Exception */ public ServerSession(String contextRoot, boolean secure, String serverName, int serverPort) { this.serverApp = new ServerApp(contextRoot, secure, serverName, serverPort); } /** * Create a server session for the specified server app * @param serverApp server application definition */ public ServerSession(ServerApp serverApp) { this.serverApp = serverApp; } /** * Serialization type (default is JMF) * @return content type */ public ContentType getContentType() { return contentType; } /** * Set the serialization type * @param contentType serialization type */ public void setContentType(ContentType contentType) { if (contentType == null) throw new NullPointerException("contentType cannot be null"); this.contentType = contentType; } /** * Set the default channel builder * @param channelBuilder channel builder */ public void setDefaultChannelBuilder(ChannelBuilder channelBuilder) { this.defaultChannelBuilder = channelBuilder; if (channelFactory != null) channelFactory.setDefaultChannelBuilder(defaultChannelBuilder); } /** * Set the default channel type for messaging * @param channelType channel type */ public void setDefaultChannelType(String channelType) { this.defaultChannelType = channelType; if (channelFactory != null) channelFactory.setDefaultChannelType(defaultChannelType); } /** * Set a custom channel factory class * @param channelFactoryClass channel factory class */ public void setChannelFactoryClass(Class<? extends ChannelFactory> channelFactoryClass) { this.channelFactoryClass = channelFactoryClass; } /** * Set the server app for this server session * @param serverApp server application definition */ public void setServerApp(ServerApp serverApp) { this.serverApp = serverApp; } public void setLogoutTimeout(long timeout) { logoutState.setTimeout(timeout); } public void setDefaultMaxReconnectAttemps(long maxReconnectAttempts) { this.defaultMaxReconnectAttempts = maxReconnectAttempts; } public void setLogoutOnSessionExpiration(boolean logout) { this.logoutOnSessionExpiration = logout; } /** * Set the Tide context for this server session * (internal method, should be set by the context itself) * @param context Tide context * @see org.granite.client.tide.ContextAware */ public void setContext(Context context) { this.context = context; if (platformContext == null) platformContext = context.getPlatformContext(); } /** * Current Tide context * @return Tide context */ public Context getContext() { return this.context; } /** * Set the platform context for this server session * @param platformContext */ public void setPlatformContext(Object platformContext) { this.platformContext = platformContext; } /** * Set an implementation of the Status interface to be notified of server related information * @param status status */ public void setStatus(Status status) { this.status = status; } /** * Status implementation * @return status */ public Status getStatus() { return status; } /** * Set the remoting transport * @param transport remoting transport */ public void setRemotingTransport(Transport transport) { this.remotingTransport = transport; } /** * Set the default messaging transport * @param transport messaging transport */ public void setMessagingTransport(Transport transport) { this.messagingTransport = transport; } /** * Set the messaging transport for the specified channel type * @param channelType channel type */ public void setMessagingTransport(String channelType, Transport transport) { this.messagingTransports.put(channelType, transport); } /** * Add a package name to scan for remote aliases * @param packageName package name */ public void addRemoteAliasPackage(String packageName) { this.packageNames.add(packageName); } /** * Reset all package names to scan for remote aliases * @param packageNames package names */ public void setRemoteAliasPackages(Set<String> packageNames) { this.packageNames.clear(); this.packageNames.addAll(packageNames); } /** * Return the current alias registry for this session * @return alias registry */ public ClientAliasRegistry getAliasRegistry() { return aliasRegistry; } private ConvertersConfig convertersConfig = null; public Object convert(Object value, Type expectedType) { if (contentType == ContentType.JMF_AMF || convertersConfig == null) return value; return convertersConfig.getConverters().convert(value, expectedType); } /** * Configure and start the server session */ public void start() { if (channelFactory != null) // Already started return; aliasRegistry = new ClientAliasRegistry(); aliasRegistry.registerAlias(InvalidValue.class); if (channelFactoryClass != null) { Constructor<? extends ChannelFactory> constructor = null; try { constructor = channelFactoryClass.getConstructor(Object.class, Configuration.class); Configuration configuration = Platform.getInstance().newConfiguration(); configuration.setClientType(ClientType.JAVA); configuration.load(); convertersConfig = configuration.getGraniteConfig(); try { channelFactory = constructor.newInstance(platformContext, configuration); } catch (Exception e) { throw new RuntimeException("Could not create ChannelFactory", e); } } catch (NoSuchMethodException nsme) { try { constructor = channelFactoryClass.getConstructor(Object.class); channelFactory = constructor.newInstance(platformContext); } catch (Exception e) { throw new RuntimeException("Could not create ChannelFactory", e); } } } else if (contentType == ContentType.JMF_AMF) { try { channelFactory = (ChannelFactory)TypeUtil.newInstance("org.granite.client.messaging.channel.JMFChannelFactory", new Class<?>[] { Object.class }, new Object[] { platformContext }); } catch (Exception e) { throw new RuntimeException("Could not create JMFChannelFactory", e); } } else { Configuration configuration = Platform.getInstance().newConfiguration(); configuration.setClientType(ClientType.JAVA); configuration.load(); convertersConfig = configuration.getGraniteConfig(); try { channelFactory = (ChannelFactory)TypeUtil.newInstance("org.granite.client.messaging.channel.AMFChannelFactory", new Class<?>[] { Object.class, Configuration.class }, new Object[] { platformContext, configuration }); } catch (Exception e) { throw new RuntimeException("Could not create AMFChannelFactory", e); } } channelFactory.setAliasRegistry(aliasRegistry); channelFactory.setScanPackageNames(packageNames); if (defaultChannelType != null) channelFactory.setDefaultChannelType(defaultChannelType); if (remotingTransport != null) channelFactory.setRemotingTransport(remotingTransport); if (messagingTransport != null) channelFactory.setMessagingTransport(messagingTransport); for (Map.Entry<String, Transport> me : messagingTransports.entrySet()) channelFactory.setMessagingTransport(me.getKey(), me.getValue()); if (defaultChannelBuilder != null) channelFactory.setDefaultChannelBuilder(defaultChannelBuilder); if (defaultTimeToLive >= 0) channelFactory.setDefaultTimeToLive(defaultTimeToLive); if (defaultMaxReconnectAttempts != null) channelFactory.setDefaultMaxReconnectAttempts(defaultMaxReconnectAttempts); channelFactory.start(); channelFactory.getRemotingTransport().setStatusHandler(statusHandler); for (Transport transport : channelFactory.getMessagingTransports().values()) transport.setStatusHandler(statusHandler); remotingChannel = channelFactory.newRemotingChannel("graniteamf", serverApp, 1); setupChannel(remotingChannel); sessionExpirationTimer = Executors.newSingleThreadScheduledExecutor(); } /** * Stop the server session and cleanup resources */ public void stop() { try { if (sessionExpirationFuture != null) { sessionExpirationFuture.cancel(false); sessionExpirationFuture = null; } if (sessionExpirationTimer != null) { sessionExpirationTimer.shutdownNow(); sessionExpirationTimer = null; } } finally { if (channelFactory != null) { channelFactory.stop(); channelFactory = null; } unsetupChannel(remotingChannel); remotingChannel = null; for (Channel messagingChannel : messagingChannelsByType.values()) unsetupChannel(messagingChannel); messagingChannelsByType.clear(); aliasRegistry = null; } } /** * Internal SPI to define how remoting/messaging elements are created */ public static interface ServiceFactory { /** * Create a remote service for the specified channel and destination * @param remotingChannel channel * @param destination destination name * @return remote service */ public RemoteService newRemoteService(RemotingChannel remotingChannel, String destination); /** * Create a producer * @param messagingChannel channel * @param destination destination name * @param topic subtopic * @return producer */ public Producer newProducer(MessagingChannel messagingChannel, String destination, String topic); /** * Create a consumer * @param messagingChannel channel * @param destination destination name * @param topic subtopic * @return consumer */ public Consumer newConsumer(MessagingChannel messagingChannel, String destination, String topic); } private static class DefaultServiceFactory implements ServiceFactory { @Override public RemoteService newRemoteService(RemotingChannel remotingChannel, String destination) { return new RemoteService(remotingChannel, destination); } @Override public Producer newProducer(MessagingChannel messagingChannel, String destination, String topic) { return new Producer(messagingChannel, destination, topic); } @Override public Consumer newConsumer(MessagingChannel messagingChannel, String destination, String topic) { return new Consumer(messagingChannel, destination, topic); } } private ServiceFactory serviceFactory = new DefaultServiceFactory(); /** * Set a custom service factory to create producer/consumers and remote services * @param serviceFactory service factory */ public void setServiceFactory(ServiceFactory serviceFactory) { this.serviceFactory = serviceFactory; } private void setupChannel(Channel channel) { channel.addListener(channelStatusBinder); channel.bindStatus(channelStatusBinder); if (channel instanceof MessagingChannel) ((MessagingChannel)channel).setReauthenticateCallback(reauthenticateCallback); } private void unsetupChannel(Channel channel) { channel.removeListener(channelStatusBinder); channel.unbindStatus(channelStatusBinder); if (channel instanceof MessagingChannel) ((MessagingChannel)channel).setReauthenticateCallback(null); } private class ChannelStatusBinder implements ChannelStatusListener, ChannelStatusNotifier { private List<ChannelStatusListener> listeners = new ArrayList<ChannelStatusListener>(); @Override public void addListener(ChannelStatusListener listener) { listeners.add(listener); } @Override public void removeListener(ChannelStatusListener listener) { listeners.remove(listener); } @Override public void pingedChanged(Channel channel, boolean pinged) { for (ChannelStatusListener listener : listeners) listener.pingedChanged(channel, pinged); } @Override public void authenticatedChanged(Channel channel, boolean authenticated, ResponseMessage response) { for (ChannelStatusListener listener : listeners) listener.authenticatedChanged(channel, authenticated, response); if (response != null) { String oldSessionId = ServerSession.this.sessionId; ServerSession.this.sessionId = (String)response.getHeader(SESSION_ID_TAG); updateSessionId(oldSessionId); } } @Override public void credentialsCleared(Channel channel) { for (ChannelStatusListener listener : listeners) listener.credentialsCleared(channel); } @Override public void fault(Channel channel, final FaultMessage faultMessage) { context.callLater(new Runnable() { public void run() { new FaultHandler<Object>(ServerSession.this, null, "channel").handleFault(context, faultMessage, null); } }); } } private ChannelStatusBinder channelStatusBinder = new ChannelStatusBinder(); private ReauthenticateCallback reauthenticateCallback = new ReauthenticateCallback() { @Override public void reauthenticate() throws ChannelException { remotingChannel.reauthenticate(); } }; /** * Returns remote service for the internal destination * Should generally not be used except for very advanced use, use {@link Component} instead * @return internal remote service */ public RemoteService getRemoteService() { return getRemoteService(destination); } /** * Returns a remote service for the specified destination * Should generally not be used except for very advanced use, use {@link Component} instead * @param destination destination name * @return remote service */ public synchronized RemoteService getRemoteService(String destination) { if (remotingChannel == null) throw new IllegalStateException("Channel not defined for server session"); RemoteService remoteService = remoteServices.get(destination); if (remoteService == null) { remoteService = serviceFactory.newRemoteService(remotingChannel, destination); remoteServices.put(destination, remoteService); } return remoteService; } /** * Return a messaging channel for the specified type * @param channelType channel type * @return messaging channel * @see org.granite.client.messaging.channel.ChannelType */ public MessagingChannel getMessagingChannel(String channelType) { MessagingChannel messagingChannel = messagingChannelsByType.get(channelType); if (messagingChannel != null) return messagingChannel; messagingChannel = channelFactory.newMessagingChannel(channelType, channelType + "amf", serverApp); messagingChannel.setSessionId(sessionId); setupChannel(messagingChannel); messagingChannelsByType.put(channelType, messagingChannel); return messagingChannel; } /** * Build a consumer for the specified channel type and destination * @param destination destination name * @param topic subtopic * @param channelType channel type * @return consumer */ public synchronized Consumer getConsumer(String destination, String topic, String channelType) { if (channelType == null) channelType = channelFactory.getDefaultChannelType(); MessagingChannel messagingChannel = getMessagingChannel(channelType); if (messagingChannel == null) throw new IllegalStateException("Channel not defined in server session for type " + channelType + ""); String key = "C:" + destination + '@' + topic; Consumer consumer = (Consumer)topicAgents.get(key); if (consumer == null) { consumer = serviceFactory.newConsumer(messagingChannel, destination, topic); consumer.addSubscriptionListener(consumerSubscriptionListener); topicAgents.put(key, consumer); } return consumer; } /** * Build a consumer for the default channel type and destination * @param destination destination name * @param topic subtopic * @return consumer */ public synchronized Consumer getConsumer(String destination, String topic) { return getConsumer(destination, topic, channelFactory.getDefaultChannelType()); } /** * Detach a consumer from the session * @param consumer consumer * @throws IllegalArgumentException when the consumer has not been created from the session */ public synchronized void removeConsumer(Consumer consumer) { String key = "C:" + consumer.getDestination() + "@" + consumer.getTopic(); if (topicAgents.get(key) != consumer) throw new IllegalArgumentException("Consumer " + key + " not managed by session"); consumer.removeSubscriptionListener(consumerSubscriptionListener); topicAgents.remove(key); } private final TopicSubscriptionListener consumerSubscriptionListener = new TopicSubscriptionListener() { @Override public void onUnsubscribing(Consumer consumer) { checkWaitForLogout(); } @Override public void onUnsubscriptionSuccess(Consumer consumer, ResultEvent event, String subscriptionId) { tryLogout(); } @Override public void onUnsubscriptionFault(Consumer consumer, IssueEvent event, String subscriptionId) { tryLogout(); } @Override public void onSubscribing(Consumer consumer) { checkWaitForLogout(); } @Override public void onSubscriptionSuccess(Consumer consumer, ResultEvent event, String subscriptionId) { tryLogout(); } @Override public void onSubscriptionFault(Consumer consumer, IssueEvent event) { tryLogout(); } }; /** * Build a producer for the specified channel type and destination * @param destination destination name * @param topic subtopic * @param channelType channel type * @return producer */ public synchronized Producer getProducer(String destination, String topic, String channelType) { if (channelType == null) channelType = channelFactory.getDefaultChannelType(); MessagingChannel messagingChannel = getMessagingChannel(channelType); if (messagingChannel == null) throw new IllegalStateException("Channel not defined for server session"); String key = "P:" + destination + '@' + topic; Producer producer = (Producer)topicAgents.get(key); if (producer == null) { producer = serviceFactory.newProducer(messagingChannel, destination, topic); topicAgents.put(key, producer); } return producer; } /** * Build a producer for the default channel type and destination * @param destination destination name * @param topic subtopic * @return producer */ public synchronized Producer getProducer(String destination, String topic) { return getProducer(destination, topic, channelFactory.getDefaultChannelType()); } /** * Detach a producer from the session * @param producer producer * @throws IllegalArgumentException when the producer has not been created from the session */ public synchronized void removeProducer(Producer producer) { String key = "P:" + producer.getDestination() + "@" + producer.getTopic(); if (topicAgents.get(key) != producer) throw new IllegalArgumentException("Producer " + key + " not managed by session"); topicAgents.remove(key); } /** * Current remote session id * @return session id */ public String getSessionId() { return sessionId; } /** * Is logging out ? * @return true if logout in progress */ public boolean isLogoutInProgress() { return logoutState.logoutInProgress; } private ScheduledExecutorService sessionExpirationTimer = null; private ScheduledFuture<?> sessionExpirationFuture = null; private Runnable sessionExpirationTask = new Runnable() { @Override public void run() { Identity identity = context.byType(Identity.class); identity.checkLoggedIn(null); } }; private void rescheduleSessionExpirationTask(long serverTime, int sessionExpirationDelay) { Identity identity = context.byType(Identity.class); if (identity == null || !identity.isLoggedIn()) // No session expiration tracking if user not logged in return; long clientOffset = serverTime - new Date().getTime(); sessionExpirationFuture = sessionExpirationTimer.schedule(sessionExpirationTask, clientOffset + sessionExpirationDelay*1000L + 1500L, TimeUnit.MILLISECONDS); } /** * Callback called when a remoting response is received * @param event event */ public void onResultEvent(Event event) { if (sessionExpirationFuture != null) sessionExpirationFuture.cancel(false); String oldSessionId = sessionId; if (event instanceof ResultEvent) { ResultMessage message = ((ResultEvent)event).getMessage(); sessionId = (String)message.getHeader(SESSION_ID_TAG); if (sessionId != null) { long serverTime = (Long)message.getHeader(SERVER_TIME_TAG); int sessionExpirationDelay = (Integer)message.getHeader(SESSION_EXP_TAG); rescheduleSessionExpirationTask(serverTime, sessionExpirationDelay); } } else if (event instanceof IncomingMessageEvent<?>) sessionId = (String)((IncomingMessageEvent<?>)event).getMessage().getHeader(SESSION_ID_TAG); updateSessionId(oldSessionId); status.setConnected(true); } /** * Callback called when a remoting fault is received * @param event fault event * @param emsg fault message */ public void onFaultEvent(FaultEvent event, FaultMessage emsg) { if (sessionExpirationFuture != null) sessionExpirationFuture.cancel(false); String oldSessionId = sessionId; sessionId = (String)event.getMessage().getHeader(SESSION_ID_TAG); if (sessionId != null) { long serverTime = (Long)event.getMessage().getHeader(SERVER_TIME_TAG); int sessionExpirationDelay = (Integer)event.getMessage().getHeader(SESSION_EXP_TAG); rescheduleSessionExpirationTask(serverTime, sessionExpirationDelay); } updateSessionId(oldSessionId); if (emsg != null && emsg.getCode().equals(Code.SERVER_CALL_FAILED)) status.setConnected(false); } private void updateSessionId(String oldSessionId) { if ((sessionId == null && oldSessionId != null) || (sessionId != null && !sessionId.equals(oldSessionId))) log.debug("Received new sessionId %s (!= %s)", sessionId, oldSessionId); if (oldSessionId != null || sessionId != null) { for (MessagingChannel messagingChannel : messagingChannelsByType.values()) messagingChannel.setSessionId(sessionId); } } /** * Callback called when a remoting failure is received * @param event failure event */ public void onIssueEvent(IssueEvent event) { if (event.getType() != IssueEvent.Type.CANCELLED) status.setConnected(false); } private final TransportStatusHandler statusHandler = new TransportStatusHandler() { private int busyCount = 0; @Override public void handleIO(boolean active) { if (active) busyCount++; else busyCount--; context.callLater(setBusy); notifyIOListeners(status.isBusy()); } private final Runnable setBusy = new Runnable() { @Override public void run() { status.setBusy(busyCount > 0); } }; @Override public void handleException(TransportException e) { log.debug(e, "Transport failed"); notifyExceptionListeners(e); } }; /** * Current remoting transport * @return remoting transport */ public Transport getRemotingTransport() { return channelFactory != null ? channelFactory.getRemotingTransport() : remotingTransport; } /** * Current messaging transport * @return messaging transport */ public Transport getMessagingTransport() { return channelFactory != null ? channelFactory.getMessagingTransport() : messagingTransport; } /** * Current messaging transport * @return messaging transport */ public Transport getMessagingTransport(String channelType) { return channelFactory != null ? channelFactory.getMessagingTransport(channelType) : messagingTransports.get(channelType); } /** * Implementation of login * Should not be called directly, called by {@link org.granite.client.tide.Identity#login} * * @param username user name * @param password password */ public void login(String username, String password) { remotingChannel.setCredentials(new UsernamePasswordCredentials(username, password)); for (MessagingChannel messagingChannel : messagingChannelsByType.values()) messagingChannel.setCredentials(new UsernamePasswordCredentials(username, password)); } /** * Implementation of login using a specific charset for username/password encoding * Should not be called directly, called by {@link org.granite.client.tide.Identity#login} * * @param username user name * @param password password * @param charset charset used for encoding */ public void login(String username, String password, Charset charset) { remotingChannel.setCredentials(new UsernamePasswordCredentials(username, password, charset)); for (MessagingChannel messagingChannel : messagingChannelsByType.values()) messagingChannel.setCredentials(new UsernamePasswordCredentials(username, password, charset)); } /** * Called by {@link org.granite.client.tide.Identity} after login has succeeded */ public void afterLogin() { log.info("Application session authenticated"); context.getEventBus().raiseEvent(context, LOGIN); } /** * Called by {@link org.granite.client.tide.Identity} after session has expired */ public void sessionExpired() { log.info("Application session expired"); logoutState.sessionExpired(); context.getEventBus().raiseEvent(context, SESSION_EXPIRED); if (logoutOnSessionExpiration) { remotingChannel.clearCredentials(); context.getEventBus().raiseEvent(context, LOGOUT); remotingChannel.logout(false); for (MessagingChannel messagingChannel : messagingChannelsByType.values()) messagingChannel.logout(false); } sessionId = null; if (remotingChannel instanceof SessionAwareChannel) ((SessionAwareChannel)remotingChannel).setSessionId(null); for (MessagingChannel messagingChannel : messagingChannelsByType.values()) messagingChannel.setSessionId(null); } /** * Implementation of logout * Should not be called directly, called by {@link org.granite.client.tide.Identity#logout} * * @param logoutObserver observer that will be notified of logout result */ public void logout(final Observer logoutObserver) { if (sessionExpirationFuture != null) { sessionExpirationFuture.cancel(false); sessionExpirationFuture = null; } logoutState.logout(logoutObserver, new TimerTask() { @Override public void run() { log.info("Force session logout"); logoutState.logout(logoutObserver); tryLogout(); } }); context.getEventBus().raiseEvent(context, LOGOUT); tryLogout(); } /** * Notify the framework that it should wait for a async operation before effectively logging out. * Only if a logout has been requested. */ public void checkWaitForLogout() { logoutState.checkWait(); } /** * Called after all remote operations on a component are finished. * The actual logout is done when all remote operations on all components have been notified as finished. */ public void tryLogout() { if (logoutState.stillWaiting()) return; if (logoutState.isSessionExpired()) { // Don't remotely logout again if we detected a session expired logoutState.loggedOut(null); return; } if (remotingChannel != null) { remotingChannel.logout(new ResultFaultIssuesResponseListener() { @Override public void onResult(final ResultEvent event) { context.callLater(new Runnable() { public void run() { log.info("Application session logged out"); new ResultHandler<Object>(ServerSession.this, null, "logout").handleResult(context, null, null); context.getContextManager().destroyContexts(); logoutState.loggedOut(new TideResultEvent<Object>(context, ServerSession.this, null, event.getResult())); } }); } @Override public void onFault(final FaultEvent event) { context.callLater(new Runnable() { public void run() { log.error("Could not log out %s", event.getDescription()); new FaultHandler<Object>(ServerSession.this, null, "logout").handleFault(context, event.getMessage(), null); Fault fault = new Fault(event.getCode(), event.getDescription(), event.getDetails(), event.getUnknownCode()); fault.setContent(event.getMessage()); fault.setCause(event.getCause()); logoutState.loggedOut(new TideFaultEvent(context, ServerSession.this, null, fault, event.getExtended())); } }); } @Override public void onIssue(final IssueEvent event) { context.callLater(new Runnable() { public void run() { log.error("Could not logout %s", event.getType()); new FaultHandler<Object>(ServerSession.this, null, "logout").handleFault(context, null, null); Fault fault = new Fault(Code.SERVER_CALL_FAILED, event.getType().name(), "", null); logoutState.loggedOut(new TideFaultEvent(context, ServerSession.this, null, fault, null)); } }); } }); } for (MessagingChannel messagingChannel : messagingChannelsByType.values()) { if (messagingChannel != remotingChannel) messagingChannel.logout(); } } private static class LogoutState extends Observable { private long timeout = 1500; private boolean logoutInProgress = false; private int waitForLogout = 0; private boolean sessionExpired = false; private Timer logoutTimeout = null; public LogoutState(long timeout) { this.timeout = timeout; } public void setTimeout(long timeout) { this.timeout = timeout; } public synchronized void logout(Observer logoutObserver, TimerTask forceLogout) { logout(logoutObserver); logoutTimeout = new Timer(true); logoutTimeout.schedule(forceLogout, timeout); } public synchronized void logout(Observer logoutObserver) { if (logoutObserver != null) addObserver(logoutObserver); if (!logoutInProgress) { logoutInProgress = true; waitForLogout = 1; } } public synchronized void checkWait() { if (logoutInProgress) waitForLogout++; } public synchronized boolean stillWaiting() { if (sessionExpired) return false; if (!logoutInProgress) return true; waitForLogout--; if (waitForLogout > 0) return true; return false; } public boolean isSessionExpired() { return sessionExpired; } public synchronized void loggedOut(TideRpcEvent event) { if (logoutTimeout != null) { logoutTimeout.cancel(); logoutTimeout = null; } if (event != null) { setChanged(); notifyObservers(event); deleteObservers(); } logoutInProgress = false; waitForLogout = 0; sessionExpired = false; } public synchronized void sessionExpired() { logoutInProgress = false; waitForLogout = 0; sessionExpired = true; } } /** * Status notified of network related events */ public interface Status { /** * Network I/O busy * @return true is busy */ public boolean isBusy(); /** * Set I/O busy, called by transport listeners * @param busy true if busy */ public void setBusy(boolean busy); /** * Network connected * @return true if connected */ public boolean isConnected(); /** * Set connected state, called by transport listeners * @param connected true if connected */ public void setConnected(boolean connected); /** * Busy cursor enabled ? * @return true if busy cursor enabled */ public boolean isShowBusyCursor(); /** * Enable/disable busy cursor * @param showBusyCursor true if enabled */ public void setShowBusyCursor(boolean showBusyCursor); } public static class DefaultStatus implements Status { private boolean showBusyCursor = true; private boolean connected = false; private boolean busy = false; @Override public boolean isBusy() { return busy; } public void setBusy(boolean busy) { this.busy = busy; } @Override public boolean isConnected() { return connected; } public void setConnected(boolean connected) { this.connected = connected; } @Override public boolean isShowBusyCursor() { return showBusyCursor; } @Override public void setShowBusyCursor(boolean showBusyCursor) { this.showBusyCursor = showBusyCursor; } } private long defaultTimeToLive = -1; /** * Set default time to live on all channels * @param timeToLive time to live in milliseconds */ public void setDefaultTimeToLive(long timeToLive) { defaultTimeToLive = timeToLive; if (channelFactory != null) channelFactory.setDefaultTimeToLive(timeToLive); for (MessagingChannel messagingChannel : messagingChannelsByType.values()) messagingChannel.setDefaultTimeToLive(timeToLive); if (remotingChannel != null) remotingChannel.setDefaultTimeToLive(timeToLive); } private List<TransportIOListener> transportIOListeners = new ArrayList<TransportIOListener>(); private List<TransportExceptionListener> transportExceptionListeners = new ArrayList<TransportExceptionListener>(); public void addListener(TransportIOListener listener) { transportIOListeners.add(listener); } public void removeListener(TransportIOListener listener) { transportIOListeners.remove(listener); } public void addListener(TransportExceptionListener listener) { transportExceptionListeners.add(listener); } public void removeListener(TransportExceptionListener listener) { transportExceptionListeners.remove(listener); } public interface TransportIOListener { public void handleIO(boolean busy); } public interface TransportExceptionListener { public void handleException(TransportException e); } public void notifyIOListeners(boolean busy) { for (TransportIOListener listener : transportIOListeners) listener.handleIO(busy); } public void notifyExceptionListeners(TransportException e) { for (TransportExceptionListener listener : transportExceptionListeners) listener.handleException(e); } }