blob: a6a229f39d0c8f3119e604c34ef8201cb52cb145 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 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.service;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
import static org.eclipse.rap.rwt.internal.protocol.ClientMessageConst.RWT_INITIALIZE;
import static org.eclipse.rap.rwt.internal.protocol.ClientMessageConst.SHUTDOWN;
import static org.eclipse.rap.rwt.internal.service.ContextProvider.getContext;
import static org.eclipse.rap.rwt.internal.service.ContextProvider.getServiceStore;
import static org.eclipse.rap.rwt.internal.service.ContextProvider.getUISession;
import static org.eclipse.rap.rwt.internal.util.HTTP.CHARSET_UTF_8;
import static org.eclipse.rap.rwt.internal.util.HTTP.CONTENT_TYPE_JSON;
import static org.eclipse.rap.rwt.internal.util.HTTP.METHOD_POST;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.rap.json.JsonObject;
import org.eclipse.rap.json.JsonValue;
import org.eclipse.rap.rwt.internal.lifecycle.RequestCounter;
import org.eclipse.rap.rwt.internal.protocol.ClientMessage;
import org.eclipse.rap.rwt.internal.protocol.ProtocolMessageWriter;
import org.eclipse.rap.rwt.internal.protocol.ProtocolUtil;
import org.eclipse.rap.rwt.internal.protocol.RequestMessage;
import org.eclipse.rap.rwt.internal.protocol.ResponseMessage;
import org.eclipse.rap.rwt.internal.remote.MessageChainReference;
import org.eclipse.rap.rwt.internal.util.HTTP;
import org.eclipse.rap.rwt.service.ServiceHandler;
import org.eclipse.rap.rwt.service.UISession;
public class LifeCycleServiceHandler implements ServiceHandler {
private static final String PROP_ERROR = "error";
private static final String PROP_REQUEST_COUNTER = "requestCounter";
private static final String ATTR_LAST_RESPONSE_MESSAGE
= LifeCycleServiceHandler.class.getName() + "#lastResponseMessage";
private static final String ATTR_SESSION_STARTED
= LifeCycleServiceHandler.class.getName() + "#isSessionStarted";
private final MessageChainReference messageChainReference;
private final StartupPage startupPage;
public LifeCycleServiceHandler( MessageChainReference messageChainReference,
StartupPage startupPage )
{
this.messageChainReference = messageChainReference;
this.startupPage = startupPage;
}
public void service( HttpServletRequest request, HttpServletResponse response )
throws IOException
{
// Do not use session store itself as a lock
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=372946
UISessionImpl uiSession = ( UISessionImpl )getUISession();
synchronized( uiSession.getRequestLock() ) {
synchronizedService( request, response );
}
}
void synchronizedService( HttpServletRequest request, HttpServletResponse response )
throws IOException
{
if( METHOD_POST.equals( request.getMethod() ) && isContentTypeValid( request ) ) {
handleUIRequest( request, response );
} else {
handleStartupRequest( request, response );
}
}
private void handleStartupRequest( HttpServletRequest request, HttpServletResponse response )
throws IOException
{
try {
sendStartupContent( request, response );
} finally {
// The GET request currently creates a dummy UI session needed for accessing the client
// information. It is not meant to be reused by other requests.
shutdownUISession();
}
}
private void handleUIRequest( HttpServletRequest request, HttpServletResponse response )
throws IOException
{
try {
processUIRequest( request, response );
} catch( IOException exception ) {
shutdownUISession();
throw exception;
} catch( RuntimeException exception ) {
shutdownUISession();
throw exception;
}
}
private void sendStartupContent( HttpServletRequest request, HttpServletResponse response )
throws IOException
{
String accept = request.getHeader( HTTP.HEADER_ACCEPT );
if( accept != null && accept.contains( HTTP.CONTENT_TYPE_JSON ) ) {
StartupJson.send( response );
} else {
startupPage.send( response );
}
}
private void processUIRequest( HttpServletRequest request, HttpServletResponse response )
throws IOException
{
RequestMessage requestMessage = readRequestMessage( request );
setJsonResponseHeaders( response );
if( isSessionShutdown( requestMessage ) ) {
shutdownUISession();
writeEmptyMessage( response );
} else if( isSessionTimeout( requestMessage ) ) {
writeSessionTimeoutError( response );
} else if( !isRequestCounterValid( requestMessage ) ) {
if( isDuplicateRequest( requestMessage ) ) {
writeBufferedResponse( response );
} else {
writeInvalidRequestCounterError( response );
}
} else {
if( isSessionRestart( requestMessage ) ) {
reinitializeUISession( request );
reinitializeServiceStore();
}
UrlParameters.merge( requestMessage );
ResponseMessage responseMessage = processMessage( requestMessage );
writeResponseMessage( responseMessage, response );
markSessionStarted();
}
}
private RequestMessage readRequestMessage( HttpServletRequest request ) {
try {
return new ClientMessage( JsonObject.readFrom( getReader( request ) ) );
} catch( IOException ioe ) {
throw new IllegalStateException( "Unable to read the json message", ioe );
}
}
/*
* Workaround for bug in certain servlet containers where the reader is sometimes empty.
* 411616: Application crash with very long messages
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=411616
*/
private static Reader getReader( HttpServletRequest request ) throws IOException {
String encoding = request.getCharacterEncoding();
if( encoding == null ) {
encoding = CHARSET_UTF_8;
}
return new InputStreamReader( request.getInputStream(), encoding );
}
private ResponseMessage processMessage( RequestMessage requestMessage ) {
return messageChainReference.get().handleMessage( requestMessage );
}
private static boolean isRequestCounterValid( RequestMessage requestMessage ) {
return hasInitializeParameter( requestMessage ) || hasValidRequestCounter( requestMessage );
}
static boolean hasValidRequestCounter( RequestMessage requestMessage ) {
int currentRequestId = RequestCounter.getInstance().currentRequestId();
JsonValue sentRequestId = requestMessage.getHead().get( PROP_REQUEST_COUNTER );
if( sentRequestId == null ) {
return currentRequestId == 0;
}
return currentRequestId == sentRequestId.asInt();
}
private static boolean isDuplicateRequest( RequestMessage requestMessage ) {
int currentRequestId = RequestCounter.getInstance().currentRequestId();
JsonValue sentRequestId = requestMessage.getHead().get( PROP_REQUEST_COUNTER );
return sentRequestId != null && sentRequestId.asInt() == currentRequestId - 1;
}
private static boolean isContentTypeValid( ServletRequest request ) {
String contentType = request.getContentType();
return contentType != null && contentType.startsWith( CONTENT_TYPE_JSON );
}
private static void shutdownUISession() {
UISessionImpl uiSession = ( UISessionImpl )getUISession();
uiSession.shutdown();
}
private static void writeInvalidRequestCounterError( HttpServletResponse response )
throws IOException
{
writeError( response, SC_PRECONDITION_FAILED, "invalid request counter" );
}
private static void writeSessionTimeoutError( HttpServletResponse response ) throws IOException {
writeError( response, SC_FORBIDDEN, "session timeout" );
}
private static void writeError( HttpServletResponse response,
int statusCode,
String errorType ) throws IOException
{
response.setStatus( statusCode );
ProtocolMessageWriter writer = new ProtocolMessageWriter();
writer.appendHead( PROP_ERROR, JsonValue.valueOf( errorType ) );
writer.createMessage().toJson().writeTo( response.getWriter() );
}
private static void reinitializeUISession( HttpServletRequest request ) {
ServiceContext serviceContext = getContext();
UISessionImpl uiSession = ( UISessionImpl )getUISession();
uiSession.shutdown();
UISessionBuilder builder = new UISessionBuilder( serviceContext );
uiSession = builder.buildUISession();
serviceContext.setUISession( uiSession );
}
private static void reinitializeServiceStore() {
ClientMessage clientMessage = ProtocolUtil.getClientMessage();
getServiceStore().clear();
ProtocolUtil.setClientMessage( clientMessage );
}
/*
* Session restart: we're in the same HttpSession and start over (e.g. by pressing F5)
*/
private static boolean isSessionRestart( RequestMessage requestMessage ) {
return isSessionStarted() && hasInitializeParameter( requestMessage );
}
private static boolean isSessionTimeout( RequestMessage message ) {
// Session is not initialized because we got a new HTTPSession
return !isSessionStarted() && !hasInitializeParameter( message );
}
static void markSessionStarted() {
getUISession().setAttribute( ATTR_SESSION_STARTED, Boolean.TRUE );
}
private static boolean isSessionStarted() {
return Boolean.TRUE.equals( getUISession().getAttribute( ATTR_SESSION_STARTED ) );
}
private static boolean isSessionShutdown( RequestMessage requestMessage ) {
return JsonValue.TRUE.equals( requestMessage.getHead().get( SHUTDOWN ) );
}
private static boolean hasInitializeParameter( RequestMessage requestMessage ) {
return JsonValue.TRUE.equals( requestMessage.getHead().get( RWT_INITIALIZE ) );
}
private static void setJsonResponseHeaders( ServletResponse response ) {
response.setContentType( CONTENT_TYPE_JSON );
response.setCharacterEncoding( CHARSET_UTF_8 );
}
private static void writeEmptyMessage( ServletResponse response ) throws IOException {
new ProtocolMessageWriter().createMessage().toJson().writeTo( response.getWriter() );
}
private static void writeResponseMessage( ResponseMessage responseMessage,
ServletResponse response )
throws IOException
{
bufferMessage( responseMessage );
responseMessage.toJson().writeTo( response.getWriter() );
}
private static void writeBufferedResponse( HttpServletResponse response ) throws IOException {
getBufferedMessage().toJson().writeTo( response.getWriter() );
}
private static void bufferMessage( ResponseMessage responseMessage ) {
UISession uiSession = getUISession();
if( uiSession != null ) {
uiSession.setAttribute( ATTR_LAST_RESPONSE_MESSAGE, responseMessage );
}
}
private static ResponseMessage getBufferedMessage() {
return ( ResponseMessage )getUISession().getAttribute( ATTR_LAST_RESPONSE_MESSAGE );
}
}