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 }