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                try {
411                        DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
412                        if (gdd != null && gdd.hasChannelId(clientId)) {
413                                log.debug("Found channel id in distributed data: %s", clientId);
414                                String channelFactoryClassName = gdd.getChannelFactoryClassName(clientId);
415                    String clientType = gdd.getChannelClientType(clientId);
416                                channelFactory = (ChannelFactory<C>)TypeUtil.newInstance(channelFactoryClassName, new Class<?>[] { Gravity.class }, new Object[] { this });
417                                C channel = channelFactory.newChannel(clientId, clientType);
418                        timeChannel = new TimeChannel<C>(channel);
419                        if (channels.putIfAbsent(clientId, timeChannel) == null) {
420                                for (CommandMessage subscription : gdd.getSubscriptions(clientId)) {
421                                        log.debug("Resubscribing channel: %s - %s", clientId, subscription);
422                                        handleSubscribeMessage(channelFactory, subscription, false);
423                                }
424                                access(clientId);
425                        }
426                        }
427                }
428                catch (Exception e) {
429                        log.error(e, "Could not recreate channel/subscriptions from distributed data: %s", clientId);
430                }
431        }
432
433        return (timeChannel != null ? timeChannel.getChannel() : null);
434    }
435
436    public Channel removeChannel(String channelId, boolean timeout) {
437        if (channelId == null)
438            return null;
439
440        // Remove existing channel id/subscriptions in distributed data (clustering).
441        try {
442                DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
443                if (gdd != null) {
444                        log.debug("Removing channel id from distributed data: %s", channelId);
445                        gdd.removeChannelId(channelId);
446                }
447                }
448                catch (Exception e) {
449                        log.error(e, "Could not remove channel id from distributed data: %s", channelId);
450                }
451        
452        TimeChannel<?> timeChannel = channels.get(channelId);
453        Channel channel = null;
454        if (timeChannel != null) {
455                try {
456                        if (timeChannel.getTimerTask() != null)
457                                timeChannel.getTimerTask().cancel();
458                }
459                catch (Exception e) {
460                        // Should never happen...
461                }
462                
463                channel = timeChannel.getChannel();
464                
465                try {
466                    for (Subscription subscription : channel.getSubscriptions()) {
467                        try {
468                                Message message = subscription.getUnsubscribeMessage();
469                                handleMessage(channel.getFactory(), message, true);
470                        }
471                        catch (Exception e) {
472                                log.error(e, "Error while unsubscribing channel: %s from subscription: %s", channel, subscription);
473                        }
474                    }
475                }
476                finally {
477                        channels.remove(channelId);
478                        channel.destroy(timeout);
479                }
480        }
481        return channel;
482    }
483    
484    public boolean access(String channelId) {
485        if (channelId != null) {
486                TimeChannel<?> timeChannel = channels.get(channelId);
487                if (timeChannel != null) {
488                        synchronized (timeChannel) {
489                                TimerTask timerTask = timeChannel.getTimerTask();
490                                if (timerTask != null) {
491                                    log.debug("Canceling TimerTask: %s", timerTask);
492                                    timerTask.cancel();
493                                        timeChannel.setTimerTask(null);
494                                }
495                            
496                                timerTask = new ChannelTimerTask(this, channelId);
497                            timeChannel.setTimerTask(timerTask);
498                            
499                            long timeout = gravityConfig.getChannelIdleTimeoutMillis();
500                            log.debug("Scheduling TimerTask: %s for %s ms.", timerTask, timeout);
501                            channelsTimer.schedule(timerTask, timeout);
502                            return true;
503                        }
504                }
505        }
506        return false;
507    }
508    
509    public void execute(AsyncChannelRunner runner) {
510        if (gravityPool == null) {
511                runner.reset();
512                throw new NullPointerException("Gravity not started or pool disabled");
513        }
514        gravityPool.execute(runner);
515    }
516    
517    public boolean cancel(AsyncChannelRunner runner) {
518        if (gravityPool == null) {
519                runner.reset();
520                throw new NullPointerException("Gravity not started or pool disabled");
521        }
522        return gravityPool.remove(runner);
523    }
524
525    ///////////////////////////////////////////////////////////////////////////
526    // Incoming message handling.
527
528    public Message handleMessage(final ChannelFactory<?> channelFactory, Message message) {
529        return handleMessage(channelFactory, message, false);
530    }
531
532    public Message handleMessage(final ChannelFactory<?> channelFactory, final Message message, boolean skipInterceptor) {
533
534        AMF3MessageInterceptor interceptor = null;
535        if (!skipInterceptor)
536                interceptor = GraniteContext.getCurrentInstance().getGraniteConfig().getAmf3MessageInterceptor();
537        
538        Message reply = null;
539        boolean publish = false;
540        
541        try {
542                if (interceptor != null)
543                    interceptor.before(message);
544
545                if (message instanceof CommandMessage) {
546                    CommandMessage command = (CommandMessage)message;
547        
548                    switch (command.getOperation()) {
549        
550                    case CommandMessage.LOGIN_OPERATION:
551                    case CommandMessage.LOGOUT_OPERATION:
552                        return handleSecurityMessage(command);
553        
554                    case CommandMessage.CLIENT_PING_OPERATION:
555                        return handlePingMessage(channelFactory, command);
556                    case CommandMessage.CONNECT_OPERATION:
557                        return handleConnectMessage(channelFactory, command);
558                    case CommandMessage.DISCONNECT_OPERATION:
559                        return handleDisconnectMessage(channelFactory, command);
560                    case CommandMessage.SUBSCRIBE_OPERATION:
561                        return handleSubscribeMessage(channelFactory, command);
562                    case CommandMessage.UNSUBSCRIBE_OPERATION:
563                        return handleUnsubscribeMessage(channelFactory, command);
564        
565                    default:
566                        throw new UnsupportedOperationException("Unsupported command operation: " + command);
567                    }
568                }
569        
570                reply = handlePublishMessage(channelFactory, (AsyncMessage)message);
571                publish = true;
572        }
573        finally {
574                if (interceptor != null)
575                    interceptor.after(message, reply);
576        }
577        
578        if (reply != null) {
579                GraniteContext context = GraniteContext.getCurrentInstance();
580                if (context.getSessionId() != null) {
581                        reply.setHeader("org.granite.sessionId", context.getSessionId());
582                    if (publish && context instanceof ServletGraniteContext && ((ServletGraniteContext)context).getSession(false) != null) {
583                        long serverTime = new Date().getTime();
584                        ((ServletGraniteContext)context).getSession().setAttribute(GraniteContext.SESSION_LAST_ACCESSED_TIME_KEY, serverTime);
585                        reply.setHeader("org.granite.time", serverTime);
586                        reply.setHeader("org.granite.sessionExp", ((ServletGraniteContext)context).getSession().getMaxInactiveInterval());
587                    }
588                }
589        }
590        
591        return reply;
592    }
593
594    ///////////////////////////////////////////////////////////////////////////
595    // Other Public API methods.
596
597    public GraniteContext initThread(String sessionId, String clientType) {
598        GraniteContext context = GraniteContext.getCurrentInstance();
599        if (context == null)
600            context = SimpleGraniteContext.createThreadInstance(graniteConfig, servicesConfig, sessionId, applicationMap, clientType);
601        return context;
602    }
603    
604    public void releaseThread() {
605        GraniteContext.release();
606        }
607
608        public Message publishMessage(AsyncMessage message) {
609        return publishMessage(serverChannel, message);
610    }
611
612    public Message publishMessage(Channel fromChannel, AsyncMessage message) {
613        initThread(null, fromChannel != null ? fromChannel.getClientType() : serverChannel.getClientType());
614
615        return handlePublishMessage(null, message, fromChannel != null ? fromChannel : serverChannel);
616    }
617
618    private Message handlePingMessage(ChannelFactory<?> channelFactory, CommandMessage message) {
619        
620        Channel channel = createChannel(channelFactory, (String)message.getClientId());
621        
622        AsyncMessage reply = new AcknowledgeMessage(message);
623        reply.setClientId(channel.getId());
624        Map<String, Object> advice = new HashMap<String, Object>();
625        advice.put(RECONNECT_INTERVAL_MS_KEY, Long.valueOf(gravityConfig.getReconnectIntervalMillis()));
626        advice.put(RECONNECT_MAX_ATTEMPTS_KEY, Long.valueOf(gravityConfig.getReconnectMaxAttempts()));
627        advice.put(ENCODE_MESSAGE_BODY_KEY, Boolean.valueOf(gravityConfig.isEncodeMessageBody()));
628        reply.setBody(advice);
629        reply.setDestination(message.getDestination());
630
631        log.debug("handshake.handle: reply=%s", reply);
632
633        return reply;
634    }
635
636    private Message handleSecurityMessage(CommandMessage message) {
637        GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
638
639        Message response = null;
640
641        if (!config.hasSecurityService())
642            log.warn("Ignored security operation (no security settings in granite-config.xml): %s", message);
643        else if (!config.getSecurityService().acceptsContext())
644            log.info("Ignored security operation (security service does not handle this kind of granite context)", message);
645        else {
646            SecurityService securityService = config.getSecurityService();
647            try {
648                if (message.isLoginOperation())
649                    securityService.login(message.getBody(), (String)message.getHeader(Message.CREDENTIALS_CHARSET_HEADER));
650                else
651                    securityService.logout();
652            }
653            catch (Exception e) {
654                if (e instanceof SecurityServiceException)
655                    log.debug(e, "Could not process security operation: %s", message);
656                else
657                    log.error(e, "Could not process security operation: %s", message);
658                response = new ErrorMessage(message, e, true);
659            }
660        }
661
662        if (response == null) {
663            response = new AcknowledgeMessage(message, true);
664            // For SDK 2.0.1_Hotfix2.
665            if (message.isSecurityOperation())
666                response.setBody("success");
667        }
668
669        return response;
670    }
671
672    private Message handleConnectMessage(final ChannelFactory<?> channelFactory, CommandMessage message) {
673        Channel client = getChannel(channelFactory, (String)message.getClientId());
674
675        if (client == null)
676            return handleUnknownClientMessage(message);
677
678        return null;
679    }
680
681    private Message handleDisconnectMessage(final ChannelFactory<?> channelFactory, CommandMessage message) {
682        Channel client = getChannel(channelFactory, (String)message.getClientId());
683        if (client == null)
684            return handleUnknownClientMessage(message);
685
686        removeChannel(client.getId(), false);
687
688        AcknowledgeMessage reply = new AcknowledgeMessage(message);
689        reply.setDestination(message.getDestination());
690        reply.setClientId(client.getId());
691        return reply;
692    }
693
694    private Message handleSubscribeMessage(final ChannelFactory<?> channelFactory, final CommandMessage message) {
695        return handleSubscribeMessage(channelFactory, message, true);
696    }
697    
698    private Message handleSubscribeMessage(final ChannelFactory<?> channelFactory, final CommandMessage message, final boolean saveMessageInSession) {
699
700        final GraniteContext context = GraniteContext.getCurrentInstance();
701
702        // Get and check destination.
703        final Destination destination = context.getServicesConfig().findDestinationById(
704            message.getMessageRefType(),
705            message.getDestination()
706        );
707
708        if (destination == null)
709            return getInvalidDestinationError(message);
710
711
712        GravityInvocationContext invocationContext = new GravityInvocationContext(message, destination) {
713                        @Override
714                        public Object invoke() throws Exception {
715                        // Subscribe...
716                        Channel channel = getChannel(channelFactory, (String)message.getClientId());
717                        if (channel == null)
718                            return handleUnknownClientMessage(message);
719
720                        String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
721                        if (subscriptionId == null) {
722                            subscriptionId = UUIDUtil.randomUUID();
723                            message.setHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER, subscriptionId);
724                        }
725                        
726                        DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
727                        if (gdd != null) {
728                            if (!gdd.hasChannelId(channel.getId()))
729                                gdd.addChannelId(channel.getId(), channel.getFactory().getClass().getName(), context.getClientType());
730                            
731                                if (Boolean.TRUE.toString().equals(destination.getProperties().get("session-selector"))) {
732                                        String selector = gdd.getDestinationSelector(destination.getId());
733                                        log.debug("Session selector found: %s", selector);
734                                        if (selector != null)
735                                                message.setHeader(CommandMessage.SELECTOR_HEADER, selector);
736                                }
737                        }
738
739                        ServiceAdapter adapter = adapterFactory.getServiceAdapter(message);
740                        
741                        AsyncMessage reply = (AsyncMessage)adapter.manage(channel, message);
742                        
743                        postManage(channel);
744                        
745                        if (saveMessageInSession && !(reply instanceof ErrorMessage)) {
746                                // Save subscription message in distributed data (clustering).
747                                try {
748                                        if (gdd != null) {
749                                                log.debug("Saving new subscription message for channel: %s - %s", channel.getId(), message);
750                                                gdd.addSubcription(channel.getId(), message);
751                                        }
752                                }
753                                catch (Exception e) {
754                                        log.error(e, "Could not add subscription in distributed data: %s - %s", channel.getId(), subscriptionId);
755                                }
756                        }
757
758                        reply.setDestination(message.getDestination());
759                        reply.setClientId(channel.getId());
760                        reply.getHeaders().putAll(message.getHeaders());
761
762                        if (gdd != null && message.getDestination() != null) {
763                                gdd.setDestinationClientId(message.getDestination(), channel.getId());
764                                gdd.setDestinationSubscriptionId(message.getDestination(), subscriptionId);
765                        }
766
767                        return reply;
768                        }               
769        };
770
771        // Check security 1 (destination).
772        if (destination.getSecurizer() instanceof GravityDestinationSecurizer) {
773            try {
774                ((GravityDestinationSecurizer)destination.getSecurizer()).canSubscribe(invocationContext);
775            } 
776            catch (Exception e) {
777                return new ErrorMessage(message, e);
778            }
779        }
780        
781        // Check security 2 (security service).
782        GraniteConfig config = context.getGraniteConfig();
783        try {
784            if (config.hasSecurityService() && config.getSecurityService().acceptsContext())
785                return (Message)config.getSecurityService().authorize(invocationContext);
786                
787            return (Message)invocationContext.invoke();
788        }
789        catch (Exception e) {
790            return new ErrorMessage(message, e, true);
791        }
792    }
793
794    private Message handleUnsubscribeMessage(final ChannelFactory<?> channelFactory, CommandMessage message) {
795        Channel channel = getChannel(channelFactory, (String)message.getClientId());
796        if (channel == null)
797            return handleUnknownClientMessage(message);
798
799        AsyncMessage reply = null;
800
801        ServiceAdapter adapter = adapterFactory.getServiceAdapter(message);
802        
803        reply = (AcknowledgeMessage)adapter.manage(channel, message);
804        
805        postManage(channel);
806        
807        if (!(reply instanceof ErrorMessage)) {
808                // Remove subscription message in distributed data (clustering).
809                try {
810                        DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
811                        if (gdd != null) {
812                                String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
813                                log.debug("Removing subscription message from channel info: %s - %s", channel.getId(), subscriptionId);
814                                gdd.removeSubcription(channel.getId(), subscriptionId);
815                        }
816                }
817                catch (Exception e) {
818                        log.error(
819                                e, "Could not remove subscription from distributed data: %s - %s",
820                                channel.getId(), message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER)
821                        );
822                }
823        }
824
825        reply.setDestination(message.getDestination());
826        reply.setClientId(channel.getId());
827        reply.getHeaders().putAll(message.getHeaders());
828
829        return reply;
830    }
831    
832    protected void postManage(Channel channel) {
833    }
834
835    private Message handlePublishMessage(final ChannelFactory<?> channelFactory, final AsyncMessage message) {
836        return handlePublishMessage(channelFactory, message, null);
837    }
838    
839    private Message handlePublishMessage(final ChannelFactory<?> channelFactory, final AsyncMessage message, final Channel channel) {
840
841        GraniteContext context = GraniteContext.getCurrentInstance();
842
843        // Get and check destination.
844        Destination destination = context.getServicesConfig().findDestinationById(
845            message.getClass().getName(),
846            message.getDestination()
847        );
848
849        if (destination == null)
850            return getInvalidDestinationError(message);
851                
852        if (message.getMessageId() == null)
853                message.setMessageId(UUIDUtil.randomUUID());
854        message.setTimestamp(System.currentTimeMillis());
855        if (channel != null)
856                message.setClientId(channel.getId());
857
858        GravityInvocationContext invocationContext = new GravityInvocationContext(message, destination) {
859                        @Override
860                        public Object invoke() throws Exception {
861                        // Publish...
862                        Channel fromChannel = channel;
863                        if (fromChannel == null)
864                                fromChannel = getChannel(channelFactory, (String)message.getClientId());
865                        if (fromChannel == null)
866                            return handleUnknownClientMessage(message);
867
868                        ServiceAdapter adapter = adapterFactory.getServiceAdapter(message);
869                        
870                        AsyncMessage reply = (AsyncMessage)adapter.invoke(fromChannel, message);
871
872                        reply.setDestination(message.getDestination());
873                        reply.setClientId(fromChannel.getId());
874
875                        return reply;
876                        }               
877        };
878
879        // Check security 1 (destination).
880        if (destination.getSecurizer() instanceof GravityDestinationSecurizer) {
881            try {
882                ((GravityDestinationSecurizer)destination.getSecurizer()).canPublish(invocationContext);
883            } 
884            catch (Exception e) {
885                return new ErrorMessage(message, e, true);
886            }
887        }
888        
889        // Check security 2 (security service).
890        GraniteConfig config = context.getGraniteConfig();
891        try {
892                if (config.hasSecurityService() && config.getSecurityService().acceptsContext())
893                return (Message)config.getSecurityService().authorize(invocationContext);
894
895                return (Message)invocationContext.invoke();
896        } 
897        catch (Exception e) {
898            return new ErrorMessage(message, e, true);
899        }
900    }
901
902    private Message handleUnknownClientMessage(Message message) {
903        ErrorMessage reply = new ErrorMessage(message, true);
904        reply.setFaultCode("Server.Call.UnknownClient");
905        reply.setFaultString("Unknown client");
906        return reply;
907    }
908
909    ///////////////////////////////////////////////////////////////////////////
910    // Utilities.
911
912    private ErrorMessage getInvalidDestinationError(Message message) {
913
914        String messageType = message.getClass().getName();
915        if (message instanceof CommandMessage)
916            messageType += '[' + ((CommandMessage)message).getMessageRefType() + ']';
917
918        ErrorMessage reply = new ErrorMessage(message, true);
919        reply.setFaultCode("Server.Messaging.InvalidDestination");
920        reply.setFaultString(
921            "No configured destination for id: " + message.getDestination() +
922            " and message type: " + messageType
923        );
924        return reply;
925    }
926
927    private static class ServerChannel extends AbstractChannel implements Serializable {
928
929                private static final long serialVersionUID = 1L;
930                
931                public ServerChannel(Gravity gravity, String channelId, ChannelFactory<ServerChannel> factory, String clientType) {
932                super(gravity, channelId, factory, clientType);
933        }
934
935                @Override
936                public Gravity getGravity() {
937                        return gravity;
938                }
939                
940                public void close() {
941                }               
942
943                @Override
944                public void receive(AsyncMessage message) throws MessageReceivingException {
945                }
946
947                @Override
948                protected boolean hasAsyncHttpContext() {
949                        return false;
950                }
951
952                @Override
953                protected AsyncHttpContext acquireAsyncHttpContext() {
954                        return null;
955                }
956
957                @Override
958                protected void releaseAsyncHttpContext(AsyncHttpContext context) {
959                }
960    }
961}