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 }