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