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 * *** 008 * 009 * Community License: GPL 3.0 010 * 011 * This file is free software: you can redistribute it and/or modify 012 * it under the terms of the GNU General Public License as published 013 * by the Free Software Foundation, either version 3 of the License, 014 * or (at your option) any later version. 015 * 016 * This file is distributed in the hope that it will be useful, but 017 * WITHOUT ANY WARRANTY; without even the implied warranty of 018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 019 * GNU General Public License for more details. 020 * 021 * You should have received a copy of the GNU General Public License 022 * along with this program. If not, see <http://www.gnu.org/licenses/>. 023 * 024 * *** 025 * 026 * Available Commercial License: GraniteDS SLA 1.0 027 * 028 * This is the appropriate option if you are creating proprietary 029 * applications and you are not prepared to distribute and share the 030 * source code of your application under the GPL v3 license. 031 * 032 * Please visit http://www.granitedataservices.com/license for more 033 * details. 034 */ 035package org.granite.client.tide.server; 036 037import java.lang.reflect.Constructor; 038import java.lang.reflect.Type; 039import java.nio.charset.Charset; 040import java.util.ArrayList; 041import java.util.Date; 042import java.util.HashMap; 043import java.util.HashSet; 044import java.util.List; 045import java.util.Map; 046import java.util.Observable; 047import java.util.Observer; 048import java.util.Set; 049import java.util.Timer; 050import java.util.TimerTask; 051import java.util.concurrent.Executors; 052import java.util.concurrent.ScheduledExecutorService; 053import java.util.concurrent.ScheduledFuture; 054import java.util.concurrent.TimeUnit; 055 056import javax.annotation.PostConstruct; 057import javax.annotation.PreDestroy; 058import javax.inject.Named; 059 060import org.granite.client.configuration.Configuration; 061import org.granite.client.messaging.ClientAliasRegistry; 062import org.granite.client.messaging.Consumer; 063import org.granite.client.messaging.Producer; 064import org.granite.client.messaging.RemoteService; 065import org.granite.client.messaging.ResultFaultIssuesResponseListener; 066import org.granite.client.messaging.ServerApp; 067import org.granite.client.messaging.TopicAgent; 068import org.granite.client.messaging.channel.*; 069import org.granite.client.messaging.codec.MessagingCodec.ClientType; 070import org.granite.client.messaging.events.Event; 071import org.granite.client.messaging.events.FaultEvent; 072import org.granite.client.messaging.events.IncomingMessageEvent; 073import org.granite.client.messaging.events.IssueEvent; 074import org.granite.client.messaging.events.ResultEvent; 075import org.granite.client.messaging.messages.responses.FaultMessage; 076import org.granite.client.messaging.messages.responses.FaultMessage.Code; 077import org.granite.client.messaging.messages.responses.ResultMessage; 078import org.granite.client.messaging.transport.Transport; 079import org.granite.client.messaging.transport.TransportException; 080import org.granite.client.messaging.transport.TransportStatusHandler; 081import org.granite.client.platform.Platform; 082import org.granite.client.tide.ApplicationConfigurable; 083import org.granite.client.tide.Context; 084import org.granite.client.tide.ContextAware; 085import org.granite.client.tide.Identity; 086import org.granite.client.tide.impl.FaultHandler; 087import org.granite.client.tide.impl.ResultHandler; 088import org.granite.client.validation.InvalidValue; 089import org.granite.config.GraniteConfig; 090import org.granite.logging.Logger; 091import org.granite.util.ContentType; 092 093 094/** 095 * ServerSession provides an API to manage all communications with the server 096 * It can be setup as a managed bean with Spring or CDI or created manually and attached to a Tide context 097 * 098 * <pre> 099 * {@code 100 * ServerSession serverSession = tideContext.set(new ServerSession("/myapp", "localhost", 8080)); 101 * } 102 * </pre> 103 * 104 * @author William DRAI 105 */ 106@ApplicationConfigurable 107@Named 108public class ServerSession implements ContextAware { 109 110 private static Logger log = Logger.getLogger(ServerSession.class); 111 112 public static final String SERVER_TIME_TAG = "org.granite.time"; 113 public static final String SESSION_ID_TAG = "org.granite.sessionId"; 114 public static final String SESSION_EXP_TAG = "org.granite.sessionExp"; 115 116 public static final String CONTEXT_RESULT = "org.granite.tide.result"; 117 public static final String CONTEXT_FAULT = "org.granite.tide.fault"; 118 119 public static final String LOGIN = "org.granite.client.tide.login"; 120 public static final String LOGOUT = "org.granite.client.tide.logout"; 121 public static final String SESSION_EXPIRED = "org.granite.client.tide.sessionExpired"; 122 123 124 private ServerApp serverApp; 125 private ContentType contentType = ContentType.JMF_AMF; 126 private Class<? extends ChannelFactory> channelFactoryClass = null; 127 private Transport remotingTransport = null; 128 private Transport messagingTransport = null; 129 private Map<String, Transport> messagingTransports = new HashMap<String, Transport>(); 130 131 private Context context = null; 132 133 private Status status = new DefaultStatus(); 134 135 private String sessionId = null; 136 137 private LogoutState logoutState = new LogoutState(); 138 139 private String destination = "server"; 140 private Object platformContext = null; 141 private ChannelBuilder defaultChannelBuilder = null; 142 private String defaultChannelType = null; 143 private ChannelFactory channelFactory; 144 private RemotingChannel remotingChannel = null; 145 private Map<String, MessagingChannel> messagingChannelsByType = new HashMap<String, MessagingChannel>(); 146 protected Map<String, RemoteService> remoteServices = new HashMap<String, RemoteService>(); 147 protected Map<String, TopicAgent> topicAgents = new HashMap<String, TopicAgent>(); 148 private Set<String> packageNames = new HashSet<String>(); 149 150 151 public ServerSession() throws Exception { 152 // Used for testing 153 } 154 155 /** 156 * Create a server session for the specified context root and server 157 * @param contextRoot context root 158 * @param serverName server host name 159 * @param serverPort server port 160 * @throws Exception 161 */ 162 public ServerSession(String contextRoot, String serverName, int serverPort) { 163 this(contextRoot, false, serverName, serverPort); 164 } 165 166 /** 167 * Create a server session for the specified context root and server 168 * @param contextRoot context root 169 * @param secure server app is secured (https/wss/...) 170 * @param serverName server host name 171 * @param serverPort server port 172 * @throws Exception 173 */ 174 public ServerSession(String contextRoot, boolean secure, String serverName, int serverPort) { 175 this.serverApp = new ServerApp(contextRoot, secure, serverName, serverPort); 176 } 177 178 /** 179 * Create a server session for the specified server app 180 * @param serverApp server application definition 181 */ 182 public ServerSession(ServerApp serverApp) { 183 this.serverApp = serverApp; 184 } 185 186 /** 187 * Serialization type (default is JMF) 188 * @return content type 189 */ 190 public ContentType getContentType() { 191 return contentType; 192 } 193 194 /** 195 * Set the serialization type 196 * @param contentType serialization type 197 */ 198 public void setContentType(ContentType contentType) { 199 if (contentType == null) 200 throw new NullPointerException("contentType cannot be null"); 201 this.contentType = contentType; 202 } 203 204 /** 205 * Set the default channel builder 206 * @param channelBuilder channel builder 207 */ 208 public void setDefaultChannelBuilder(ChannelBuilder channelBuilder) { 209 this.defaultChannelBuilder = channelBuilder; 210 if (channelFactory != null) 211 channelFactory.setDefaultChannelBuilder(defaultChannelBuilder); 212 } 213 214 /** 215 * Set the default channel type for messaging 216 * @param channelType channel type 217 */ 218 public void setDefaultChannelType(String channelType) { 219 this.defaultChannelType = channelType; 220 if (channelFactory != null) 221 channelFactory.setDefaultChannelType(defaultChannelType); 222 } 223 224 /** 225 * Set a custom channel factory class 226 * @param channelFactoryClass channel factory class 227 */ 228 public void setChannelFactoryClass(Class<? extends ChannelFactory> channelFactoryClass) { 229 this.channelFactoryClass = channelFactoryClass; 230 } 231 232 /** 233 * Set the server app for this server session 234 * @param serverApp server application definition 235 */ 236 public void setServerApp(ServerApp serverApp) { 237 this.serverApp = serverApp; 238 } 239 240 /** 241 * Set the Tide context for this server session 242 * (internal method, should be set by the context itself) 243 * @param context Tide context 244 * @see org.granite.client.tide.ContextAware 245 */ 246 public void setContext(Context context) { 247 this.context = context; 248 } 249 250 /** 251 * Current Tide context 252 * @return Tide context 253 */ 254 public Context getContext() { 255 return this.context; 256 } 257 258 /** 259 * Set the platform context for this server session 260 * @param platformContext 261 */ 262 public void setPlatformContext(Object platformContext) { 263 this.platformContext = platformContext; 264 } 265 266 /** 267 * Set an implementation of the Status interface to be notified of server related information 268 * @param status status 269 */ 270 public void setStatus(Status status) { 271 this.status = status; 272 } 273 274 /** 275 * Status implementation 276 * @return status 277 */ 278 public Status getStatus() { 279 return status; 280 } 281 282 /** 283 * Set the remoting transport 284 * @param transport remoting transport 285 */ 286 public void setRemotingTransport(Transport transport) { 287 this.remotingTransport = transport; 288 } 289 290 /** 291 * Set the default messaging transport 292 * @param transport messaging transport 293 */ 294 public void setMessagingTransport(Transport transport) { 295 this.messagingTransport = transport; 296 } 297 298 /** 299 * Set the messaging transport for the specified channel type 300 * @param channelType channel type 301 */ 302 public void setMessagingTransport(String channelType, Transport transport) { 303 this.messagingTransports.put(channelType, transport); 304 } 305 306 /** 307 * Add a package name to scan for remote aliases 308 * @param packageName package name 309 */ 310 public void addRemoteAliasPackage(String packageName) { 311 this.packageNames.add(packageName); 312 } 313 314 /** 315 * Reset all package names to scan for remote aliases 316 * @param packageNames package names 317 */ 318 public void setRemoteAliasPackages(Set<String> packageNames) { 319 this.packageNames.clear(); 320 this.packageNames.addAll(packageNames); 321 } 322 323 private GraniteConfig graniteConfig = null; 324 325 public Object convert(Object value, Type expectedType) { 326 if (contentType == ContentType.JMF_AMF || graniteConfig == null) 327 return value; 328 return graniteConfig.getConverters().convert(value, expectedType); 329 } 330 331 /** 332 * Configure and start the server session 333 * @throws Exception 334 */ 335 public void start() throws Exception { 336 if (channelFactory != null) // Already started 337 return; 338 339 ClientAliasRegistry aliasRegistry = new ClientAliasRegistry(); 340 aliasRegistry.registerAlias(InvalidValue.class); 341 342 if (channelFactoryClass != null) { 343 Constructor<? extends ChannelFactory> constructor = null; 344 try { 345 constructor = channelFactoryClass.getConstructor(Object.class, Configuration.class); 346 Configuration configuration = Platform.getInstance().newConfiguration(); 347 configuration.setClientType(ClientType.JAVA); 348 configuration.load(); 349 graniteConfig = configuration.getGraniteConfig(); 350 channelFactory = constructor.newInstance(platformContext, configuration); 351 } 352 catch (NoSuchMethodException e) { 353 constructor = channelFactoryClass.getConstructor(Object.class); 354 channelFactory = constructor.newInstance(platformContext); 355 } 356 } 357 else if (contentType == ContentType.JMF_AMF) 358 channelFactory = new JMFChannelFactory(platformContext); 359 else { 360 Configuration configuration = Platform.getInstance().newConfiguration(); 361 configuration.setClientType(ClientType.JAVA); 362 configuration.load(); 363 graniteConfig = configuration.getGraniteConfig(); 364 channelFactory = new AMFChannelFactory(platformContext, configuration); 365 } 366 channelFactory.setAliasRegistry(aliasRegistry); 367 channelFactory.setScanPackageNames(packageNames); 368 369 if (defaultChannelType != null) 370 channelFactory.setDefaultChannelType(defaultChannelType); 371 372 if (remotingTransport != null) { 373 channelFactory.setRemotingTransport(remotingTransport); 374 remotingTransport.setStatusHandler(statusHandler); 375 } 376 if (messagingTransport != null) { 377 channelFactory.setMessagingTransport(messagingTransport); 378 messagingTransport.setStatusHandler(statusHandler); 379 } 380 for (Map.Entry<String, Transport> me : messagingTransports.entrySet()) { 381 channelFactory.setMessagingTransport(me.getKey(), me.getValue()); 382 me.getValue().setStatusHandler(statusHandler); 383 } 384 if (defaultChannelBuilder != null) 385 channelFactory.setDefaultChannelBuilder(defaultChannelBuilder); 386 387 if (defaultTimeToLive >= 0) 388 channelFactory.setDefaultTimeToLive(defaultTimeToLive); 389 390 channelFactory.start(); 391 392 remotingChannel = channelFactory.newRemotingChannel("graniteamf", serverApp, 1); 393 394 sessionExpirationTimer = Executors.newSingleThreadScheduledExecutor(); 395 } 396 397 /** 398 * Stop the server session and cleanup resources 399 * @throws Exception 400 */ 401 public void stop() throws Exception { 402 try { 403 if (sessionExpirationFuture != null) { 404 sessionExpirationFuture.cancel(false); 405 sessionExpirationFuture = null; 406 } 407 if (sessionExpirationTimer != null) { 408 sessionExpirationTimer.shutdownNow(); 409 sessionExpirationTimer = null; 410 } 411 } 412 finally { 413 if (channelFactory != null) { 414 channelFactory.stop(); 415 channelFactory = null; 416 } 417 418 remotingChannel = null; 419 messagingChannelsByType.clear(); 420 } 421 } 422 423 /** 424 * Internal SPI to define how remoting/messaging elements are created 425 */ 426 public static interface ServiceFactory { 427 428 /** 429 * Create a remote service for the specified channel and destination 430 * @param remotingChannel channel 431 * @param destination destination name 432 * @return remote service 433 */ 434 public RemoteService newRemoteService(RemotingChannel remotingChannel, String destination); 435 436 /** 437 * Create a producer 438 * @param messagingChannel channel 439 * @param destination destination name 440 * @param topic subtopic 441 * @return producer 442 */ 443 public Producer newProducer(MessagingChannel messagingChannel, String destination, String topic); 444 445 /** 446 * Create a consumer 447 * @param messagingChannel channel 448 * @param destination destination name 449 * @param topic subtopic 450 * @return consumer 451 */ 452 public Consumer newConsumer(MessagingChannel messagingChannel, String destination, String topic); 453 } 454 455 private static class DefaultServiceFactory implements ServiceFactory { 456 457 @Override 458 public RemoteService newRemoteService(RemotingChannel remotingChannel, String destination) { 459 return new RemoteService(remotingChannel, destination); 460 } 461 462 @Override 463 public Producer newProducer(MessagingChannel messagingChannel, String destination, String topic) { 464 return new Producer(messagingChannel, destination, topic); 465 } 466 467 @Override 468 public Consumer newConsumer(MessagingChannel messagingChannel, String destination, String topic) { 469 return new Consumer(messagingChannel, destination, topic); 470 } 471 } 472 473 private ServiceFactory serviceFactory = new DefaultServiceFactory(); 474 475 /** 476 * Set a custom service factory to create producer/consumers and remote services 477 * @param serviceFactory service factory 478 */ 479 public void setServiceFactory(ServiceFactory serviceFactory) { 480 this.serviceFactory = serviceFactory; 481 } 482 483 /** 484 * Returns remote service for the internal destination 485 * Should generally not be used except for very advanced use, use {@link Component} instead 486 * @return internal remote service 487 */ 488 public RemoteService getRemoteService() { 489 return getRemoteService(destination); 490 } 491 492 /** 493 * Returns a remote service for the specified destination 494 * Should generally not be used except for very advanced use, use {@link Component} instead 495 * @param destination destination name 496 * @return remote service 497 */ 498 public synchronized RemoteService getRemoteService(String destination) { 499 if (remotingChannel == null) 500 throw new IllegalStateException("Channel not defined for server session"); 501 502 RemoteService remoteService = remoteServices.get(destination); 503 if (remoteService == null) { 504 remoteService = serviceFactory.newRemoteService(remotingChannel, destination); 505 remoteServices.put(destination, remoteService); 506 } 507 return remoteService; 508 } 509 510 /** 511 * Return a messaging channel for the specified type 512 * @param channelType channel type 513 * @return messaging channel 514 * @see org.granite.client.messaging.channel.ChannelType 515 */ 516 public MessagingChannel getMessagingChannel(String channelType) { 517 MessagingChannel messagingChannel = messagingChannelsByType.get(channelType); 518 if (messagingChannel != null) 519 return messagingChannel; 520 521 messagingChannel = channelFactory.newMessagingChannel(channelType, channelType + "amf", serverApp); 522 return messagingChannel; 523 } 524 525 /** 526 * Build a consumer for the specified channel type and destination 527 * @param destination destination name 528 * @param topic subtopic 529 * @param channelType channel type 530 * @return consumer 531 */ 532 public synchronized Consumer getConsumer(String destination, String topic, String channelType) { 533 if (channelType == null) 534 channelType = channelFactory.getDefaultChannelType(); 535 536 MessagingChannel messagingChannel = getMessagingChannel(channelType); 537 if (messagingChannel == null) 538 throw new IllegalStateException("Channel not defined in server session for type " + channelType + ""); 539 540 String key = "C:" + destination + '@' + topic; 541 TopicAgent consumer = topicAgents.get(key); 542 if (consumer == null) { 543 consumer = serviceFactory.newConsumer(messagingChannel, destination, topic); 544 topicAgents.put(key, consumer); 545 } 546 return consumer instanceof Consumer ? (Consumer)consumer : null; 547 } 548 549 /** 550 * Build a consumer for the default channel type and destination 551 * @param destination destination name 552 * @param topic subtopic 553 * @return consumer 554 */ 555 public synchronized Consumer getConsumer(String destination, String topic) { 556 return getConsumer(destination, topic, channelFactory.getDefaultChannelType()); 557 } 558 559 /** 560 * Build a producer for the specified channel type and destination 561 * @param destination destination name 562 * @param topic subtopic 563 * @param channelType channel type 564 * @return producer 565 */ 566 public synchronized Producer getProducer(String destination, String topic, String channelType) { 567 if (channelType == null) 568 channelType = channelFactory.getDefaultChannelType(); 569 570 MessagingChannel messagingChannel = getMessagingChannel(channelType); 571 if (messagingChannel == null) 572 throw new IllegalStateException("Channel not defined for server session"); 573 574 String key = "P:" + destination + '@' + topic; 575 TopicAgent producer = topicAgents.get(key); 576 if (producer == null) { 577 producer = serviceFactory.newProducer(messagingChannel, destination, topic); 578 topicAgents.put(key, producer); 579 } 580 return producer instanceof Producer ? (Producer)producer : null; 581 } 582 583 /** 584 * Build a producer for the default channel type and destination 585 * @param destination destination name 586 * @param topic subtopic 587 * @return producer 588 */ 589 public synchronized Producer getProducer(String destination, String topic) { 590 return getProducer(destination, topic, channelFactory.getDefaultChannelType()); 591 } 592 593 /** 594 * Current remote session id 595 * @return session id 596 */ 597 public String getSessionId() { 598 return sessionId; 599 } 600 601 /** 602 * Is logging out ? 603 * @return true if logout in progress 604 */ 605 public boolean isLogoutInProgress() { 606 return logoutState.logoutInProgress; 607 } 608 609 private ScheduledExecutorService sessionExpirationTimer = null; 610 private ScheduledFuture<?> sessionExpirationFuture = null; 611 612 private Runnable sessionExpirationTask = new Runnable() { 613 @Override 614 public void run() { 615 Identity identity = context.byType(Identity.class); 616 identity.checkLoggedIn(null); 617 } 618 }; 619 620 private void rescheduleSessionExpirationTask(long serverTime, int sessionExpirationDelay) { 621 Identity identity = context.byType(Identity.class); 622 if (identity == null || !identity.isLoggedIn()) // No session expiration tracking if user not logged in 623 return; 624 625 long clientOffset = serverTime - new Date().getTime(); 626 sessionExpirationFuture = sessionExpirationTimer.schedule(sessionExpirationTask, clientOffset + sessionExpirationDelay*1000L + 1500L, TimeUnit.MILLISECONDS); 627 } 628 629 /** 630 * Callback called when a remoting response is received 631 * @param event event 632 */ 633 public void onResultEvent(Event event) { 634 if (sessionExpirationFuture != null) 635 sessionExpirationFuture.cancel(false); 636 637 String oldSessionId = sessionId; 638 639 if (event instanceof ResultEvent) { 640 ResultMessage message = ((ResultEvent)event).getMessage(); 641 sessionId = (String)message.getHeader(SESSION_ID_TAG); 642 if (sessionId != null) { 643 long serverTime = (Long)message.getHeader(SERVER_TIME_TAG); 644 int sessionExpirationDelay = (Integer)message.getHeader(SESSION_EXP_TAG); 645 rescheduleSessionExpirationTask(serverTime, sessionExpirationDelay); 646 } 647 } 648 else if (event instanceof IncomingMessageEvent<?>) 649 sessionId = (String)((IncomingMessageEvent<?>)event).getMessage().getHeader(SESSION_ID_TAG); 650 651 if (sessionId == null || !sessionId.equals(oldSessionId)) 652 log.info("Received new sessionId %s", sessionId); 653 654 if (oldSessionId != null || sessionId != null) { 655 for (MessagingChannel messagingChannel : messagingChannelsByType.values()) 656 messagingChannel.setSessionId(sessionId); 657 } 658 659 status.setConnected(true); 660 } 661 662 /** 663 * Callback called when a remoting fault is received 664 * @param event fault event 665 * @param emsg fault message 666 */ 667 public void onFaultEvent(FaultEvent event, FaultMessage emsg) { 668 if (sessionExpirationFuture != null) 669 sessionExpirationFuture.cancel(false); 670 671 String oldSessionId = sessionId; 672 673 sessionId = (String)event.getMessage().getHeader(SESSION_ID_TAG); 674 if (sessionId != null) { 675 long serverTime = (Long)event.getMessage().getHeader(SERVER_TIME_TAG); 676 int sessionExpirationDelay = (Integer)event.getMessage().getHeader(SESSION_EXP_TAG); 677 rescheduleSessionExpirationTask(serverTime, sessionExpirationDelay); 678 } 679 680 if (sessionId == null || !sessionId.equals(oldSessionId)) 681 log.info("Received new sessionId %s", sessionId); 682 683 if (oldSessionId != null || sessionId != null) { 684 for (MessagingChannel messagingChannel : messagingChannelsByType.values()) 685 messagingChannel.setSessionId(sessionId); 686 } 687 688 if (emsg != null && emsg.getCode().equals(Code.SERVER_CALL_FAILED)) 689 status.setConnected(false); 690 } 691 692 /** 693 * Callback called when a remoting failure is received 694 * @param event failure event 695 */ 696 public void onIssueEvent(IssueEvent event) { 697 status.setConnected(false); 698 } 699 700 private final TransportStatusHandler statusHandler = new TransportStatusHandler() { 701 702 private int busyCount = 0; 703 704 @Override 705 public void handleIO(boolean active) { 706 if (active) 707 busyCount++; 708 else 709 busyCount--; 710 status.setBusy(busyCount > 0); 711 notifyIOListeners(status.isBusy()); 712 } 713 714 @Override 715 public void handleException(TransportException e) { 716 log.warn(e, "Transport failed"); 717 notifyExceptionListeners(e); 718 } 719 }; 720 721 /** 722 * Current remoting transport 723 * @return remoting transport 724 */ 725 public Transport getRemotingTransport() { 726 return channelFactory != null ? channelFactory.getRemotingTransport() : null; 727 } 728 729 /** 730 * Current messaging transport 731 * @return messaging transport 732 */ 733 public Transport getMessagingTransport() { 734 return channelFactory != null ? channelFactory.getMessagingTransport() : null; 735 } 736 737 738 /** 739 * Implementation of login 740 * Should not be called directly, called by {@link org.granite.client.tide.Identity#login} 741 * 742 * @param username user name 743 * @param password password 744 */ 745 public void login(String username, String password) { 746 remotingChannel.setCredentials(new UsernamePasswordCredentials(username, password)); 747 for (MessagingChannel messagingChannel : messagingChannelsByType.values()) 748 messagingChannel.setCredentials(new UsernamePasswordCredentials(username, password)); 749 } 750 751 /** 752 * Implementation of login using a specific charset for username/password encoding 753 * Should not be called directly, called by {@link org.granite.client.tide.Identity#login} 754 * 755 * @param username user name 756 * @param password password 757 * @param charset charset used for encoding 758 */ 759 public void login(String username, String password, Charset charset) { 760 remotingChannel.setCredentials(new UsernamePasswordCredentials(username, password, charset)); 761 for (MessagingChannel messagingChannel : messagingChannelsByType.values()) 762 messagingChannel.setCredentials(new UsernamePasswordCredentials(username, password, charset)); 763 } 764 765 /** 766 * Called by {@link org.granite.client.tide.Identity} after login has succeeded 767 */ 768 public void afterLogin() { 769 log.info("Application session authenticated"); 770 771 context.getEventBus().raiseEvent(context, LOGIN); 772 } 773 774 /** 775 * Called by {@link org.granite.client.tide.Identity} after session has expired 776 */ 777 public void sessionExpired() { 778 log.info("Application session expired"); 779 780 if (remotingChannel.isAuthenticated()) 781 remotingChannel.logout(false); 782 for (MessagingChannel messagingChannel : messagingChannelsByType.values()) { 783 if (messagingChannel.isAuthenticated()) 784 messagingChannel.logout(false); 785 } 786 787 sessionId = null; 788 if (remotingChannel instanceof SessionAwareChannel) 789 ((SessionAwareChannel)remotingChannel).setSessionId(null); 790 for (MessagingChannel messagingChannel : messagingChannelsByType.values()) 791 messagingChannel.setSessionId(null); 792 793 logoutState.sessionExpired(); 794 795 context.getEventBus().raiseEvent(context, SESSION_EXPIRED); 796 context.getEventBus().raiseEvent(context, LOGOUT); 797 } 798 799 /** 800 * Implementation of logout 801 * Should not be called directly, called by {@link org.granite.client.tide.Identity#logout} 802 * 803 * @param logoutObserver observer that will be notified of logout result 804 */ 805 public void logout(final Observer logoutObserver) { 806 if (sessionExpirationFuture != null) { 807 sessionExpirationFuture.cancel(false); 808 sessionExpirationFuture = null; 809 } 810 811 logoutState.logout(logoutObserver, new TimerTask() { 812 @Override 813 public void run() { 814 log.info("Force session logout"); 815 logoutState.logout(logoutObserver); 816 tryLogout(); 817 } 818 }); 819 820 context.getEventBus().raiseEvent(context, LOGOUT); 821 822 tryLogout(); 823 } 824 825 /** 826 * Notify the framework that it should wait for a async operation before effectively logging out. 827 * Only if a logout has been requested. 828 */ 829 public void checkWaitForLogout() { 830 logoutState.checkWait(); 831 } 832 833 /** 834 * Called after all remote operations on a component are finished. 835 * The actual logout is done when all remote operations on all components have been notified as finished. 836 */ 837 public void tryLogout() { 838 if (logoutState.stillWaiting()) 839 return; 840 841 if (logoutState.isSessionExpired()) { // Don't remotely logout again if we detected a session expired 842 logoutState.loggedOut(null); 843 return; 844 } 845 846 if (remotingChannel.isAuthenticated()) { 847 remotingChannel.logout(new ResultFaultIssuesResponseListener() { 848 @Override 849 public void onResult(final ResultEvent event) { 850 context.callLater(new Runnable() { 851 public void run() { 852 log.info("Application session logged out"); 853 854 new ResultHandler<Object>(ServerSession.this, null, "logout").handleResult(context, null, null, null); 855 context.getContextManager().destroyContexts(); 856 857 logoutState.loggedOut(new TideResultEvent<Object>(context, ServerSession.this, null, event.getResult())); 858 } 859 }); 860 } 861 862 @Override 863 public void onFault(final FaultEvent event) { 864 context.callLater(new Runnable() { 865 public void run() { 866 log.error("Could not log out %s", event.getDescription()); 867 868 new FaultHandler<Object>(ServerSession.this, null, "logout").handleFault(context, event.getMessage()); 869 870 Fault fault = new Fault(event.getCode(), event.getDescription(), event.getDetails()); 871 fault.setContent(event.getMessage()); 872 fault.setCause(event.getCause()); 873 logoutState.loggedOut(new TideFaultEvent(context, ServerSession.this, null, fault, event.getExtended())); 874 } 875 }); 876 } 877 878 @Override 879 public void onIssue(final IssueEvent event) { 880 context.callLater(new Runnable() { 881 public void run() { 882 log.error("Could not logout %s", event.getType()); 883 884 new FaultHandler<Object>(ServerSession.this, null, "logout").handleFault(context, null); 885 886 Fault fault = new Fault(Code.SERVER_CALL_FAILED, event.getType().name(), ""); 887 logoutState.loggedOut(new TideFaultEvent(context, ServerSession.this, null, fault, null)); 888 } 889 }); 890 } 891 }); 892 } 893 894 for (MessagingChannel messagingChannel : messagingChannelsByType.values()) { 895 if (messagingChannel != remotingChannel && messagingChannel.isAuthenticated()) 896 messagingChannel.logout(); 897 } 898 } 899 900 901 private static class LogoutState extends Observable { 902 903 private boolean logoutInProgress = false; 904 private int waitForLogout = 0; 905 private boolean sessionExpired = false; 906 private Timer logoutTimeout = null; 907 908 public synchronized void logout(Observer logoutObserver, TimerTask forceLogout) { 909 logout(logoutObserver); 910 logoutTimeout = new Timer(true); 911 logoutTimeout.schedule(forceLogout, 1000L); 912 } 913 914 public synchronized void logout(Observer logoutObserver) { 915 if (logoutObserver != null) 916 addObserver(logoutObserver); 917 if (!logoutInProgress) { 918 logoutInProgress = true; 919 waitForLogout = 1; 920 } 921 } 922 923 public synchronized void checkWait() { 924 if (logoutInProgress) 925 waitForLogout++; 926 } 927 928 public synchronized boolean stillWaiting() { 929 if (sessionExpired) 930 return false; 931 932 if (!logoutInProgress) 933 return true; 934 935 waitForLogout--; 936 if (waitForLogout > 0) 937 return true; 938 939 return false; 940 } 941 942 public boolean isSessionExpired() { 943 return sessionExpired; 944 } 945 946 public synchronized void loggedOut(TideRpcEvent event) { 947 if (logoutTimeout != null) { 948 logoutTimeout.cancel(); 949 logoutTimeout = null; 950 } 951 952 if (event != null) { 953 setChanged(); 954 notifyObservers(event); 955 deleteObservers(); 956 } 957 958 logoutInProgress = false; 959 waitForLogout = 0; 960 sessionExpired = false; 961 } 962 963 public synchronized void sessionExpired() { 964 logoutInProgress = false; 965 waitForLogout = 0; 966 sessionExpired = true; 967 } 968 } 969 970 971 /** 972 * Status notified of network related events 973 */ 974 public interface Status { 975 976 /** 977 * Network I/O busy 978 * @return true is busy 979 */ 980 public boolean isBusy(); 981 982 /** 983 * Set I/O busy, called by transport listeners 984 * @param busy true if busy 985 */ 986 public void setBusy(boolean busy); 987 988 /** 989 * Network connected 990 * @return true if connected 991 */ 992 public boolean isConnected(); 993 994 /** 995 * Set connected state, called by transport listeners 996 * @param connected true if connected 997 */ 998 public void setConnected(boolean connected); 999 1000 /** 1001 * Busy cursor enabled ? 1002 * @return true if busy cursor enabled 1003 */ 1004 public boolean isShowBusyCursor(); 1005 1006 /** 1007 * Enable/disable busy cursor 1008 * @param showBusyCursor true if enabled 1009 */ 1010 public void setShowBusyCursor(boolean showBusyCursor); 1011 } 1012 1013 1014 public static class DefaultStatus implements Status { 1015 1016 private boolean showBusyCursor = true; 1017 1018 private boolean connected = false; 1019 private boolean busy = false; 1020 1021 @Override 1022 public boolean isBusy() { 1023 return busy; 1024 } 1025 1026 public void setBusy(boolean busy) { 1027 this.busy = busy; 1028 } 1029 1030 @Override 1031 public boolean isConnected() { 1032 return connected; 1033 } 1034 1035 public void setConnected(boolean connected) { 1036 this.connected = connected; 1037 } 1038 1039 @Override 1040 public boolean isShowBusyCursor() { 1041 return showBusyCursor; 1042 } 1043 1044 @Override 1045 public void setShowBusyCursor(boolean showBusyCursor) { 1046 this.showBusyCursor = showBusyCursor; 1047 } 1048 } 1049 1050 1051 private long defaultTimeToLive = -1; 1052 1053 /** 1054 * Set default time to live on all channels 1055 * @param timeToLive time to live in milliseconds 1056 */ 1057 public void setDefaultTimeToLive(long timeToLive) { 1058 defaultTimeToLive = timeToLive; 1059 1060 if (channelFactory != null) 1061 channelFactory.setDefaultTimeToLive(timeToLive); 1062 1063 for (MessagingChannel messagingChannel : messagingChannelsByType.values()) 1064 messagingChannel.setDefaultTimeToLive(timeToLive); 1065 1066 if (remotingChannel != null) 1067 remotingChannel.setDefaultTimeToLive(timeToLive); 1068 } 1069 1070 private List<TransportIOListener> transportIOListeners = new ArrayList<TransportIOListener>(); 1071 private List<TransportExceptionListener> transportExceptionListeners = new ArrayList<TransportExceptionListener>(); 1072 1073 public void addListener(TransportIOListener listener) { 1074 transportIOListeners.add(listener); 1075 } 1076 public void removeListener(TransportIOListener listener) { 1077 transportIOListeners.remove(listener); 1078 } 1079 1080 public void addListener(TransportExceptionListener listener) { 1081 transportExceptionListeners.add(listener); 1082 } 1083 public void removeListener(TransportExceptionListener listener) { 1084 transportExceptionListeners.remove(listener); 1085 } 1086 1087 public interface TransportIOListener { 1088 public void handleIO(boolean busy); 1089 } 1090 1091 public interface TransportExceptionListener { 1092 public void handleException(TransportException e); 1093 } 1094 1095 public void notifyIOListeners(boolean busy) { 1096 for (TransportIOListener listener : transportIOListeners) 1097 listener.handleIO(busy); 1098 } 1099 1100 public void notifyExceptionListeners(TransportException e) { 1101 for (TransportExceptionListener listener : transportExceptionListeners) 1102 listener.handleException(e); 1103 } 1104}