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     */
022    package org.granite.gravity;
023    
024    import java.io.Serializable;
025    import java.util.Date;
026    import java.util.HashMap;
027    import java.util.Iterator;
028    import java.util.Map;
029    import java.util.Timer;
030    import java.util.TimerTask;
031    import java.util.concurrent.ConcurrentHashMap;
032    
033    import javax.management.ObjectName;
034    
035    import org.granite.clustering.DistributedData;
036    import org.granite.config.GraniteConfig;
037    import org.granite.config.flex.Destination;
038    import org.granite.config.flex.ServicesConfig;
039    import org.granite.context.GraniteContext;
040    import org.granite.context.SimpleGraniteContext;
041    import org.granite.gravity.adapters.AdapterFactory;
042    import org.granite.gravity.adapters.ServiceAdapter;
043    import org.granite.gravity.security.GravityDestinationSecurizer;
044    import org.granite.gravity.security.GravityInvocationContext;
045    import org.granite.gravity.udp.UdpReceiverFactory;
046    import org.granite.jmx.MBeanServerLocator;
047    import org.granite.jmx.OpenMBean;
048    import org.granite.logging.Logger;
049    import org.granite.messaging.amf.process.AMF3MessageInterceptor;
050    import org.granite.messaging.jmf.SharedContext;
051    import org.granite.messaging.service.security.SecurityService;
052    import org.granite.messaging.service.security.SecurityServiceException;
053    import org.granite.messaging.webapp.ServletGraniteContext;
054    import org.granite.scan.ServiceLoader;
055    import org.granite.util.TypeUtil;
056    import org.granite.util.UUIDUtil;
057    
058    import flex.messaging.messages.AcknowledgeMessage;
059    import flex.messaging.messages.AsyncMessage;
060    import flex.messaging.messages.CommandMessage;
061    import flex.messaging.messages.ErrorMessage;
062    import flex.messaging.messages.Message;
063    
064    /**
065     * @author William DRAI
066     * @author Franck WOLFF
067     */
068    public 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    }