blob: a8a51749bbfa8d2ef116f39a40bc23602cdea681 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2015 Innoopract Informationssysteme GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
* EclipseSource - ongoing development
******************************************************************************/
package org.eclipse.rap.rwt.internal.serverpush;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.eclipse.rap.rwt.SingletonUtil;
import org.eclipse.rap.rwt.internal.service.ContextProvider;
import org.eclipse.rap.rwt.internal.service.ServiceStore;
import org.eclipse.rap.rwt.internal.util.SerializableLock;
import org.eclipse.rap.rwt.service.UISession;
import org.eclipse.rap.rwt.service.UISessionEvent;
import org.eclipse.rap.rwt.service.UISessionListener;
import org.eclipse.swt.internal.SerializableCompatibility;
public final class ServerPushManager implements SerializableCompatibility {
private static final int DEFAULT_REQUEST_CHECK_INTERVAL = 30000;
private static final String FORCE_PUSH = ServerPushManager.class.getName() + "#forcePush";
private final ServerPushActivationTracker serverPushActivationTracker;
private final SerializableLock lock;
// Flag that indicates whether a request is processed. In that case no
// notifications are sent to the client.
private boolean uiThreadRunning;
// indicates whether the display has runnables to execute
private boolean hasRunnables;
private int requestCheckInterval;
private transient ServerPushRequestTracker serverPushRequestTracker;
private ServerPushManager() {
lock = new SerializableLock();
serverPushActivationTracker = new ServerPushActivationTracker();
uiThreadRunning = false;
requestCheckInterval = DEFAULT_REQUEST_CHECK_INTERVAL;
serverPushRequestTracker = new ServerPushRequestTracker();
}
public static ServerPushManager getInstance() {
return SingletonUtil.getSessionInstance( ServerPushManager.class );
}
public boolean isCallBackRequestBlocked() {
synchronized( lock ) {
return !serverPushRequestTracker.hasActive();
}
}
public void wakeClient() {
synchronized( lock ) {
if( !uiThreadRunning ) {
releaseBlockedRequest();
}
}
}
public void releaseBlockedRequest() {
synchronized( lock ) {
lock.notifyAll();
}
}
public void setHasRunnables( boolean hasRunnables ) {
synchronized( lock ) {
this.hasRunnables = hasRunnables;
}
ServiceStore serviceStore = ContextProvider.getServiceStore();
if( serviceStore != null && hasRunnables && isServerPushActive() ) {
serviceStore.setAttribute( FORCE_PUSH, Boolean.TRUE );
}
}
public void setRequestCheckInterval( int requestCheckInterval ) {
this.requestCheckInterval = requestCheckInterval;
}
public void notifyUIThreadStart() {
synchronized( lock ) {
uiThreadRunning = true;
}
}
public void notifyUIThreadEnd() {
synchronized( lock ) {
uiThreadRunning = false;
if( hasRunnables ) {
wakeClient();
}
}
}
public void activateServerPushFor( Object handle ) {
serverPushActivationTracker.activate( handle );
}
public void deactivateServerPushFor( Object handle ) {
serverPushActivationTracker.deactivate( handle );
if( !serverPushActivationTracker.isActive() ) {
releaseBlockedRequest();
}
}
public boolean hasRunnables() {
synchronized( lock ) {
return hasRunnables;
}
}
public boolean needsActivation() {
return isServerPushActive() || forceServerPushForPendingRunnables();
}
void processRequest( HttpServletResponse response ) {
synchronized( lock ) {
if( isCallBackRequestBlocked() ) {
releaseBlockedRequest();
}
if( mustBlockCallBackRequest() ) {
long requestStartTime = System.currentTimeMillis();
serverPushRequestTracker.activate( Thread.currentThread() );
TerminationListener listener = attachTerminationListener();
try {
boolean canRelease = false;
while( !canRelease ) {
lock.wait( requestCheckInterval );
canRelease = canReleaseBlockedRequest( response, requestStartTime );
}
} catch( @SuppressWarnings( "unused" ) InterruptedException ie ) {
Thread.interrupted(); // Reset interrupted state, see bug 300254
} finally {
listener.detach();
serverPushRequestTracker.deactivate( Thread.currentThread() );
}
}
}
}
private boolean canReleaseBlockedRequest( HttpServletResponse response, long requestStartTime ) {
boolean result = false;
if( !mustBlockCallBackRequest() ) {
result = true;
} else if( isSessionExpired( requestStartTime ) ) {
result = true;
} else if( !isConnectionAlive( response ) ) {
result = true;
} else if( !serverPushRequestTracker.isActive( Thread.currentThread() ) ) {
result = true;
}
return result;
}
boolean mustBlockCallBackRequest() {
return isServerPushActive() && !hasRunnables;
}
public boolean isServerPushActive() {
return serverPushActivationTracker.isActive();
}
private Object readResolve() {
serverPushRequestTracker = new ServerPushRequestTracker();
return this;
}
private static TerminationListener attachTerminationListener() {
UISession uiSession = ContextProvider.getUISession();
TerminationListener result = new TerminationListener( uiSession );
result.attach();
return result;
}
private static boolean isSessionExpired( long requestStartTime ) {
return isSessionExpired( requestStartTime, System.currentTimeMillis() );
}
static boolean isSessionExpired( long requestStartTime, long currentTime ) {
boolean result = false;
HttpSession httpSession = ContextProvider.getUISession().getHttpSession();
int maxInactiveInterval = httpSession.getMaxInactiveInterval();
if( maxInactiveInterval > 0 ) {
result = currentTime > requestStartTime + maxInactiveInterval * 1000;
}
return result;
}
private static boolean isConnectionAlive( HttpServletResponse response ) {
try {
PrintWriter writer = response.getWriter();
writer.write( " " );
return !writer.checkError();
} catch( @SuppressWarnings( "unused" ) IOException ioe ) {
return false;
}
}
private static boolean forceServerPushForPendingRunnables() {
boolean result = false;
ServiceStore serviceStore = ContextProvider.getServiceStore();
if( serviceStore != null ) {
result = Boolean.TRUE.equals( serviceStore.getAttribute( FORCE_PUSH ) );
}
return result;
}
private static class TerminationListener implements UISessionListener {
private transient final Thread currentThread;
private transient final UISession uiSession;
private TerminationListener( UISession uiSession ) {
this.uiSession = uiSession;
currentThread = Thread.currentThread();
}
public void attach() {
uiSession.addUISessionListener( this );
}
public void detach() {
uiSession.removeUISessionListener( this );
}
@Override
public void beforeDestroy( UISessionEvent event ) {
currentThread.interrupt();
}
}
}