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