/******************************************************************************* * 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.REQUEST_COUNTER; import static org.eclipse.rap.rwt.internal.protocol.ClientMessageConst.SHUTDOWN; 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 java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; 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.RequestMessage; import org.eclipse.rap.rwt.internal.protocol.ResponseMessage; import org.eclipse.rap.rwt.internal.remote.MessageChainReference; 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 ATTR_LAST_RESPONSE_MESSAGE = LifeCycleServiceHandler.class.getName() + "#lastResponseMessage"; private final MessageChainReference messageChainReference; public LifeCycleServiceHandler( MessageChainReference messageChainReference ) { this.messageChainReference = messageChainReference; } @Override public void service( HttpServletRequest request, HttpServletResponse response ) throws IOException { UISessionImpl uiSession = ( UISessionImpl )getUISession(); if( uiSession == null ) { setJsonResponseHeaders( response ); writeSessionTimeoutError( response ); } else { // Do not use uiSession itself as a lock // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=372946 synchronized( uiSession.getRequestLock() ) { synchronizedService( request, response ); } } } void synchronizedService( HttpServletRequest request, HttpServletResponse response ) throws IOException { try { processUIRequest( request, response ); } catch( IOException exception ) { shutdownUISession(); throw exception; } catch( RuntimeException exception ) { shutdownUISession(); throw exception; } } private void processUIRequest( HttpServletRequest request, HttpServletResponse response ) throws IOException { RequestMessage requestMessage = readRequestMessage( request ); setJsonResponseHeaders( response ); if( isSessionShutdown( requestMessage ) ) { shutdownUISession(); writeEmptyMessage( response ); } else if( !isRequestCounterValid( requestMessage ) ) { if( isDuplicateRequest( requestMessage ) ) { writeBufferedResponse( response ); } else { writeInvalidRequestCounterError( response ); } } else { ResponseMessage responseMessage = processMessage( requestMessage ); writeResponseMessage( responseMessage, response ); RequestCounter.getInstance().nextRequestId(); } } private static 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 ); } static boolean isRequestCounterValid( RequestMessage requestMessage ) { int expectedRequestId = RequestCounter.getInstance().currentRequestId(); JsonValue sentRequestId = requestMessage.getHead().get( REQUEST_COUNTER ); if( sentRequestId == null ) { return false; } return sentRequestId.asInt() == expectedRequestId; } private static boolean isDuplicateRequest( RequestMessage requestMessage ) { int currentRequestId = RequestCounter.getInstance().currentRequestId(); JsonValue sentRequestId = requestMessage.getHead().get( REQUEST_COUNTER ); return sentRequestId != null && sentRequestId.asInt() == currentRequestId - 1; } 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 boolean isSessionShutdown( RequestMessage requestMessage ) { return JsonValue.TRUE.equals( requestMessage.getHead().get( SHUTDOWN ) ); } 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 ); } }