001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022package org.granite.gravity;
023
024import java.io.Serializable;
025import java.util.Date;
026import java.util.HashMap;
027import java.util.Iterator;
028import java.util.Map;
029import java.util.Timer;
030import java.util.TimerTask;
031import java.util.concurrent.ConcurrentHashMap;
032
033import javax.management.ObjectName;
034
035import org.granite.clustering.DistributedData;
036import org.granite.config.GraniteConfig;
037import org.granite.config.flex.Destination;
038import org.granite.config.flex.ServicesConfig;
039import org.granite.context.GraniteContext;
040import org.granite.context.SimpleGraniteContext;
041import org.granite.gravity.adapters.AdapterFactory;
042import org.granite.gravity.adapters.ServiceAdapter;
043import org.granite.gravity.security.GravityDestinationSecurizer;
044import org.granite.gravity.security.GravityInvocationContext;
045import org.granite.gravity.udp.UdpReceiverFactory;
046import org.granite.jmx.MBeanServerLocator;
047import org.granite.jmx.OpenMBean;
048import org.granite.logging.Logger;
049import org.granite.messaging.amf.process.AMF3MessageInterceptor;
050import org.granite.messaging.jmf.SharedContext;
051import org.granite.messaging.service.security.SecurityService;
052import org.granite.messaging.service.security.SecurityServiceException;
053import org.granite.messaging.webapp.ServletGraniteContext;
054import org.granite.scan.ServiceLoader;
055import org.granite.util.TypeUtil;
056import org.granite.util.UUIDUtil;
057
058import flex.messaging.messages.AcknowledgeMessage;
059import flex.messaging.messages.AsyncMessage;
060import flex.messaging.messages.CommandMessage;
061import flex.messaging.messages.ErrorMessage;
062import flex.messaging.messages.Message;
063
064/**
065 * @author William DRAI
066 * @author Franck WOLFF
067 */
068public class DefaultGravity implements Gravity, DefaultGravityMBean {
069
070    ///////////////////////////////////////////////////////////////////////////
071    // Fields.
072
073    private static final Logger log = Logger.getLogger(Gravity.class);
074
075    private final Map<String, Object> applicationMap = new HashMap<String, Object>();
076    private final ConcurrentHashMap<String, TimeChannel<?>> channels = new ConcurrentHashMap<String, TimeChannel<?>>();
077    
078    private GravityConfig gravityConfig = null;
079    private ServicesConfig servicesConfig = null;
080    private GraniteConfig graniteConfig = null;
081    private SharedContext sharedContext = null;
082
083    private Channel serverChannel = null;
084    private AdapterFactory adapterFactory = null;
085    private GravityPool gravityPool = null;
086    
087    private UdpReceiverFactory udpReceiverFactory = null;
088
089    private Timer channelsTimer;
090    private boolean started;
091
092    ///////////////////////////////////////////////////////////////////////////
093    // Constructor.
094
095    public DefaultGravity(GravityConfig gravityConfig, ServicesConfig servicesConfig, GraniteConfig graniteConfig, SharedContext sharedContext) {
096        if (gravityConfig == null || servicesConfig == null || graniteConfig == null)
097            throw new NullPointerException("All arguments must be non null.");
098
099        this.gravityConfig = gravityConfig;
100        this.servicesConfig = servicesConfig;
101        this.graniteConfig = graniteConfig;
102        this.sharedContext = sharedContext;
103    }
104
105    ///////////////////////////////////////////////////////////////////////////
106    // Properties.
107
108    public GravityConfig getGravityConfig() {
109                return gravityConfig;
110        }
111
112        public ServicesConfig getServicesConfig() {
113        return servicesConfig;
114    }
115
116        public GraniteConfig getGraniteConfig() {
117        return graniteConfig;
118    }
119
120    public SharedContext getSharedContext() {
121        return sharedContext;
122    }
123
124        public boolean isStarted() {
125        return started;
126    }
127        
128        public ServiceAdapter getServiceAdapter(String messageType, String destinationId) {
129                return adapterFactory.getServiceAdapter(messageType, destinationId);
130        }
131
132    ///////////////////////////////////////////////////////////////////////////
133    // Starting/stopping.
134
135    public void start() throws Exception {
136        log.info("Starting Gravity...");
137        synchronized (this) {
138                if (!started) {
139                    adapterFactory = new AdapterFactory(this);
140                    internalStart();
141                    serverChannel = new ServerChannel(this, ServerChannel.class.getName(), null, null);
142                    
143                    if (gravityConfig.isUseUdp()) {
144                        ServiceLoader<UdpReceiverFactory> loader = ServiceLoader.load(UdpReceiverFactory.class);
145                        Iterator<UdpReceiverFactory> factories = loader.iterator();
146                        if (factories.hasNext()) {
147                                udpReceiverFactory = factories.next();
148                                udpReceiverFactory.setPort(gravityConfig.getUdpPort());
149                                udpReceiverFactory.setNio(gravityConfig.isUdpNio());
150                                udpReceiverFactory.setConnected(gravityConfig.isUdpConnected());
151                                udpReceiverFactory.setSendBufferSize(gravityConfig.getUdpSendBufferSize());
152                                udpReceiverFactory.start();
153                        }
154                        else
155                                log.warn("UDP receiver factory not found");
156                    }
157                    
158                    started = true;
159                }
160        }
161        log.info("Gravity successfully started.");
162    }
163    
164    protected void internalStart() {
165        gravityPool = new GravityPool(gravityConfig);
166        channelsTimer = new Timer();
167        
168        if (graniteConfig.isRegisterMBeans()) {
169                try {
170                    ObjectName name = new ObjectName("org.graniteds:type=Gravity,context=" + graniteConfig.getMBeanContextName());
171                        log.info("Registering MBean: %s", name);
172                    OpenMBean mBean = OpenMBean.createMBean(this);
173                        MBeanServerLocator.getInstance().register(mBean, name, true);
174                }
175                catch (Exception e) {
176                        log.error(e, "Could not register Gravity MBean for context: %s", graniteConfig.getMBeanContextName());
177                }
178        }
179    }
180    
181    public void restart() throws Exception {
182        synchronized (this) {
183                stop();
184                start();
185        }
186        }
187
188        public void reconfigure(GravityConfig gravityConfig, GraniteConfig graniteConfig) {
189        this.gravityConfig = gravityConfig;
190        this.graniteConfig = graniteConfig;
191        if (gravityPool != null)
192                gravityPool.reconfigure(gravityConfig);
193    }
194
195    public void stop() throws Exception {
196        stop(true);
197    }
198
199    public void stop(boolean now) throws Exception {
200        log.info("Stopping Gravity (now=%s)...", now);
201        synchronized (this) {
202                if (adapterFactory != null) {
203                    try {
204                                        adapterFactory.stopAll();
205                                } catch (Exception e) {
206                                log.error(e, "Error while stopping adapter factory");
207                                }
208                    adapterFactory = null;
209                }
210
211                if (serverChannel != null) {
212                    try {
213                                        removeChannel(serverChannel.getId(), false);
214                                } catch (Exception e) {
215                                log.error(e, "Error while removing server channel: %s", serverChannel);
216                                }
217                    serverChannel = null;
218                }
219            
220            if (channelsTimer != null) {
221                    try {
222                                        channelsTimer.cancel();
223                                } catch (Exception e) {
224                                log.error(e, "Error while cancelling channels timer");
225                                }
226                    channelsTimer = null;
227            }
228                
229                if (gravityPool != null) {
230                        try {
231                                if (now)
232                                        gravityPool.shutdownNow();
233                                else
234                                        gravityPool.shutdown();
235                        }
236                        catch (Exception e) {
237                                log.error(e, "Error while stopping thread pool");
238                        }
239                        gravityPool = null;
240                }
241                
242                if (udpReceiverFactory != null) {
243                        try {
244                                udpReceiverFactory.stop();
245                        }
246                        catch (Exception e) {
247                                log.error(e, "Error while stopping udp receiver factory");
248                        }
249                        udpReceiverFactory = null;
250                }
251            
252            started = false;
253        }
254        log.info("Gravity sucessfully stopped.");
255    }
256
257    ///////////////////////////////////////////////////////////////////////////
258    // GravityMBean attributes implementation.
259
260        public String getGravityFactoryName() {
261                return gravityConfig.getGravityFactory();
262        }
263
264        public long getChannelIdleTimeoutMillis() {
265                return gravityConfig.getChannelIdleTimeoutMillis();
266        }
267        public void setChannelIdleTimeoutMillis(long channelIdleTimeoutMillis) {
268                gravityConfig.setChannelIdleTimeoutMillis(channelIdleTimeoutMillis);
269        }
270
271        public boolean isRetryOnError() {
272                return gravityConfig.isRetryOnError();
273        }
274        public void setRetryOnError(boolean retryOnError) {
275                gravityConfig.setRetryOnError(retryOnError);
276        }
277
278        public long getLongPollingTimeoutMillis() {
279                return gravityConfig.getLongPollingTimeoutMillis();
280        }
281        public void setLongPollingTimeoutMillis(long longPollingTimeoutMillis) {
282                gravityConfig.setLongPollingTimeoutMillis(longPollingTimeoutMillis);
283        }
284
285        public int getMaxMessagesQueuedPerChannel() {
286                return gravityConfig.getMaxMessagesQueuedPerChannel();
287        }
288        public void setMaxMessagesQueuedPerChannel(int maxMessagesQueuedPerChannel) {
289                gravityConfig.setMaxMessagesQueuedPerChannel(maxMessagesQueuedPerChannel);
290        }
291
292        public long getReconnectIntervalMillis() {
293                return gravityConfig.getReconnectIntervalMillis();
294        }
295
296        public int getReconnectMaxAttempts() {
297                return gravityConfig.getReconnectMaxAttempts();
298        }
299
300    public int getCorePoolSize() {
301        if (gravityPool != null)
302                return gravityPool.getCorePoolSize();
303        return gravityConfig.getCorePoolSize();
304        }
305
306        public void setCorePoolSize(int corePoolSize) {
307                gravityConfig.setCorePoolSize(corePoolSize);
308                if (gravityPool != null)
309                gravityPool.setCorePoolSize(corePoolSize);
310        }
311
312        public long getKeepAliveTimeMillis() {
313        if (gravityPool != null)
314                return gravityPool.getKeepAliveTimeMillis();
315        return gravityConfig.getKeepAliveTimeMillis();
316        }
317        public void setKeepAliveTimeMillis(long keepAliveTimeMillis) {
318                gravityConfig.setKeepAliveTimeMillis(keepAliveTimeMillis);
319                if (gravityPool != null)
320                gravityPool.setKeepAliveTimeMillis(keepAliveTimeMillis);
321        }
322
323        public int getMaximumPoolSize() {
324        if (gravityPool != null)
325                return gravityPool.getMaximumPoolSize();
326        return gravityConfig.getMaximumPoolSize();
327        }
328        public void setMaximumPoolSize(int maximumPoolSize) {
329                gravityConfig.setMaximumPoolSize(maximumPoolSize);
330                if (gravityPool != null)
331                gravityPool.setMaximumPoolSize(maximumPoolSize);
332        }
333
334        public int getQueueCapacity() {
335        if (gravityPool != null)
336                return gravityPool.getQueueCapacity();
337        return gravityConfig.getQueueCapacity();
338        }
339
340        public int getQueueRemainingCapacity() {
341        if (gravityPool != null)
342                return gravityPool.getQueueRemainingCapacity();
343        return gravityConfig.getQueueCapacity();
344        }
345
346        public int getQueueSize() {
347        if (gravityPool != null)
348                return gravityPool.getQueueSize();
349        return 0;
350        }
351        
352        public boolean hasUdpReceiverFactory() {
353                return udpReceiverFactory != null;
354        }
355
356    public UdpReceiverFactory getUdpReceiverFactory() {
357                return udpReceiverFactory;
358        }
359
360    ///////////////////////////////////////////////////////////////////////////
361    // Channel's operations.
362
363        protected <C extends Channel> C createChannel(ChannelFactory<C> channelFactory, String clientId) {
364        C channel = null;
365        if (clientId != null) {
366                channel = getChannel(channelFactory, clientId);
367                if (channel != null)
368                        return channel;
369        }
370        
371        String clientType = GraniteContext.getCurrentInstance().getClientType();
372        channel = channelFactory.newChannel(UUIDUtil.randomUUID(), clientType);
373        TimeChannel<C> timeChannel = new TimeChannel<C>(channel);
374        for (int i = 0; channels.putIfAbsent(channel.getId(), timeChannel) != null; i++) {
375            if (i >= 10)
376                throw new RuntimeException("Could not find random new clientId after 10 iterations");
377            channel.destroy(false);
378            channel = channelFactory.newChannel(UUIDUtil.randomUUID(), clientType);
379            timeChannel = new TimeChannel<C>(channel);
380        }
381
382        String channelId = channel.getId();
383        
384        // Save channel id in distributed data (clustering).
385        try {
386                DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
387                if (gdd != null) {
388                        log.debug("Saving channel id in distributed data: %s", channelId);
389                        gdd.addChannelId(channelId, channelFactory.getClass().getName(), clientType);
390                }
391        }
392        catch (Exception e) {
393                        log.error(e, "Could not add channel id in distributed data: %s", channelId);
394        }
395        
396        // Initialize timer task.
397        access(channelId);
398        
399        return channel;
400    }
401
402    @SuppressWarnings("unchecked")
403        public <C extends Channel> C getChannel(ChannelFactory<C> channelFactory, String clientId) {
404        if (clientId == null)
405            return null;
406
407                TimeChannel<C> timeChannel = (TimeChannel<C>)channels.get(clientId);
408        if (timeChannel == null) {
409                // Look for existing channel id/subscriptions in distributed data (clustering).
410            log.debug("Lookup channel %s in distributed data", clientId);
411            try {
412                        DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
413                        if (gdd != null && gdd.hasChannelId(clientId)) {
414                                log.debug("Found channel id in distributed data: %s", clientId);
415                                String channelFactoryClassName = gdd.getChannelFactoryClassName(clientId);
416                    String clientType = gdd.getChannelClientType(clientId);
417                                channelFactory = (ChannelFactory<C>)TypeUtil.newInstance(channelFactoryClassName, new Class<?>[] { Gravity.class }, new Object[] { this });
418                                C channel = channelFactory.newChannel(clientId, clientType);
419                        timeChannel = new TimeChannel<C>(channel);
420                        if (channels.putIfAbsent(clientId, timeChannel) == null) {
421                                for (CommandMessage subscription : gdd.getSubscriptions(clientId)) {
422                                        log.debug("Resubscribing channel: %s - %s", clientId, subscription);
423                                        handleSubscribeMessage(channelFactory, subscription, false);
424                                }
425                                access(clientId);
426                        }
427                        }
428                }
429                catch (Exception e) {
430                        log.error(e, "Could not recreate channel/subscriptions from distributed data: %s", clientId);
431                }
432        }
433
434        return (timeChannel != null ? timeChannel.getChannel() : null);
435    }
436
437    public Channel removeChannel(String channelId, boolean timeout) {
438        if (channelId == null)
439            return null;
440
441        // Remove existing channel id/subscriptions in distributed data (clustering).
442        try {
443                DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
444                if (gdd != null) {
445                        log.debug("Removing channel id from distributed data: %s", channelId);
446                        gdd.removeChannelId(channelId);
447                }
448                }
449                catch (Exception e) {
450                        log.error(e, "Could not remove channel id from distributed data: %s", channelId);
451                }
452        
453        TimeChannel<?> timeChannel = channels.get(channelId);
454        Channel channel = null;
455        if (timeChannel != null) {
456                try {
457                        if (timeChannel.getTimerTask() != null)
458                                timeChannel.getTimerTask().cancel();
459                }
460                catch (Exception e) {
461                        // Should never happen...
462                }
463                
464                channel = timeChannel.getChannel();
465                
466                try {
467                    for (Subscription subscription : channel.getSubscriptions()) {
468                        try {
469                                Message message = subscription.getUnsubscribeMessage();
470                                handleMessage(channel.getFactory(), message, true);
471                        }
472                        catch (Exception e) {
473                                log.error(e, "Error while unsubscribing channel: %s from subscription: %s", channel, subscription);
474                        }
475                    }
476                }
477                finally {
478                        channels.remove(channelId);
479                        channel.destroy(timeout);
480                }
481        }
482        return channel;
483    }
484    
485    public boolean access(String channelId) {
486        if (channelId != null) {
487                TimeChannel<?> timeChannel = channels.get(channelId);
488                if (timeChannel != null) {
489                        synchronized (timeChannel) {
490                                TimerTask timerTask = timeChannel.getTimerTask();
491                                if (timerTask != null) {
492                                    log.debug("Canceling TimerTask: %s", timerTask);
493                                    timerTask.cancel();
494                                        timeChannel.setTimerTask(null);
495                                }
496                            
497                                timerTask = new ChannelTimerTask(this, channelId);
498                            timeChannel.setTimerTask(timerTask);
499                            
500                            long timeout = gravityConfig.getChannelIdleTimeoutMillis();
501                            log.debug("Scheduling TimerTask: %s for %s ms.", timerTask, timeout);
502                            channelsTimer.schedule(timerTask, timeout);
503                            return true;
504                        }
505                }
506        }
507        return false;
508    }
509    
510    public void execute(AsyncChannelRunner runner) {
511        if (gravityPool == null) {
512                runner.reset();
513                throw new NullPointerException("Gravity not started or pool disabled");
514        }
515        gravityPool.execute(runner);
516    }
517    
518    public boolean cancel(AsyncChannelRunner runner) {
519        if (gravityPool == null) {
520                runner.reset();
521                throw new NullPointerException("Gravity not started or pool disabled");
522        }
523        return gravityPool.remove(runner);
524    }
525
526    ///////////////////////////////////////////////////////////////////////////
527    // Incoming message handling.
528
529    public Message handleMessage(final ChannelFactory<?> channelFactory, Message message) {
530        return handleMessage(channelFactory, message, false);
531    }
532
533    public Message handleMessage(final ChannelFactory<?> channelFactory, final Message message, boolean skipInterceptor) {
534
535        AMF3MessageInterceptor interceptor = null;
536        if (!skipInterceptor)
537                interceptor = GraniteContext.getCurrentInstance().getGraniteConfig().getAmf3MessageInterceptor();
538        
539        Message reply = null;
540        boolean publish = false;
541        
542        try {
543                if (interceptor != null)
544                    interceptor.before(message);
545
546                if (message instanceof CommandMessage) {
547                    CommandMessage command = (CommandMessage)message;
548        
549                    switch (command.getOperation()) {
550        
551                    case CommandMessage.LOGIN_OPERATION:
552                    case CommandMessage.LOGOUT_OPERATION:
553                        return handleSecurityMessage(command);
554        
555                    case CommandMessage.CLIENT_PING_OPERATION:
556                        return handlePingMessage(channelFactory, command);
557                    case CommandMessage.CONNECT_OPERATION:
558                        return handleConnectMessage(channelFactory, command);
559                    case CommandMessage.DISCONNECT_OPERATION:
560                        return handleDisconnectMessage(channelFactory, command);
561                    case CommandMessage.SUBSCRIBE_OPERATION:
562                        return handleSubscribeMessage(channelFactory, command);
563                    case CommandMessage.UNSUBSCRIBE_OPERATION:
564                        return handleUnsubscribeMessage(channelFactory, command);
565        
566                    default:
567                        throw new UnsupportedOperationException("Unsupported command operation: " + command);
568                    }
569                }
570        
571                reply = handlePublishMessage(channelFactory, (AsyncMessage)message);
572                publish = true;
573        }
574        finally {
575                if (interceptor != null)
576                    interceptor.after(message, reply);
577        }
578        
579        if (reply != null) {
580                GraniteContext context = GraniteContext.getCurrentInstance();
581                if (context.getSessionId() != null) {
582                        reply.setHeader("org.granite.sessionId", context.getSessionId());
583                    if (publish && context instanceof ServletGraniteContext && ((ServletGraniteContext)context).getSession(false) != null) {
584                        long serverTime = new Date().getTime();
585                        ((ServletGraniteContext)context).getSession().setAttribute(GraniteContext.SESSION_LAST_ACCESSED_TIME_KEY, serverTime);
586                        reply.setHeader("org.granite.time", serverTime);
587                        reply.setHeader("org.granite.sessionExp", ((ServletGraniteContext)context).getSession().getMaxInactiveInterval());
588                    }
589                }
590        }
591        
592        return reply;
593    }
594
595    ///////////////////////////////////////////////////////////////////////////
596    // Other Public API methods.
597
598    public GraniteContext initThread(String sessionId, String clientType) {
599        GraniteContext context = GraniteContext.getCurrentInstance();
600        if (context == null)
601            context = SimpleGraniteContext.createThreadInstance(graniteConfig, servicesConfig, sessionId, applicationMap, clientType);
602        return context;
603    }
604    
605    public void releaseThread() {
606        GraniteContext.release();
607        }
608
609        public Message publishMessage(AsyncMessage message) {
610        return publishMessage(serverChannel, message);
611    }
612
613    public Message publishMessage(Channel fromChannel, AsyncMessage message) {
614        initThread(null, fromChannel != null ? fromChannel.getClientType() : serverChannel.getClientType());
615
616        return handlePublishMessage(null, message, fromChannel != null ? fromChannel : serverChannel);
617    }
618
619    private Message handlePingMessage(ChannelFactory<?> channelFactory, CommandMessage message) {
620        
621        Channel channel = createChannel(channelFactory, (String)message.getClientId());
622        
623        AsyncMessage reply = new AcknowledgeMessage(message);
624        reply.setClientId(channel.getId());
625        Map<String, Object> advice = new HashMap<String, Object>();
626        advice.put(RECONNECT_INTERVAL_MS_KEY, Long.valueOf(gravityConfig.getReconnectIntervalMillis()));
627        advice.put(RECONNECT_MAX_ATTEMPTS_KEY, Long.valueOf(gravityConfig.getReconnectMaxAttempts()));
628        advice.put(ENCODE_MESSAGE_BODY_KEY, Boolean.valueOf(gravityConfig.isEncodeMessageBody()));
629        reply.setBody(advice);
630        reply.setDestination(message.getDestination());
631
632        log.debug("handshake.handle: reply=%s", reply);
633
634        return reply;
635    }
636
637    private Message handleSecurityMessage(CommandMessage message) {
638        GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
639
640        Message response = null;
641
642        if (!config.hasSecurityService())
643            log.warn("Ignored security operation (no security settings in granite-config.xml): %s", message);
644        else if (!config.getSecurityService().acceptsContext())
645            log.info("Ignored security operation (security service does not handle this kind of granite context)", message);
646        else {
647            SecurityService securityService = config.getSecurityService();
648            try {
649                if (message.isLoginOperation())
650                    securityService.login(message.getBody(), (String)message.getHeader(Message.CREDENTIALS_CHARSET_HEADER));
651                else
652                    securityService.logout();
653            }
654            catch (Exception e) {
655                if (e instanceof SecurityServiceException)
656                    log.debug(e, "Could not process security operation: %s", message);
657                else
658                    log.error(e, "Could not process security operation: %s", message);
659                response = new ErrorMessage(message, e, true);
660            }
661        }
662
663        if (response == null) {
664            response = new AcknowledgeMessage(message, true);
665            // For SDK 2.0.1_Hotfix2.
666            if (message.isSecurityOperation())
667                response.setBody("success");
668        }
669
670        return response;
671    }
672
673    private Message handleConnectMessage(final ChannelFactory<?> channelFactory, CommandMessage message) {
674        Channel client = getChannel(channelFactory, (String)message.getClientId());
675
676        if (client == null)
677            return handleUnknownClientMessage(message);
678
679        return null;
680    }
681
682    private Message handleDisconnectMessage(final ChannelFactory<?> channelFactory, CommandMessage message) {
683        Channel client = getChannel(channelFactory, (String)message.getClientId());
684        if (client == null)
685            return handleUnknownClientMessage(message);
686
687        removeChannel(client.getId(), false);
688
689        AcknowledgeMessage reply = new AcknowledgeMessage(message);
690        reply.setDestination(message.getDestination());
691        reply.setClientId(client.getId());
692        return reply;
693    }
694
695    private Message handleSubscribeMessage(final ChannelFactory<?> channelFactory, final CommandMessage message) {
696        return handleSubscribeMessage(channelFactory, message, true);
697    }
698    
699    private Message handleSubscribeMessage(final ChannelFactory<?> channelFactory, final CommandMessage message, final boolean saveMessageInSession) {
700
701        final GraniteContext context = GraniteContext.getCurrentInstance();
702
703        // Get and check destination.
704        final Destination destination = context.getServicesConfig().findDestinationById(
705            message.getMessageRefType(),
706            message.getDestination()
707        );
708
709        if (destination == null)
710            return getInvalidDestinationError(message);
711
712
713        GravityInvocationContext invocationContext = new GravityInvocationContext(message, destination) {
714                        @Override
715                        public Object invoke() throws Exception {
716                        // Subscribe...
717                        Channel channel = getChannel(channelFactory, (String)message.getClientId());
718                        if (channel == null)
719                            return handleUnknownClientMessage(message);
720
721                        String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
722                        if (subscriptionId == null) {
723                            subscriptionId = UUIDUtil.randomUUID();
724                            message.setHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER, subscriptionId);
725                        }
726                        
727                        DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
728                        if (gdd != null) {
729                            if (!gdd.hasChannelId(channel.getId())) {
730                                gdd.addChannelId(channel.getId(), channel.getFactory().getClass().getName(), context.getClientType());
731                        log.debug("Stored channel %s in distributed data", channel.getId());
732                    }
733                            
734                                if (Boolean.TRUE.toString().equals(destination.getProperties().get("session-selector"))) {
735                                        String selector = gdd.getDestinationSelector(destination.getId());
736                                        log.debug("Session selector found: %s", selector);
737                                        if (selector != null)
738                                                message.setHeader(CommandMessage.SELECTOR_HEADER, selector);
739                                }
740                        }
741
742                        ServiceAdapter adapter = adapterFactory.getServiceAdapter(message);
743                        
744                        AsyncMessage reply = (AsyncMessage)adapter.manage(channel, message);
745                        
746                        postManage(channel);
747                        
748                        if (saveMessageInSession && !(reply instanceof ErrorMessage)) {
749                                // Save subscription message in distributed data (clustering).
750                                try {
751                                        if (gdd != null) {
752                                                log.debug("Saving new subscription message for channel: %s - %s", channel.getId(), message);
753                                                gdd.addSubcription(channel.getId(), message);
754                                        }
755                                }
756                                catch (Exception e) {
757                                        log.error(e, "Could not add subscription in distributed data: %s - %s", channel.getId(), subscriptionId);
758                                }
759                        }
760
761                        reply.setDestination(message.getDestination());
762                        reply.setClientId(channel.getId());
763                        reply.getHeaders().putAll(message.getHeaders());
764
765                        if (gdd != null && message.getDestination() != null) {
766                                gdd.setDestinationClientId(message.getDestination(), channel.getId());
767                                gdd.setDestinationSubscriptionId(message.getDestination(), subscriptionId);
768                        }
769
770                        return reply;
771                        }               
772        };
773
774        // Check security 1 (destination).
775        if (destination.getSecurizer() instanceof GravityDestinationSecurizer) {
776            try {
777                ((GravityDestinationSecurizer)destination.getSecurizer()).canSubscribe(invocationContext);
778            } 
779            catch (Exception e) {
780                return new ErrorMessage(message, e);
781            }
782        }
783        
784        // Check security 2 (security service).
785        GraniteConfig config = context.getGraniteConfig();
786        try {
787            if (config.hasSecurityService() && config.getSecurityService().acceptsContext())
788                return (Message)config.getSecurityService().authorize(invocationContext);
789
790            return (Message)invocationContext.invoke();
791        }
792        catch (Exception e) {
793            return new ErrorMessage(message, e, true);
794        }
795    }
796
797    private Message handleUnsubscribeMessage(final ChannelFactory<?> channelFactory, CommandMessage message) {
798        Channel channel = getChannel(channelFactory, (String)message.getClientId());
799        if (channel == null)
800            return handleUnknownClientMessage(message);
801
802        AsyncMessage reply = null;
803
804        ServiceAdapter adapter = adapterFactory.getServiceAdapter(message);
805        
806        reply = (AcknowledgeMessage)adapter.manage(channel, message);
807        
808        postManage(channel);
809        
810        if (!(reply instanceof ErrorMessage)) {
811                // Remove subscription message in distributed data (clustering).
812                try {
813                        DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
814                        if (gdd != null) {
815                                String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
816                                log.debug("Removing subscription message from channel info: %s - %s", channel.getId(), subscriptionId);
817                                gdd.removeSubcription(channel.getId(), subscriptionId);
818                        }
819                }
820                catch (Exception e) {
821                        log.error(
822                                e, "Could not remove subscription from distributed data: %s - %s",
823                                channel.getId(), message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER)
824                        );
825                }
826        }
827
828        reply.setDestination(message.getDestination());
829        reply.setClientId(channel.getId());
830        reply.getHeaders().putAll(message.getHeaders());
831
832        return reply;
833    }
834    
835    protected void postManage(Channel channel) {
836    }
837
838    private Message handlePublishMessage(final ChannelFactory<?> channelFactory, final AsyncMessage message) {
839        return handlePublishMessage(channelFactory, message, null);
840    }
841    
842    private Message handlePublishMessage(final ChannelFactory<?> channelFactory, final AsyncMessage message, final Channel channel) {
843
844        GraniteContext context = GraniteContext.getCurrentInstance();
845
846        // Get and check destination.
847        Destination destination = context.getServicesConfig().findDestinationById(
848            message.getClass().getName(),
849            message.getDestination()
850        );
851
852        if (destination == null)
853            return getInvalidDestinationError(message);
854                
855        if (message.getMessageId() == null)
856                message.setMessageId(UUIDUtil.randomUUID());
857        message.setTimestamp(System.currentTimeMillis());
858        if (channel != null)
859                message.setClientId(channel.getId());
860
861        GravityInvocationContext invocationContext = new GravityInvocationContext(message, destination) {
862                        @Override
863                        public Object invoke() throws Exception {
864                        // Publish...
865                        Channel fromChannel = channel;
866                        if (fromChannel == null)
867                                fromChannel = getChannel(channelFactory, (String)message.getClientId());
868                        if (fromChannel == null)
869                            return handleUnknownClientMessage(message);
870
871                        ServiceAdapter adapter = adapterFactory.getServiceAdapter(message);
872                        
873                        AsyncMessage reply = (AsyncMessage)adapter.invoke(fromChannel, message);
874
875                        reply.setDestination(message.getDestination());
876                        reply.setClientId(fromChannel.getId());
877
878                        return reply;
879                        }               
880        };
881
882        // Check security 1 (destination).
883        if (destination.getSecurizer() instanceof GravityDestinationSecurizer) {
884            try {
885                ((GravityDestinationSecurizer)destination.getSecurizer()).canPublish(invocationContext);
886            } 
887            catch (Exception e) {
888                return new ErrorMessage(message, e, true);
889            }
890        }
891        
892        // Check security 2 (security service).
893        GraniteConfig config = context.getGraniteConfig();
894        try {
895                if (config.hasSecurityService() && config.getSecurityService().acceptsContext())
896                return (Message)config.getSecurityService().authorize(invocationContext);
897
898                return (Message)invocationContext.invoke();
899        } 
900        catch (Exception e) {
901            return new ErrorMessage(message, e, true);
902        }
903    }
904
905    private Message handleUnknownClientMessage(Message message) {
906        ErrorMessage reply = new ErrorMessage(message, true);
907        reply.setFaultCode("Server.Call.UnknownClient");
908        reply.setFaultString("Unknown client");
909        return reply;
910    }
911
912    ///////////////////////////////////////////////////////////////////////////
913    // Utilities.
914
915    private ErrorMessage getInvalidDestinationError(Message message) {
916
917        String messageType = message.getClass().getName();
918        if (message instanceof CommandMessage)
919            messageType += '[' + ((CommandMessage)message).getMessageRefType() + ']';
920
921        ErrorMessage reply = new ErrorMessage(message, true);
922        reply.setFaultCode("Server.Messaging.InvalidDestination");
923        reply.setFaultString(
924            "No configured destination for id: " + message.getDestination() +
925            " and message type: " + messageType
926        );
927        return reply;
928    }
929
930    private static class ServerChannel extends AbstractChannel implements Serializable {
931
932                private static final long serialVersionUID = 1L;
933                
934                public ServerChannel(Gravity gravity, String channelId, ChannelFactory<ServerChannel> factory, String clientType) {
935                super(gravity, channelId, factory, clientType);
936        }
937
938                @Override
939                public Gravity getGravity() {
940                        return gravity;
941                }
942                
943                public void close() {
944                }               
945
946                @Override
947                public void receive(AsyncMessage message) throws MessageReceivingException {
948                }
949
950                @Override
951                protected boolean hasAsyncHttpContext() {
952                        return false;
953                }
954
955                @Override
956                protected AsyncHttpContext acquireAsyncHttpContext() {
957                        return null;
958                }
959
960                @Override
961                protected void releaseAsyncHttpContext(AsyncHttpContext context) {
962                }
963    }
964}