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 021package org.granite.gravity; 022 023import java.io.Serializable; 024import java.util.Date; 025import java.util.HashMap; 026import java.util.Map; 027import java.util.Timer; 028import java.util.TimerTask; 029import java.util.concurrent.ConcurrentHashMap; 030 031import javax.management.ObjectName; 032 033import org.granite.clustering.DistributedData; 034import org.granite.config.GraniteConfig; 035import org.granite.config.flex.Destination; 036import org.granite.config.flex.ServicesConfig; 037import org.granite.context.GraniteContext; 038import org.granite.context.SimpleGraniteContext; 039import org.granite.gravity.adapters.AdapterFactory; 040import org.granite.gravity.adapters.ServiceAdapter; 041import org.granite.gravity.security.GravityDestinationSecurizer; 042import org.granite.gravity.security.GravityInvocationContext; 043import org.granite.jmx.MBeanServerLocator; 044import org.granite.jmx.OpenMBean; 045import org.granite.logging.Logger; 046import org.granite.messaging.amf.process.AMF3MessageInterceptor; 047import org.granite.messaging.service.security.SecurityService; 048import org.granite.messaging.service.security.SecurityServiceException; 049import org.granite.messaging.webapp.ServletGraniteContext; 050import org.granite.util.TypeUtil; 051import org.granite.util.UUIDUtil; 052 053import flex.messaging.messages.AcknowledgeMessage; 054import flex.messaging.messages.AsyncMessage; 055import flex.messaging.messages.CommandMessage; 056import flex.messaging.messages.ErrorMessage; 057import flex.messaging.messages.Message; 058 059/** 060 * @author William DRAI 061 * @author Franck WOLFF 062 */ 063public 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, ServerChannel.class.getName(), null, null); 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 long getChannelIdleTimeoutMillis() { 226 return gravityConfig.getChannelIdleTimeoutMillis(); 227 } 228 public void setChannelIdleTimeoutMillis(long channelIdleTimeoutMillis) { 229 gravityConfig.setChannelIdleTimeoutMillis(channelIdleTimeoutMillis); 230 } 231 232 public boolean isRetryOnError() { 233 return gravityConfig.isRetryOnError(); 234 } 235 public void setRetryOnError(boolean retryOnError) { 236 gravityConfig.setRetryOnError(retryOnError); 237 } 238 239 public long getLongPollingTimeoutMillis() { 240 return gravityConfig.getLongPollingTimeoutMillis(); 241 } 242 public void setLongPollingTimeoutMillis(long longPollingTimeoutMillis) { 243 gravityConfig.setLongPollingTimeoutMillis(longPollingTimeoutMillis); 244 } 245 246 public int getMaxMessagesQueuedPerChannel() { 247 return gravityConfig.getMaxMessagesQueuedPerChannel(); 248 } 249 public void setMaxMessagesQueuedPerChannel(int maxMessagesQueuedPerChannel) { 250 gravityConfig.setMaxMessagesQueuedPerChannel(maxMessagesQueuedPerChannel); 251 } 252 253 public long getReconnectIntervalMillis() { 254 return gravityConfig.getReconnectIntervalMillis(); 255 } 256 257 public int getReconnectMaxAttempts() { 258 return gravityConfig.getReconnectMaxAttempts(); 259 } 260 261 public int getCorePoolSize() { 262 if (gravityPool != null) 263 return gravityPool.getCorePoolSize(); 264 return gravityConfig.getCorePoolSize(); 265 } 266 267 public void setCorePoolSize(int corePoolSize) { 268 gravityConfig.setCorePoolSize(corePoolSize); 269 if (gravityPool != null) 270 gravityPool.setCorePoolSize(corePoolSize); 271 } 272 273 public long getKeepAliveTimeMillis() { 274 if (gravityPool != null) 275 return gravityPool.getKeepAliveTimeMillis(); 276 return gravityConfig.getKeepAliveTimeMillis(); 277 } 278 public void setKeepAliveTimeMillis(long keepAliveTimeMillis) { 279 gravityConfig.setKeepAliveTimeMillis(keepAliveTimeMillis); 280 if (gravityPool != null) 281 gravityPool.setKeepAliveTimeMillis(keepAliveTimeMillis); 282 } 283 284 public int getMaximumPoolSize() { 285 if (gravityPool != null) 286 return gravityPool.getMaximumPoolSize(); 287 return gravityConfig.getMaximumPoolSize(); 288 } 289 public void setMaximumPoolSize(int maximumPoolSize) { 290 gravityConfig.setMaximumPoolSize(maximumPoolSize); 291 if (gravityPool != null) 292 gravityPool.setMaximumPoolSize(maximumPoolSize); 293 } 294 295 public int getQueueCapacity() { 296 if (gravityPool != null) 297 return gravityPool.getQueueCapacity(); 298 return gravityConfig.getQueueCapacity(); 299 } 300 301 public int getQueueRemainingCapacity() { 302 if (gravityPool != null) 303 return gravityPool.getQueueRemainingCapacity(); 304 return gravityConfig.getQueueCapacity(); 305 } 306 307 public int getQueueSize() { 308 if (gravityPool != null) 309 return gravityPool.getQueueSize(); 310 return 0; 311 } 312 313 /////////////////////////////////////////////////////////////////////////// 314 // Channel's operations. 315 316 protected <C extends Channel> C createChannel(ChannelFactory<C> channelFactory, String clientId) { 317 C channel = null; 318 if (clientId != null) { 319 channel = getChannel(channelFactory, clientId); 320 if (channel != null) 321 return channel; 322 } 323 324 String clientType = GraniteContext.getCurrentInstance().getClientType(); 325 channel = channelFactory.newChannel(UUIDUtil.randomUUID(), clientType); 326 TimeChannel<C> timeChannel = new TimeChannel<C>(channel); 327 for (int i = 0; channels.putIfAbsent(channel.getId(), timeChannel) != null; i++) { 328 if (i >= 10) 329 throw new RuntimeException("Could not find random new clientId after 10 iterations"); 330 channel.destroy(); 331 channel = channelFactory.newChannel(UUIDUtil.randomUUID(), clientType); 332 timeChannel = new TimeChannel<C>(channel); 333 } 334 335 String channelId = channel.getId(); 336 337 // Save channel id in distributed data (clustering). 338 try { 339 DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance(); 340 if (gdd != null) { 341 log.debug("Saving channel id in distributed data: %s", channelId); 342 gdd.addChannelId(channelId, channelFactory.getClass().getName()); 343 } 344 } 345 catch (Exception e) { 346 log.error(e, "Could not add channel id in distributed data: %s", channelId); 347 } 348 349 // Initialize timer task. 350 access(channelId); 351 352 return channel; 353 } 354 355 @SuppressWarnings("unchecked") 356 public <C extends Channel> C getChannel(ChannelFactory<C> channelFactory, String clientId) { 357 if (clientId == null) 358 return null; 359 360 TimeChannel<C> timeChannel = (TimeChannel<C>)channels.get(clientId); 361 if (timeChannel == null) { 362 // Look for existing channel id/subscriptions in distributed data (clustering). 363 try { 364 DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance(); 365 if (gdd != null && gdd.hasChannelId(clientId)) { 366 log.debug("Found channel id in distributed data: %s", clientId); 367 String channelFactoryClassName = gdd.getChannelFactoryClassName(clientId); 368 channelFactory = TypeUtil.newInstance(channelFactoryClassName, ChannelFactory.class); 369 String clientType = GraniteContext.getCurrentInstance().getClientType(); 370 C channel = channelFactory.newChannel(clientId, clientType); 371 timeChannel = new TimeChannel<C>(channel); 372 if (channels.putIfAbsent(clientId, timeChannel) == null) { 373 for (CommandMessage subscription : gdd.getSubscriptions(clientId)) { 374 log.debug("Resubscribing channel: %s - %s", clientId, subscription); 375 handleSubscribeMessage(channelFactory, subscription, false); 376 } 377 access(clientId); 378 } 379 } 380 } 381 catch (Exception e) { 382 log.error(e, "Could not recreate channel/subscriptions from distributed data: %s", clientId); 383 } 384 } 385 386 return (timeChannel != null ? timeChannel.getChannel() : null); 387 } 388 389 public Channel removeChannel(String channelId) { 390 if (channelId == null) 391 return null; 392 393 // Remove existing channel id/subscriptions in distributed data (clustering). 394 try { 395 DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance(); 396 if (gdd != null) { 397 log.debug("Removing channel id from distributed data: %s", channelId); 398 gdd.removeChannelId(channelId); 399 } 400 } 401 catch (Exception e) { 402 log.error(e, "Could not remove channel id from distributed data: %s", channelId); 403 } 404 405 TimeChannel<?> timeChannel = channels.remove(channelId); 406 Channel channel = null; 407 if (timeChannel != null) { 408 try { 409 if (timeChannel.getTimerTask() != null) 410 timeChannel.getTimerTask().cancel(); 411 } 412 catch (Exception e) { 413 // Should never happen... 414 } 415 416 channel = timeChannel.getChannel(); 417 418 try { 419 for (Subscription subscription : channel.getSubscriptions()) { 420 try { 421 Message message = subscription.getUnsubscribeMessage(); 422 handleMessage(channel.getFactory(), message, true); 423 } 424 catch (Exception e) { 425 log.error(e, "Error while unsubscribing channel: %s from subscription: %s", channel, subscription); 426 } 427 } 428 } 429 finally { 430 channel.destroy(); 431 } 432 } 433 434 return channel; 435 } 436 437 public boolean access(String channelId) { 438 if (channelId != null) { 439 TimeChannel<?> timeChannel = channels.get(channelId); 440 if (timeChannel != null) { 441 synchronized (timeChannel) { 442 TimerTask timerTask = timeChannel.getTimerTask(); 443 if (timerTask != null) { 444 log.debug("Canceling TimerTask: %s", timerTask); 445 timerTask.cancel(); 446 timeChannel.setTimerTask(null); 447 } 448 449 timerTask = new ChannelTimerTask(this, channelId); 450 timeChannel.setTimerTask(timerTask); 451 452 long timeout = gravityConfig.getChannelIdleTimeoutMillis(); 453 log.debug("Scheduling TimerTask: %s for %s ms.", timerTask, timeout); 454 channelsTimer.schedule(timerTask, timeout); 455 return true; 456 } 457 } 458 } 459 return false; 460 } 461 462 public void execute(AsyncChannelRunner runner) { 463 if (gravityPool == null) { 464 runner.reset(); 465 throw new NullPointerException("Gravity not started or pool disabled"); 466 } 467 gravityPool.execute(runner); 468 } 469 470 public boolean cancel(AsyncChannelRunner runner) { 471 if (gravityPool == null) { 472 runner.reset(); 473 throw new NullPointerException("Gravity not started or pool disabled"); 474 } 475 return gravityPool.remove(runner); 476 } 477 478 /////////////////////////////////////////////////////////////////////////// 479 // Incoming message handling. 480 481 public Message handleMessage(final ChannelFactory<?> channelFactory, Message message) { 482 return handleMessage(channelFactory, message, false); 483 } 484 485 public Message handleMessage(final ChannelFactory<?> channelFactory, final Message message, boolean skipInterceptor) { 486 487 AMF3MessageInterceptor interceptor = null; 488 if (!skipInterceptor) 489 interceptor = GraniteContext.getCurrentInstance().getGraniteConfig().getAmf3MessageInterceptor(); 490 491 Message reply = null; 492 boolean publish = false; 493 494 try { 495 if (interceptor != null) 496 interceptor.before(message); 497 498 if (message instanceof CommandMessage) { 499 CommandMessage command = (CommandMessage)message; 500 501 switch (command.getOperation()) { 502 503 case CommandMessage.LOGIN_OPERATION: 504 case CommandMessage.LOGOUT_OPERATION: 505 return handleSecurityMessage(command); 506 507 case CommandMessage.CLIENT_PING_OPERATION: 508 return handlePingMessage(channelFactory, command); 509 case CommandMessage.CONNECT_OPERATION: 510 return handleConnectMessage(channelFactory, command); 511 case CommandMessage.DISCONNECT_OPERATION: 512 return handleDisconnectMessage(channelFactory, command); 513 case CommandMessage.SUBSCRIBE_OPERATION: 514 return handleSubscribeMessage(channelFactory, command); 515 case CommandMessage.UNSUBSCRIBE_OPERATION: 516 return handleUnsubscribeMessage(channelFactory, command); 517 518 default: 519 throw new UnsupportedOperationException("Unsupported command operation: " + command); 520 } 521 } 522 523 reply = handlePublishMessage(channelFactory, (AsyncMessage)message); 524 publish = true; 525 } 526 finally { 527 if (interceptor != null) 528 interceptor.after(message, reply); 529 } 530 531 if (reply != null) { 532 GraniteContext context = GraniteContext.getCurrentInstance(); 533 if (context.getSessionId() != null) { 534 reply.setHeader("org.granite.sessionId", context.getSessionId()); 535 if (publish && context instanceof ServletGraniteContext && ((ServletGraniteContext)context).getSession(false) != null) { 536 long serverTime = new Date().getTime(); 537 ((ServletGraniteContext)context).getSession().setAttribute(GraniteContext.SESSION_LAST_ACCESSED_TIME_KEY, serverTime); 538 reply.setHeader("org.granite.time", serverTime); 539 reply.setHeader("org.granite.sessionExp", ((ServletGraniteContext)context).getSession().getMaxInactiveInterval()); 540 } 541 } 542 } 543 544 return reply; 545 } 546 547 /////////////////////////////////////////////////////////////////////////// 548 // Other Public API methods. 549 550 public GraniteContext initThread(String sessionId, String clientType) { 551 GraniteContext context = GraniteContext.getCurrentInstance(); 552 if (context == null) 553 context = SimpleGraniteContext.createThreadInstance(graniteConfig, servicesConfig, sessionId, applicationMap, clientType); 554 return context; 555 } 556 557 public void releaseThread() { 558 GraniteContext.release(); 559 } 560 561 public Message publishMessage(AsyncMessage message) { 562 return publishMessage(serverChannel, message); 563 } 564 565 public Message publishMessage(Channel fromChannel, AsyncMessage message) { 566 initThread(null, fromChannel != null ? fromChannel.getClientType() : serverChannel.getClientType()); 567 568 return handlePublishMessage(null, message, fromChannel != null ? fromChannel : serverChannel); 569 } 570 571 private Message handlePingMessage(ChannelFactory<?> channelFactory, CommandMessage message) { 572 573 Channel channel = createChannel(channelFactory, (String)message.getClientId()); 574 575 AsyncMessage reply = new AcknowledgeMessage(message); 576 reply.setClientId(channel.getId()); 577 Map<String, Object> advice = new HashMap<String, Object>(); 578 advice.put(RECONNECT_INTERVAL_MS_KEY, Long.valueOf(gravityConfig.getReconnectIntervalMillis())); 579 advice.put(RECONNECT_MAX_ATTEMPTS_KEY, Long.valueOf(gravityConfig.getReconnectMaxAttempts())); 580 reply.setBody(advice); 581 reply.setDestination(message.getDestination()); 582 583 log.debug("handshake.handle: reply=%s", reply); 584 585 return reply; 586 } 587 588 private Message handleSecurityMessage(CommandMessage message) { 589 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig(); 590 591 Message response = null; 592 593 if (!config.hasSecurityService()) 594 log.warn("Ignored security operation (no security settings in granite-config.xml): %s", message); 595 else if (!config.getSecurityService().acceptsContext()) 596 log.info("Ignored security operation (security service does not handle this kind of granite context)", message); 597 else { 598 SecurityService securityService = config.getSecurityService(); 599 try { 600 if (message.isLoginOperation()) 601 securityService.login(message.getBody(), (String)message.getHeader(Message.CREDENTIALS_CHARSET_HEADER)); 602 else 603 securityService.logout(); 604 } 605 catch (Exception e) { 606 if (e instanceof SecurityServiceException) 607 log.debug(e, "Could not process security operation: %s", message); 608 else 609 log.error(e, "Could not process security operation: %s", message); 610 response = new ErrorMessage(message, e, true); 611 } 612 } 613 614 if (response == null) { 615 response = new AcknowledgeMessage(message, true); 616 // For SDK 2.0.1_Hotfix2. 617 if (message.isSecurityOperation()) 618 response.setBody("success"); 619 } 620 621 return response; 622 } 623 624 private Message handleConnectMessage(final ChannelFactory<?> channelFactory, CommandMessage message) { 625 Channel client = getChannel(channelFactory, (String)message.getClientId()); 626 627 if (client == null) 628 return handleUnknownClientMessage(message); 629 630 return null; 631 } 632 633 private Message handleDisconnectMessage(final ChannelFactory<?> channelFactory, CommandMessage message) { 634 Channel client = getChannel(channelFactory, (String)message.getClientId()); 635 if (client == null) 636 return handleUnknownClientMessage(message); 637 638 removeChannel(client.getId()); 639 640 AcknowledgeMessage reply = new AcknowledgeMessage(message); 641 reply.setDestination(message.getDestination()); 642 reply.setClientId(client.getId()); 643 return reply; 644 } 645 646 private Message handleSubscribeMessage(final ChannelFactory<?> channelFactory, final CommandMessage message) { 647 return handleSubscribeMessage(channelFactory, message, true); 648 } 649 650 private Message handleSubscribeMessage(final ChannelFactory<?> channelFactory, final CommandMessage message, final boolean saveMessageInSession) { 651 652 final GraniteContext context = GraniteContext.getCurrentInstance(); 653 654 // Get and check destination. 655 final Destination destination = context.getServicesConfig().findDestinationById( 656 message.getMessageRefType(), 657 message.getDestination() 658 ); 659 660 if (destination == null) 661 return getInvalidDestinationError(message); 662 663 664 GravityInvocationContext invocationContext = new GravityInvocationContext(message, destination) { 665 @Override 666 public Object invoke() throws Exception { 667 // Subscribe... 668 Channel channel = getChannel(channelFactory, (String)message.getClientId()); 669 if (channel == null) 670 return handleUnknownClientMessage(message); 671 672 String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER); 673 if (subscriptionId == null) { 674 subscriptionId = UUIDUtil.randomUUID(); 675 message.setHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER, subscriptionId); 676 } 677 678 DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance(); 679 if (gdd != null) { 680 if (Boolean.TRUE.toString().equals(destination.getProperties().get("session-selector"))) { 681 String selector = gdd.getDestinationSelector(destination.getId()); 682 log.debug("Session selector found: %s", selector); 683 if (selector != null) 684 message.setHeader(CommandMessage.SELECTOR_HEADER, selector); 685 } 686 } 687 688 ServiceAdapter adapter = adapterFactory.getServiceAdapter(message); 689 690 AsyncMessage reply = (AsyncMessage)adapter.manage(channel, message); 691 692 postManage(channel); 693 694 if (saveMessageInSession && !(reply instanceof ErrorMessage)) { 695 // Save subscription message in distributed data (clustering). 696 try { 697 if (gdd != null) { 698 log.debug("Saving new subscription message for channel: %s - %s", channel.getId(), message); 699 gdd.addSubcription(channel.getId(), message); 700 } 701 } 702 catch (Exception e) { 703 log.error(e, "Could not add subscription in distributed data: %s - %s", channel.getId(), subscriptionId); 704 } 705 } 706 707 reply.setDestination(message.getDestination()); 708 reply.setClientId(channel.getId()); 709 reply.getHeaders().putAll(message.getHeaders()); 710 711 if (gdd != null && message.getDestination() != null) { 712 gdd.setDestinationClientId(message.getDestination(), channel.getId()); 713 gdd.setDestinationSubscriptionId(message.getDestination(), subscriptionId); 714 } 715 716 return reply; 717 } 718 }; 719 720 // Check security 1 (destination). 721 if (destination.getSecurizer() instanceof GravityDestinationSecurizer) { 722 try { 723 ((GravityDestinationSecurizer)destination.getSecurizer()).canSubscribe(invocationContext); 724 } 725 catch (Exception e) { 726 return new ErrorMessage(message, e); 727 } 728 } 729 730 // Check security 2 (security service). 731 GraniteConfig config = context.getGraniteConfig(); 732 try { 733 if (config.hasSecurityService() && config.getSecurityService().acceptsContext()) 734 return (Message)config.getSecurityService().authorize(invocationContext); 735 736 return (Message)invocationContext.invoke(); 737 } 738 catch (Exception e) { 739 return new ErrorMessage(message, e, true); 740 } 741 } 742 743 private Message handleUnsubscribeMessage(final ChannelFactory<?> channelFactory, CommandMessage message) { 744 Channel channel = getChannel(channelFactory, (String)message.getClientId()); 745 if (channel == null) 746 return handleUnknownClientMessage(message); 747 748 AsyncMessage reply = null; 749 750 ServiceAdapter adapter = adapterFactory.getServiceAdapter(message); 751 752 reply = (AcknowledgeMessage)adapter.manage(channel, message); 753 754 postManage(channel); 755 756 if (!(reply instanceof ErrorMessage)) { 757 // Remove subscription message in distributed data (clustering). 758 try { 759 DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance(); 760 if (gdd != null) { 761 String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER); 762 log.debug("Removing subscription message from channel info: %s - %s", channel.getId(), subscriptionId); 763 gdd.removeSubcription(channel.getId(), subscriptionId); 764 } 765 } 766 catch (Exception e) { 767 log.error( 768 e, "Could not remove subscription from distributed data: %s - %s", 769 channel.getId(), message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER) 770 ); 771 } 772 } 773 774 reply.setDestination(message.getDestination()); 775 reply.setClientId(channel.getId()); 776 reply.getHeaders().putAll(message.getHeaders()); 777 778 return reply; 779 } 780 781 protected void postManage(Channel channel) { 782 } 783 784 private Message handlePublishMessage(final ChannelFactory<?> channelFactory, final AsyncMessage message) { 785 return handlePublishMessage(channelFactory, message, null); 786 } 787 788 private Message handlePublishMessage(final ChannelFactory<?> channelFactory, final AsyncMessage message, final Channel channel) { 789 790 GraniteContext context = GraniteContext.getCurrentInstance(); 791 792 // Get and check destination. 793 Destination destination = context.getServicesConfig().findDestinationById( 794 message.getClass().getName(), 795 message.getDestination() 796 ); 797 798 if (destination == null) 799 return getInvalidDestinationError(message); 800 801 if (message.getMessageId() == null) 802 message.setMessageId(UUIDUtil.randomUUID()); 803 message.setTimestamp(System.currentTimeMillis()); 804 if (channel != null) 805 message.setClientId(channel.getId()); 806 807 GravityInvocationContext invocationContext = new GravityInvocationContext(message, destination) { 808 @Override 809 public Object invoke() throws Exception { 810 // Publish... 811 Channel fromChannel = channel; 812 if (fromChannel == null) 813 fromChannel = getChannel(channelFactory, (String)message.getClientId()); 814 if (fromChannel == null) 815 return handleUnknownClientMessage(message); 816 817 ServiceAdapter adapter = adapterFactory.getServiceAdapter(message); 818 819 AsyncMessage reply = (AsyncMessage)adapter.invoke(fromChannel, message); 820 821 reply.setDestination(message.getDestination()); 822 reply.setClientId(fromChannel.getId()); 823 824 return reply; 825 } 826 }; 827 828 // Check security 1 (destination). 829 if (destination.getSecurizer() instanceof GravityDestinationSecurizer) { 830 try { 831 ((GravityDestinationSecurizer)destination.getSecurizer()).canPublish(invocationContext); 832 } 833 catch (Exception e) { 834 return new ErrorMessage(message, e, true); 835 } 836 } 837 838 // Check security 2 (security service). 839 GraniteConfig config = context.getGraniteConfig(); 840 try { 841 if (config.hasSecurityService() && config.getSecurityService().acceptsContext()) 842 return (Message)config.getSecurityService().authorize(invocationContext); 843 844 return (Message)invocationContext.invoke(); 845 } 846 catch (Exception e) { 847 return new ErrorMessage(message, e, true); 848 } 849 } 850 851 private Message handleUnknownClientMessage(Message message) { 852 ErrorMessage reply = new ErrorMessage(message, true); 853 reply.setFaultCode("Server.Call.UnknownClient"); 854 reply.setFaultString("Unknown client"); 855 return reply; 856 } 857 858 /////////////////////////////////////////////////////////////////////////// 859 // Utilities. 860 861 private ErrorMessage getInvalidDestinationError(Message message) { 862 863 String messageType = message.getClass().getName(); 864 if (message instanceof CommandMessage) 865 messageType += '[' + ((CommandMessage)message).getMessageRefType() + ']'; 866 867 ErrorMessage reply = new ErrorMessage(message, true); 868 reply.setFaultCode("Server.Messaging.InvalidDestination"); 869 reply.setFaultString( 870 "No configured destination for id: " + message.getDestination() + 871 " and message type: " + messageType 872 ); 873 return reply; 874 } 875 876 private static class ServerChannel extends AbstractChannel implements Serializable { 877 878 private static final long serialVersionUID = 1L; 879 880 public ServerChannel(Gravity gravity, String channelId, ChannelFactory<ServerChannel> factory, String clientType) { 881 super(gravity, channelId, factory, clientType); 882 } 883 884 @Override 885 public Gravity getGravity() { 886 return gravity; 887 } 888 889 public void close() { 890 } 891 892 @Override 893 public void receive(AsyncMessage message) throws MessageReceivingException { 894 } 895 896 @Override 897 protected boolean hasAsyncHttpContext() { 898 return false; 899 } 900 901 @Override 902 protected AsyncHttpContext acquireAsyncHttpContext() { 903 return null; 904 } 905 906 @Override 907 protected void releaseAsyncHttpContext(AsyncHttpContext context) { 908 } 909 } 910}